【问题标题】:SQL - Computing overlap between InterestsSQL - 计算兴趣之间的重叠
【发布时间】:2016-01-28 21:53:14
【问题描述】:

我有一个如下所示的架构(具有适当索引的数百万条记录):

groups    |  interests
------    |  ---------
user_id   |  user_id
group_id  |  interest_id

一个用户可以喜欢 0..多个兴趣并属于 0..多个组。

问题:给定一个组 ID,我想获取不属于该组的所有用户的所有兴趣,并且与属于该组的任何人共享至少一个兴趣提供相同的组。

由于上述内容可能令人困惑,这里有一个简单的示例 (SQLFiddle):

| 1 | 2 | 3 | 4 | 5 | (User IDs)
|-------------------|
| A |   | A |   |   |
| B | B | B |   | B |
|   | C |   |   |   |
|   |   | D | D |   |

在上面的示例中,用户用数字标记,而兴趣用字符标记。

如果我们假设用户 1 和 2 属于组 -1,那么用户 3 和 5 会很有趣:

user_id  interest_id
-------  -----------
      3            A
      3            B
      3            D
      5            B

我已经编写了一个愚蠢且非常低效的查询,可以正确返回上述内容:

SELECT * FROM "interests" WHERE "user_id" IN (
    SELECT "user_id" FROM "interests" WHERE "interest_id" IN (
        SELECT "interest_id" FROM "interests" WHERE "user_id" IN (
            SELECT "user_id" FROM "groups" WHERE "group_id" = -1
        )
    ) AND "user_id" NOT IN (
        SELECT "user_id" FROM "groups" WHERE "group_id" = -1
    )
);

但我所有将其转换为正确连接查询的尝试都表明自己没有结果:查询返回的行数比它应该返回的行多,或者它只需要子查询的 10 倍,例如:

SELECT "iii"."user_id" FROM "interests" AS "iii"
WHERE EXISTS
(
    SELECT "ii"."user_id", "ii"."interest_id" FROM "groups" AS "gg"
    INNER JOIN "interests" AS "ii" ON "gg"."user_id" = "ii"."user_id"
    WHERE EXISTS
    (
        SELECT "i"."interest_id" FROM "groups" AS "g"
        INNER JOIN "interests" AS "i" ON "g"."user_id" = "i"."user_id"
        WHERE "group_id" = -1 AND "i"."interest_id" = "ii"."interest_id"
    ) AND "group_id" != -1 AND "ii"."user_id" = "iii"."user_id"
);

在过去的两个晚上,我一直在努力优化这个查询......

我们将不胜感激任何能让我朝着正确方向前进的帮助或见解。 :)


PS:理想情况下,一个返回共同兴趣汇总计数的查询会更好:

user_id  totalInterests  commonInterests
-------  --------------  ---------------
      3               3              1/2 (either is fine, but 2 is better)
      5               1                1

但是,我不确定它与在代码中执行相比会慢多少。

【问题讨论】:

  • 是什么让您认为联接更“合适”?
  • @CL。只是探索性的基准,没有别的。我知道,对于 JOIN 与子查询,并不总是有明显的赢家。但是,事实证明,PhilipKelley 的答案比我原来的方法快 100 倍以上(我想主要是因为使用了 CTE)。
  • 非递归 CTE 对查询优化器有 no 影响;它们只是作为子查询插入到FROM cte 中。它们仅对文档有用,或在多次使用时用于节省输入。

标签: sql sqlite join query-optimization


【解决方案1】:

使用以下设置测试表

--drop table Interests  ----------------------------
CREATE TABLE Interests
 (
   InterestId  char(1)  not null
  ,UserId      int      not null
 )

INSERT Interests values
  ('A',1)
 ,('A',3)
 ,('B',1)
 ,('B',2)
 ,('B',3)
 ,('B',5)
 ,('C',2)
 ,('D',3)
 ,('D',4)


--  drop table Groups  ---------------------
CREATE TABLE Groups
 (
   GroupId  int  not null
  ,UserId   int  not null
 )

INSERT Groups values
  (-1, 1)
 ,(-1, 2)


SELECT * from Groups
SELECT * from Groups

以下查询似乎可以满足您的要求:

DECLARE @GroupId int

SET @GroupId = -1

;WITH cteGroupInterests (InterestId)
 as (--  List of the interests referenced by the target group
     select distinct InterestId
      from Groups gr
       inner join Interests nt
        on nt.UserId = gr.UserId
      where gr.GroupId = @GroupId)
--  Aggregate interests for each user
SELECT
   UserId
  ,count(OwnInterstId)      OwnInterests
  ,count(SharedInterestId)  SharedInterests
 from (--  Subquery lists all interests for each user
       select
          nt.UserId
         ,nt.InterestId   OwnInterstId
         ,cte.InterestId  SharedInterestId
        from Interests nt
         left outer join cteGroupInterests cte
          on cte.InterestId = nt.InterestId
        where not exists (--  Correlated subquery: is "this" user in the target group?)
                          select 1
                           from Groups gr
                           where gr.GroupId = @GroupId
                            and gr.UserId = nt.UserId)) xx
 group by UserId
 having count(SharedInterestId) > 0

它似乎有效,但我想做更精细的测试,我不知道它对数百万行的效果如何。重点是:

  • cte 创建一个临时表,供后面的查询引用;构建一个实际的临时表可能会提高性能
  • 相关的子查询可能很棘手,但索引和not exists 应该会很快完成
  • 我很懒,把所有的下划线都省略了,对不起

【讨论】:

  • 对不起,我花了这么长时间才找到时间来研究这个。 CTE 确实有很大帮助(我之前尝试过使用它们,但不知道具体是如何使用的),我最终创建了另一个 CTE 来容纳组成员,并且加速效果更加明显!顺便说一句,WHERE nt.UserId NOT IN cteGroup 似乎比NOT EXISTS 方法更快。非常感谢!
  • @AlixAxel:非常小心NOT IN 和可能的空值。如果NOT IN 明显更快,那么您很可能得到错误 结果。详情请见stackoverflow.com/a/11074428/484293
【解决方案2】:

这有点令人困惑。我认为最好的方法是existsnot exists

select i.*
from interest i
where not exists (select 1
                  from groups g
                  where i.user_id = g.user_id and
                        g.group_id = $group_id
                 ) and
      exists (select 1
              from groups g join
                   interest i2
                   on g.user_id = i2.user_id
              where g.user_id <> i.user_user_id and
                    i.interest_id = i2.interest_id
             );

第一个子查询表示用户不在组中。第二个是说兴趣与小组中的某个人共享。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    • 1970-01-01
    相关资源
    最近更新 更多