1

我有 3 张桌子

items
  tag_id
  mark_id

tags_users
  tag_id
  user_id

marks_users
  mark_id
  user_id

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

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
4

2 回答 2

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 的回答),您可以有效地形成一个cross join,它以随着行数的增加而迅速降低性能而闻名。O(N²)。说,你有:

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

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

  1. 加入 to 的itemstags_users。每个项目连接到 100 行,结果为 10,000 x 100 = 1,000,000 行。(!)
  2. 加入到marks_users. 每行连接到 100 个标记,从而产生 100,000,000 行。(!!)
  3. 应用该WHERE子句并且许多重复项被 折叠DISTINCT,从而产生 10,000 行。

用 测试EXPLAIN ANALYZE。即使数量很少并且数量不断增加,差异也会很明显。

SQL小提琴。

基准

我在我的机器上使用此设置进行了一些快速测试(第 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 毫秒

消毒版

将条件拉user_idJOIN子句从根本上减少了组合,但它仍然是一个(更小)的 cross 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 毫秒

使用 EXISTS 半连接

(参见上面的查询)
使用此查询,每行检查一次是否符合条件。您不需要 a DISTINCT,因为从一开始就不会重复行。

总运行时间:26.569 毫秒

联盟

为了完整起见,带有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 毫秒

于 2012-07-05T19:53:29.547 回答
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)似乎做得更差。但是,最初的问题没有指定数据的大小,也没有给出任何关于性能需求的暗示。

于 2012-07-05T19:16:14.430 回答