在具有特定基数的映射(联结)表之间加入

时间:2012-02-07 02:08:46

标签: sql join mapping relational-division

我有一个关于执行特定连接的最有效方法的简单问题。

拿这三张桌子,实名改变了以保护无辜者:

表:动物

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),计划相当广泛。对于查询这个“简单”,我通常会尝试使用单个或两个数字的估计成本来获取查询计划。

所以我的问题是,有更好的方法来执行此查询吗?看起来像一个简单的查询,但我已经难过了更好的东西。

5 个答案:

答案 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并删除co​​unt(*)):

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?我的大脑沸腾了:))