将大型SQL查询转换为NumPy
我在我的网页应用中有一个非常大的MySQL查询,长得像这样:
query =
SELECT video_tag.video_id, (sum(user_rating.rating) * video.rating_norm) as score
FROM video_tag
JOIN user_rating ON user_rating.item_id = video_tag.tag_id
JOIN video ON video.id = video_tag.video_id
WHERE item_type = 3 AND user_id = 1 AND rating != 0 AND video.website_id = 2
AND rating_norm > 0 AND video_id NOT IN (1,2,3) GROUP BY video_id
ORDER BY score DESC LIMIT 20"
这个查询是把三个表(视频表、视频标签表和用户评分表)连接在一起,整理结果,并进行一些基本的数学运算来计算每个视频的分数。因为这些表都很大,所以这个过程大约需要2秒钟。
我在想,既然SQL要做这么多工作,可能用NumPy数组来计算会更快一些。因为'视频'和'视频标签'里的数据是固定的,所以我可以把这两个表的数据一次性加载到内存里,这样就不用每次都去问SQL了。
不过,虽然我可以把这三个表的数据分别加载到三个数组里,但我在复制上面的查询时遇到了麻烦,特别是JOIN和GROUP BY的部分。有没有人有用NumPy数组来复制SQL查询的经验呢?
谢谢!
1 个回答
3
这个练习让人觉得有点尴尬的原因是NumPy数组的单一数据类型限制。举个例子,进行GROUP BY操作时,至少需要有一个连续值的字段/列(用来聚合或求和),还有一个字段/列用来进行分组。
当然,NumPy的记录数组(recarrays)可以用不同的数据类型来表示一个二维数组(或者说SQL表),每一列可以有不同的类型(也叫“字段”)。不过我觉得这些组合数组使用起来挺麻烦的。所以在下面的代码示例中,我只是用了传统的ndarray类来模拟提问者问题中提到的两个SQL操作。
在NumPy中模拟SQL JOIN:
首先,创建两个NumPy数组(A和B),每个数组代表一个SQL表。A的主键在第一列;B的外键也在第一列。
import numpy as NP
A = NP.random.randint(10, 100, 40).reshape(8, 5)
a = NP.random.randint(1, 3, 8).reshape(8, -1) # add column of primary keys
A = NP.column_stack((a, A))
B = NP.random.randint(0, 10, 4).reshape(2, 2)
b = NP.array([1, 2])
B = NP.column_stack((b, B))
现在(尝试)用NumPy数组对象来模拟JOIN操作:
# prepare the array that will hold the 'result set':
AB = NP.column_stack((A, NP.zeros((A.shape[0], B.shape[1]-1))))
def join(A, B) :
'''
returns None, side effect is population of 'results set' NumPy array, 'AB';
pass in A, B, two NumPy 2D arrays, representing the two SQL Tables to join
'''
k, v = B[:,0], B[:,1:]
dx = dict(zip(k, v))
for i in range(A.shape[0]) :
AB[i:,-2:] = dx[A[i,0]]
在NumPy中模拟SQL GROUP BY:
def group_by(AB, col_id) :
'''
returns 2D NumPy array aggregated on the unique values in column specified by col_id;
pass in a 2D NumPy array and the col_id (integer) which holds the unique values to group by
'''
uv = NP.unique(AB[:,col_id])
temp = []
for v in uv :
ndx = AB[:,0] == v
temp.append(NP.sum(AB[:,1:][ndx,], axis=0))
temp = NP. row_stack(temp)
uv = uv.reshape(-1, 1)
return NP.column_stack((uv, temp))
对于一个测试案例,它们返回了正确的结果:
>>> A
array([[ 1, 92, 50, 67, 51, 75],
[ 2, 64, 35, 38, 69, 11],
[ 1, 83, 62, 73, 24, 55],
[ 2, 54, 71, 38, 15, 73],
[ 2, 39, 28, 49, 47, 28],
[ 1, 68, 52, 28, 46, 69],
[ 2, 82, 98, 24, 97, 98],
[ 1, 98, 37, 32, 53, 29]])
>>> B
array([[1, 5, 4],
[2, 3, 7]])
>>> join(A, B)
array([[ 1., 92., 50., 67., 51., 75., 5., 4.],
[ 2., 64., 35., 38., 69., 11., 3., 7.],
[ 1., 83., 62., 73., 24., 55., 5., 4.],
[ 2., 54., 71., 38., 15., 73., 3., 7.],
[ 2., 39., 28., 49., 47., 28., 3., 7.],
[ 1., 68., 52., 28., 46., 69., 5., 4.],
[ 2., 82., 98., 24., 97., 98., 3., 7.],
[ 1., 98., 37., 32., 53., 29., 5., 4.]])
>>> group_by(AB, 0)
array([[ 1., 341., 201., 200., 174., 228., 20., 16.],
[ 2., 239., 232., 149., 228., 210., 12., 28.]])