将连接的结果组合在两个表上

时间:2012-07-05 18:52:10

标签: sql postgresql union left-join

我有3张桌子

items
  tag_id
  mark_id

tags_users
  tag_id
  user_id

marks_users
  mark_id
  user_id

有没有办法为没有items和嵌套选择的特定user_id选择唯一UNION

SELECT items.*
FROM items
INNER JOIN tags_users ON tags_users.tag_id = items.tag_id
AND  tags_users.user_id = 5

UNION

SELECT items.*
FROM items
INNER JOIN marks_users ON marks_users.mark_id = items.mark_id
AND marks_users.user_id = 5

2 个答案:

答案 0 :(得分:2)

(tag_id, mark_id)

链接
SELECT DISTINCT i.*
FROM   tags_users  tu  
JOIN   marks_users mu USING (user_id)
JOIN   items       i  USING (tag_id, mark_id)
WHERE  tu.user_id = 5;

如果您在列上定义了多列主键或唯一键,则不需要DISTINCT

tag_id mark_id

关联

@Gordon的回答完全有效。但它会执行非常 这会快得多:

SELECT i.*
FROM   items i  
WHERE  EXISTS (
    SELECT 1
    FROM   tags_users  tu
    WHERE  tu.tag_id = i.tag_id
    AND    tu.user_id = 5
    )
OR     EXISTS (
    SELECT 1
    FROM   marks_users mu 
    WHERE  mu.mark_id = i.mark_id
    AND    mu.user_id = 5
    );

假设items中的条目本身在(tag_id, mark_id)上是唯一的。

为什么更快?

如果你JOIN到两个不相关的表(比如@ Gordon的答案),你就可以有效地形成一个交叉连接,这种连接以随着行数的增加而迅速降低性能而闻名。 O(N²)。说,你有:

  • 100个用户,100个标签和100个标记。
  • 每种组合都存在(简单的假设设置,现实生活数据将不那么平衡)。
  • 每个表中的10,000行结果。

这将发生在@Gordon的查询中:

  1. 加入itemstags_users的行。每个项目连接到100行,结果 10,000 x 100 = 1,000,000行。 (!)
  2. 加入marks_users。每行连接到100个标记,从而产生 100,000,000行。 (!!)
  3. WHERE子句已应用,许多重复项被DISTINCT折叠,产生10,000行。
  4. 使用EXPLAIN ANALYZE进行测试。即使数量很少,而且随着数量的增加,这种差异也会很明显。

    SQL Fiddle.

    基准

    我在我的机器上使用此设置进行了一些快速测试(第9.1页):

    戈登的询问

    SELECT DISTINCT i.*
    FROM   items i
    LEFT   JOIN tags_users tu on i.tag_id = tu.tag_id
    LEFT   JOIN marks_users mu on i.mark_id = mu.mark_id
    WHERE  5 IN (tu.user_id, mu.user_id);
    

    总运行时间: 38229.860 ms

    Sanitized version

    user_id中的条件拉入JOIN条款可以从根本上减少组合,但它仍然是(更小的)交叉连接

    SELECT DISTINCT i.*
    FROM   items i
    LEFT   JOIN tags_users tu on i.tag_id = tu.tag_id AND tu.user_id = 5
    LEFT   JOIN marks_users mu on i.mark_id = mu.mark_id AND mu.user_id = 5
    WHERE  tu.user_id = 5 OR mu.user_id = 5;
    

    总运行时间: 110.450 ms

    使用EXISTS半连接

    (见上文查询)
    使用此查询,如果符合条件,则会检查每一行一次。您不需要DISTINCT,因为行不会重复开始。

    总运行时间: 26.569 ms

    UNION

    为了完整性,使用UNION的变体。使用UNION而非UNION ALL删除重复项:

    SELECT i.*
    FROM   items i 
    JOIN   tags_users  tu ON i.tag_id = tu.tag_id AND tu.user_id = 5
    UNION
    SELECT i.*
    FROM   items i 
    JOIN   marks_users mu ON i.mark_id = mu.mark_id AND mu.user_id = 5;
    

    总运行时间: 178.901 ms

答案 1 :(得分:1)

我认为您可以通过将表连接在一起并查看标记和标记表中的用户ID来实现此目的。你必须要小心重复。

以下是如何执行此操作的示例:

select distinct i.tag_id, i.user_id
from items i left outer join
     tags_users tu
     on i.tag_id = tu.tag_id left outer join
     marks_users mu
     on i.mark_id = mu.mark_id
where 5 in (tu.user_id, mu.user_id)

或者您可以将where子句更改为:

where tu.user_id = 5 or mu.user_id = 5

我想强调的是,这个答案解决了原始问题,该问题询问了制定查询的特定方式(不使用连接或子查询)。此查询可能效率不高;但是,它回答了原来的问题。我不知道为什么原始问题将这些限制放在答案上,但我选择不解决限制,只讨论所提出的问题。我绝对使用工会和子查询;事实上,我有时会批评过度使用后者。

在某些数据库中,这将被有效地编译;其他人(比如postgres)似乎做得更差。但是,原始问题并没有指明数据的大小,也没有提供有关性能需求的任何提示。