我有一个关于执行特定连接的最有效方法的简单问题。
拿这三张桌子,实名改变了以保护无辜者:
表:动物
animal_id name ... ====================== 1 bunny 2 bear 3 cat 4 mouse
表:标签
tag_id tag ================== 1 fluffy 2 brown 3 cute 4 small
映射表:animal_tag
animal_id tag_id ================== 1 1 1 2 1 3 2 2 3 4 4 2
我想找到所有被标记为'蓬松','棕色'和'可爱'的动物。也就是说,动物必须用所有三个标记。实际上,所需标签的数量可能会有所不同,但与此讨论无关。这是我提出的查询:
SELECT * FROM animal
JOIN (
SELECT at.animal_id FROM animal_tag at
WHERE at.tag_id IN (
SELECT tg.tag_id FROM tag tg
WHERE tg.tag='fluffy' OR tg.tag='brown' OR tg.tag='cute'
)
GROUP BY at.animal_id HAVING COUNT(at.tag_id)=3
) AS jt
ON animal.animal_id=jt.animal_id
在包含数千个“动物”和数百个“标签”的桌子上,此查询的表现相当于...... 10毫秒。但是,当我查看查询计划(Apache Derby是数据库)时,优化程序的估计成本相当高(9945.12),计划相当广泛。对于查询这个“简单”,我通常会尝试使用单个或两个数字的估计成本来获取查询计划。
所以我的问题是,有更好的方法来执行此查询吗?看起来像一个简单的查询,但我已经难过了更好的东西。
答案 0 :(得分:1)
您可以使用DECLARE GLOBAL TEMPORARY TABLE创建临时表 然后做一个INNER JOIN来消除“WHERE IN”。使用基于集合的联接通常比必须为每行计算的Where语句更有效。
答案 1 :(得分:1)
试试这个:
SELECT DISTINCT f.Animal_ID, g.Name
FROM Animal f INNER JOIN
(SELECT a.Animal_ID, a.Name, COUNT(*) as iCount
FROM Animal a INNER JOIN Animal_Tag b
ON a.Animal_ID = b.animal_ID
INNER JOIN Tags c
On b.tag_ID = c.tag_ID
WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here
GROUP BY a.Animal_ID) g
WHERE g.iCount = 3 -- No. of tags
<强>更新强>
SELECT DISTINCT a.Animal_ID, a.Name, COUNT(*) as iCount
FROM Animal a INNER JOIN Animal_Tag b
ON a.Animal_ID = b.animal_ID
INNER JOIN Tags c
On b.tag_ID = c.tag_ID
WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here
GROUP BY Animal_ID
HAVING iCount = 3 -- No. of tags
答案 2 :(得分:1)
给它一个旋转:
SELECT a.*
FROM animal a
INNER JOIN
(
SELECT at.animal_id
FROM tag t
INNER JOIN animal_tag at ON at.tag_id = t.tag_id
WHERE tag IN ('fluffy', 'brown', 'cute')
GROUP BY at.animal_id
HAVING count(*) = 3
) f ON a.animal_id = f.animal_id
这是另一种选择,只是为了它的乐趣:
SELECT a.*
FROM animal a
INNER JOIN animal_tag at1 on at1.animal_id = a.animal_id
INNER JOIN tag t1 on t1.tag_id = at1.tag_id
INNER JOIN animal_tag at2 on at2.animal_id = a.animal_id
INNER JOIN tag t2 on t2.tag_id = at2.tag_id
INNER JOIN animal_tag at3 on at3.animal_id = a.animal_id
INNER JOIN tag t3 on t3.tag_id = at3.tag_id
WHERE t1.tag = 'fluffy' AND t2.tag = 'brown' AND t3.tag = 'cute'
我真的不希望这最后一个选项做得好...其他选项避免需要多次返回标签表来解析id中的标签名称...但你永远不知道查询是什么优化器会一直运行,直到你尝试它。
答案 3 :(得分:1)
首先,非常感谢所有参与其中的人。最终答案是,正如几位评论者所引用的那样,关系师。
虽然我在许多月前参加了Codd的关系数据模型课程,但课程很多,并没有真正涵盖关系师。在不知不觉中,我的原始查询实际上是关系部门的应用程序。
参考this presentation关于关系分区的幻灯片26-27,我的查询应用比较设定基数的技巧。我尝试了一些其他提到的应用关系除法的方法,但至少在我的情况下,计数方法提供了最快的运行时间。我鼓励任何对此问题感兴趣的人阅读上述幻灯片,以及Mikael Eriksson在本页中引用的文章。再次感谢大家。
答案 4 :(得分:0)
我想知道在那里使用关系部门会有多糟糕。你可以试一试吗?我知道这会花费更多,但我很感兴趣:)如果你能提供估计的成本和时间,那就太棒了。
select a2.animal_id, a2.animal_name from animal2 a2
where not exists (
select * from animal1 a1, tags t1
where not exists (
select * from animal_tag at1
where at1.animal_id = a1.animal_id and at1.animal_tag = t1.tag_id
) and a2.animal_id = a1.animal_id and t1.tag in ('fluffy', 'brown', 'cute')
)
现在正在寻找一个快速的查询,我想不到比约翰或你的更快。实际上john可能比你的慢一点,因为他正在执行不必要的操作(从select中删除distinct并删除count(*)):
SELECT a.Animal_ID, a.Name FROM Animal a
INNER JOIN Animal_Tag b ON a.Animal_ID = b.animal_ID
INNER JOIN Tags c On b.tag_ID = c.tag_ID
WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here
GROUP BY Animal_ID, a.Name
HAVING count(*) = 3 -- No. of tags
这应该和你的一样快。
PS:有没有办法在没有复制where子句的情况下删除该死的3?我的大脑沸腾了:))