我有一个包含tool_id,时间和消息的元组列表。我想从这个列表中选择消息匹配某些字符串的所有元素,以及所有其他元素,其中时间在该工具的任何匹配消息的某些差异内。
以下是我目前的做法:
# record time for each message matching the specified message for each tool
messageTimes = {}
for row in cdata: # tool, time, message
if self.message in row[2]:
messageTimes[row[0], row[1]] = 1
# now pull out each message that is within the time diff for each matched message
# as well as the matched messages themselves
def determine(tup):
if self.message in tup[2]: return True # matched message
for (tool, date_time) in messageTimes:
if tool == tup[0]:
if abs(date_time-tup[1]) <= tdiff:
return True
return False
cdata[:] = [tup for tup in cdata if determine(tup)]
此代码有效,但运行时间太长 - 例如当cdata有600,000个元素(这是我的应用程序的典型元素)时,运行需要2个小时。
此数据来自数据库。最初我只使用SQL获取了我想要的数据,但这也花了太长时间。我只选择了我想要的消息,然后为每个进行另一个查询的消息选择每个消息的时间差异。这导致了成千上万的查询。所以我改变它以立即拉出所有潜在的匹配,然后在python中处理它,认为这会更快。也许我错了。
任何人都可以就加快这个问题给我一些建议吗?
更新我的帖子以显示我在SQL中所做的事情。
我在SQL中所做的非常简单。第一个查询类似于:
SELECT tool, date_time, message
FROM event_log
WHERE message LIKE '%foo%'
AND other selection criteria
这足够快,但可能会返回20或3万行。然后我循环遍历结果集,并为每一行运行这样的查询(其中dt和t是来自上面选择的行中的date_time和工具):
SELECT date_time, message
FROM event_log
WHERE tool = t
AND ABS(TIMESTAMPDIFF(SECOND, date_time, dt)) <= timediff
这花了大约一个小时。
我还尝试在一个嵌套查询中进行操作,其中内部查询从第一个查询中选择行,外部查询选择时间差异行。这花了更长时间。
所以现在我选择没有消息LIKE'%foo%'子句,我回到了600,000行并尝试从python中提取我想要的行。
答案 0 :(得分:6)
优化SQL的方法是在一个查询中完成所有操作,而不是迭代超过20K行并为每个查询执行另一个查询。
通常这意味着您需要添加JOIN,或者偶尔添加子查询。是的,只要重命名一个或两个副本,就可以将表连接到自身。所以,像这样:
SELECT el2.date_time, el2.message
FROM event_log as el1 JOIN event_log as el2
WHERE el1.message LIKE '%foo%'
AND other selection criteria
AND el2.tool = el1.tool
AND ABS(TIMESTAMPDIFF(SECOND, el2.datetime, el1.datetime)) <= el1.timediff
现在,这可能不会快速开箱即用,因此有两个步骤可以改进它。
首先,查找显然需要编制索引的列。显然tool
和datetime
需要简单的索引。 message
可以从一个简单的索引中受益,或者,如果你的数据库有更好的东西,可能更有趣,但鉴于初始查询足够快,你可能不需要担心它。
偶尔,这就足够了。但通常情况下,你无法正确猜测一切。并且可能还需要重新排列查询的顺序等等。因此,您将需要EXPLAIN
查询,并查看数据库引擎正在执行的步骤,并查看它在执行的操作慢速迭代查找,它可以进行快速索引查找,或者在小集合之前迭代大型集合。
答案 1 :(得分:2)
对于表格数据,您无法浏览Python pandas库,其中包含针对此类查询的高度优化代码。
答案 2 :(得分:0)
我通过更改我的代码修复了这个问题:
- 首先我将messageTimes作为工具键入的列表的字典:
messageTimes = defaultdict(list) # a dict with sorted lists
for row in cdata: # tool, time, module, message
if self.message in row[3]:
messageTimes[row[0]].append(row[1])
- 然后在确定函数中我使用了bisect:
def determine(tup):
if self.message in tup[3]: return True # matched message
times = messageTimes[tup[0]]
le = bisect.bisect_right(times, tup[1])
ge = bisect.bisect_left(times, tup[1])
return (le and tup[1]-times[le-1] <= tdiff) or (ge != len(times) and times[ge]-tup[1] <= tdiff)
通过这些更改,花费超过2小时的代码花了不到20分钟,甚至更好,一个耗时40分钟的查询耗时8秒!
答案 3 :(得分:0)
我做了2个更改,现在20分钟查询需要3分钟:
found = defaultdict(int)
def determine(tup):
if self.message in tup[3]: return True # matched message
times = messageTimes[tup[0]]
idx = found[tup[0]]
le = bisect.bisect_right(times, tup[1], idx)
idx = le
return (le and tup[1]-times[le-1] <= tdiff) or (le != len(times) and times[le]-tup[1] <= tdiff)