【问题标题】:Effective query with multiple conditions多条件有效查询
【发布时间】:2012-04-19 16:01:37
【问题描述】:

我有一个数据库

books          (primary key: bookID)
characterNames (foreign key: books.bookID) 
locations      (foreign key: books.bookID)

字符名称和位置的文本位置保存在相应的表中。
现在我想使用 psycopg2 编写一个 Python 脚本来查找给定字符名称和书中给定位置的所有出现,两者都出现。
目前,我执行 4 个查询:

SELECT bookID, position FROM characterNames WHERE name='XXX';
--> result is saved in list 'charnames'

SELECT DISTINCT bookID FROM characterNames WHERE name='XXX';
--> result is saved in list 'charnamesIDs'

SELECT bookID, position FROM locations WHERE locName='YYY';
--> result is saved in list 'locs'

SELECT bookID FROM locations WHERE locName='YYY';
--> result is saved in list 'locsIDs'

这两个查询都可以为我提供仅显示名称或位置的 bookID。所以我的目标是消除所有的 'charnames' 元素,而 bookID 没有出现在 'locs' 中,反之亦然。我的做法是:

for cnameTuple in charnames:  
~if cnameTuple[0] in locsIDs:  
~~continue  
~del(cname)

我为 locs 中的元组做了一个相应的循环。
不幸的是,这个算法需要很多时间。有没有办法更快地完成这项任务?

【问题讨论】:

标签: python postgresql psycopg2


【解决方案1】:

如果使用 JOIN 进行查询,这可能会更快、更简单
像这样的:

SELECT b.*, c.position, l.position
FROM   books b
JOIN   characternames c USING (bookid)
JOIN   locations l USING (bookid)
WHERE  c.name = 'XXX'
AND    l.locname = 'YYY';

评论后的更多信息

“数千本书”对于像 PostgreSQL 这样旨在处理百万的 RDBMS 来说完全没有问题。大表性能的关键是正确的indexes。对于此处的查询,以下索引可能会有所帮助:

CREATE INDEX books_bookid_idx ON books(bookid); -- a primary key will do, too

CREATE INDEX cn_bookid_idx ON characternames (bookid);
CREATE INDEX cn_name_idx ON characternames (name);

CREATE INDEX locations_bookid_idx ON locations (bookid);
CREATE INDEX locations_locname_idx ON locations (locname);

Multicolumn indexes 的性能可能会更好。使用EXPLAIN ANALYZE 进行测试,它将显示使用了哪些索引以及查询的速度。创建索引非常快,试验它们很容易。只是不要保留不需要的索引。它们也有维护成本。


优化查询

think我现在明白了,你在找什么。应该优化此查询以获取每个 bookid 的位置或名称的所有位置,但仅限于名称 位置出现在同一本书中的位置,并且每本书没有更多详细信息:

WITH b AS (
    SELECT bookid
    FROM   characternames
    WHERE  name = 'XXX'
    GROUP  BY 1
    INTERSECT
    SELECT bookid
    FROM   locations
    WHERE  l.locname = 'YYY'
    GROUP  BY 1
    )
SELECT bookid, position, 'char' AS what
FROM   b
JOIN   characternames USING (bookid)
WHERE  name = 'XXX'
UNION  ALL
SELECT bookid, position, 'loc' AS what
FROM   b
JOIN   locations USING (bookid)
WHERE  locname = 'YYY'
ORDER  BY bookid, position;

要点

  • CTE (WITH query) 确保基本查询只执行一次。
  • INTERSECT 只选择具有位置名称的bookids
  • 最终SELECT 中的UNION ALL 返回所有 找到的位置。如果要修剪具有相同位置的重复项,请改用 UNION
  • 我通过 bookid, position 订购 - 我猜这是需要的。
  • 添加了what 列来标记位置的来源(位置或名称)。

进一步优化

如果搜索词在每本书中出现多次,您可以通过为(bookid, term) 创建带有不同 条目的辅助表,从而大大加快搜索速度。在两列上创建一个多列主索引,并在 term 上创建一个附加主索引。为位置创建一个这样的表,为名称创建另一个表。如果需要,请让它们与触发器保持同步,但我认为书籍的内容没有太大变化。将简化并加快 CTE。

如果还不够快,请查看Full Text Search

【讨论】:

  • 您好,很抱歉我还没有回复。使用 JOIN 是我的第一种方法,但有人告诉我它很昂贵。我正在处理包含数千本书的语料库,因此交叉连接可能需要一些时间。 (我可能错了,但你的解决方案有点像交叉连接和附加条件。如果我错了,请随时纠正我。)我对此很好,但有没有可能获得所有职位一次?作为 (bookID/c.position/l.position) 元组,我得到例如4/10/20 4/10/24 4/18/20 4/18/24 我得到每个 bookID 的每个 (name/locname) 对,所以还有冗余数据。
  • @EarlGrey:交叉连接是没有连接条件的连接。这里没有交叉连接。虽然连接有其成本,但它保证比您在问题中的速度快一个数量级。现在,如果您清楚地定义每本书的 哪个 位置以及您想要哪些其他数据,我将能够提供查询。首先?最后的?来自characterNames 还是来自locations?所有位置的数组?如果您无法清楚地表达它,请尝试使用示例数据来展示您想要的内容。但是为此编辑您的问题。不要放在 cmets 中。
  • 我猜了我认为你可能需要什么。
  • +1 表示出色的回答。接受我所有的赞成票!
  • 是的,这正是我所需要的。对不起,我说得不够清楚。非常感谢您在 SQL 和 stackoverflow 上的帮助。
【解决方案2】:

如果可以加快操作,您可以使用 set to see

>>> xxx = set([(1,'a'), (2,'b')])
>>> xxx
set([(1, 'a'), (2, 'b')])
>>> xxx = set([(1,'a'), (3,'c')])
>>> yyy
set([(1, 'a'), (3, 'c')])
>>> c = xxx.intersection(yyy)
>>> c
set([(1, 'a')])   # common between xxx and yyy
>>> xxx - c
set([(2, 'b')])

【讨论】:

    猜你喜欢
    • 2011-12-16
    • 1970-01-01
    • 2019-07-27
    • 2021-09-23
    • 2023-03-12
    • 2017-04-13
    • 2012-12-01
    • 2020-09-19
    相关资源
    最近更新 更多