MySQL 根据条件计算行数

Kay*_*ast 5 mysql sql

我有以下(简化的)表格

users
+----+-------+
| id | name  |
+----+-------+
|  1 | alpha |
|  3 | gamma |
|  5 | five  |
|  7 | seven |
|  9 | nine  |
+----+-------+

user_relationships
+--------------+----------------+----------------------+
| from_user_id | target_user_id | relationship_type_id |
+--------------+----------------+----------------------+
|            1 |              3 |                    1 |
|            1 |              5 |                   -1 |
|            1 |              7 |                    1 |
|            1 |              9 |                    1 |
|            7 |              1 |                    1 |
+--------------+----------------+----------------------+
Run Code Online (Sandbox Code Playgroud)

lation_type_id = 1 代表“以下”

role_type_id = -1 用于“阻止”

由此产生的 alpha 关系是:

  • alpha 跟随 gamma,九个 [following_count = 2]
  • alpha 紧随 7 且 7 紧随 alpha [mutual_count = 1]
  • alpha 正在阻止 5 个 [blocking_count = 1]

伽玛的关系是:

  • alpha 正在跟随 gamma [followed_count = 1]

我需要在输出中捕获上述关系:

Output
+----+-------+-----------------+----------------+--------------+----------------+
| id | name  | following_count | followed_count | mutual_count | blocking_count |
+----+-------+-----------------+----------------+--------------+----------------+
|  1 | alpha |               2 |              0 |            1 |              1 |
|  3 | gamma |               0 |              1 |            0 |              0 |
|  5 | five  |               0 |              0 |            0 |              0 |
|  7 | seven |               0 |              0 |            1 |              0 |
|  9 | nine  |               0 |              1 |            0 |              0 |
+----+-------+-----------------+----------------+--------------+----------------+
Run Code Online (Sandbox Code Playgroud)

我已经花了几个小时来处理 GROUP BY、COUNT、HAVING、DISTINCT、SUM、(SELECT 中的 SUM)等组合,但无法让它工作。

请需要帮助或指导。我很高兴进一步尝试。

下面是基本的 MySQL 查询(没有我搞砸的实验)

select 
    u.id, 
    u.name,
    r1.from_user_id, r1.target_user_id, r1.relationship_type_id,
    r2.from_user_id, r2.target_user_id, r2.relationship_type_id,
    r3.from_user_id, r3.target_user_id, r3.relationship_type_id
from users u
join user_relationships r1
    on u.id = r1.from_user_id
join user_relationships r2
    on u.id = r2.target_user_id
join user_relationships r3
    on u.id = r3.from_user_id or u.id = r3.target_user_id;
Run Code Online (Sandbox Code Playgroud)

Pau*_*gel 2

following_countmutual_count并且可以用条件聚合blocking_count来实现。你可以写一个子查询。followed_count

\n\n
select u.id, u.name\n    , coalesce(sum(r.relationship_type_id = 1 and r1.relationship_type_id is null), 0) as following_count\n    , coalesce(sum(r.relationship_type_id = 1 and r1.relationship_type_id = 1), 0) as mutual_count\n    , coalesce(sum(r.relationship_type_id = -1), 0) as blocking_count\n    , (\n        select count(*)\n        from user_relationships r2\n        left join user_relationships r3 \n          on r3.from_user_id = r2.target_user_id\n          and r3.target_user_id = r2.from_user_id  \n        where r2.target_user_id = u.id\n          and r2.relationship_type_id = 1\n          and r3.from_user_id is null\n    ) as followed_count\nfrom users u\nleft join user_relationships r on r.from_user_id = u.id\nleft join user_relationships r1\n    on  r1.from_user_id = r.target_user_id\n    and r1.target_user_id = r.from_user_id\ngroup by u.id, u.name;\n
Run Code Online (Sandbox Code Playgroud)\n\n

演示: http: //rextester.com/WJED13044

\n\n

更新1

\n\n

另一种方法是首先生成完整的外连接,以便在单行中获得两个方向的关系。那会是这样的

\n\n
select *\nfrom user_relationships r1\nfull outer join user_relationships r2\n  on  r2.from_user_id = r1.target_user_id\n  and r1.from_user_id = r2.target_user_id\n
Run Code Online (Sandbox Code Playgroud)\n\n

但由于 MySQL 不支持完全外连接,我们需要这样的东西:

\n\n
select r.*, r1.relationship_type_id as type1, r2.relationship_type_id as type2\nfrom (\n    select from_user_id uid1, target_user_id uid2 from user_relationships\n    union distinct\n    select target_user_id uid1, from_user_id uid2 from user_relationships\n) r\nleft join user_relationships r1\n  on  r1.from_user_id   = r.uid1\n  and r1.target_user_id = r.uid2\nleft join user_relationships r2\n  on  r2.target_user_id = r.uid1\n  and r2.from_user_id   = r.uid2;\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将返回

\n\n
uid1 \xe2\x94\x82 uid2 \xe2\x94\x82 type1 \xe2\x94\x82 type2\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n   7 \xe2\x94\x82    1 \xe2\x94\x82     1 \xe2\x94\x82     1\n   1 \xe2\x94\x82    7 \xe2\x94\x82     1 \xe2\x94\x82     1\n   1 \xe2\x94\x82    3 \xe2\x94\x82     1 \xe2\x94\x82  null\n   1 \xe2\x94\x82    5 \xe2\x94\x82    -1 \xe2\x94\x82  null\n   1 \xe2\x94\x82    9 \xe2\x94\x82     1 \xe2\x94\x82  null\n   3 \xe2\x94\x82    1 \xe2\x94\x82  null \xe2\x94\x82     1\n   5 \xe2\x94\x82    1 \xe2\x94\x82  null \xe2\x94\x82    -1\n   9 \xe2\x94\x82    1 \xe2\x94\x82  null \xe2\x94\x82     1\n
Run Code Online (Sandbox Code Playgroud)\n\n

这样我们就可以在单行中获得两个方向的关系,因此不需要该列的子查询followed_count,而是可以使用条件聚合

\n\n
select u.id, u.name\n    , coalesce(sum(r1.relationship_type_id = 1 and r2.relationship_type_id is null), 0) as following_count\n    , coalesce(sum(r2.relationship_type_id = 1 and r1.relationship_type_id is null), 0) as followed_count\n    , coalesce(sum(r1.relationship_type_id = 1 and r2.relationship_type_id = 1), 0) as mutual_count\n    , coalesce(sum(r1.relationship_type_id = -1), 0) as blocking_count\nfrom users u\nleft join (\n    select from_user_id uid1, target_user_id uid2 from user_relationships\n    union distinct\n    select target_user_id uid1, from_user_id uid2 from user_relationships\n) r on r.uid1 = u.id\nleft join user_relationships r1\n  on  r1.from_user_id   = r.uid1\n  and r1.target_user_id = r.uid2\nleft join user_relationships r2\n  on  r2.target_user_id = r.uid1\n  and r2.from_user_id   = r.uid2\ngroup by u.id, u.name\norder by u.id;\n
Run Code Online (Sandbox Code Playgroud)\n\n

演示: http: //rextester.com/IFGLT77163

\n\n

这也更灵活,因为我们现在可以轻松添加blocked_count

\n\n
, coalesce(sum(r2.relationship_type_id = -1), 0) as blocked_count\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您使用 MySQL 8 或 MariaDB 10.2,则可以使用CTE编写得更好一些:

\n\n
with bdr as ( -- bidirectional relations\n    select from_user_id uid1, target_user_id uid2 from user_relationships\n    union distinct\n    select target_user_id uid1, from_user_id uid2 from user_relationships\n), rfoj as ( -- relations full outer join\n    select uid1, uid2, r1.relationship_type_id type1, r2.relationship_type_id type2\n    from bdr\n    left join user_relationships r1\n      on  r1.from_user_id   = bdr.uid1\n      and r1.target_user_id = bdr.uid2\n    left join user_relationships r2\n      on  r2.target_user_id = bdr.uid1\n      and r2.from_user_id   = bdr.uid2\n)\n    select u.id, u.name\n        , coalesce(sum(type1 = 1 and type2 is null), 0) as following_count\n        , coalesce(sum(type2 = 1 and type1 is null), 0) as followed_count\n        , coalesce(sum(type1 = 1 and type2 = 1), 0) as mutual_count\n        , coalesce(sum(type1 = -1), 0) as blocking_count\n        , coalesce(sum(type2 = -1), 0) as blocked_count\n    from users u\n    left join rfoj r on r.uid1 = u.id\n    group by u.id, u.name\n    order by u.id\n
Run Code Online (Sandbox Code Playgroud)\n\n

演示:https://www.db-fiddle.com/f/nEDXXkrLEj9F4dKfipzN9Q/0

\n\n

更新2

\n\n

在阅读了您的评论并查看了您对查询的尝试后,我也有了一个“见解”,并且认为应该可以仅通过两个连接而无需子查询来获得结果。

\n\n

可以通过以下方式获得与 FULL OUTER JOIN 类似的结果:

\n\n
select u.*\n    , coalesce(r1.from_user_id, r2.target_user_id) as uid1\n    , coalesce(r2.from_user_id, r1.target_user_id) as uid2\n    , r1.relationship_type_id as type1\n    , r2.relationship_type_id as type2\nfrom users u\nleft join user_relationships r1 on r1.from_user_id = u.id\nleft join user_relationships r2\n    on r2.target_user_id = u.id\n    and (r2.from_user_id = r1.target_user_id or r1.from_user_id is null)\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我们只需要添加 GROUP BY 子句并执行条件聚合,就像我们在其他查询中所做的那样:

\n\n
select u.id, u.name\n    , coalesce(sum(r1.relationship_type_id = 1 and r2.relationship_type_id is null), 0) as following_count\n    , coalesce(sum(r2.relationship_type_id = 1 and r1.relationship_type_id is null), 0) as followed_count\n    , coalesce(sum(r1.relationship_type_id = 1 and r2.relationship_type_id = 1), 0) as mutual_count\n    , coalesce(sum(r1.relationship_type_id = -1), 0) as blocking_count\nfrom users u\nleft join user_relationships r1 on r1.from_user_id = u.id\nleft join user_relationships r2\n    on r2.target_user_id = u.id\n    and (r2.from_user_id = r1.target_user_id or r1.from_user_id is null)\ngroup by u.id, u.name\norder by u.id;\n
Run Code Online (Sandbox Code Playgroud)\n\n

演示: http: //rextester.com/UAS51627

\n\n

注1

\n\n

子句(更新 2OR )中的条件可能会损害性能。这通常通过UNION 优化来解决,这将导致与完全外连接类似的解决方案。ON

\n\n

笔记2

\n\n

就性能而言,带有LEFT JOIN子查询(更新 1)也不是最好的主意,因为 ON 子句不能使用任何索引。最好使用 INNER JOIN 来代替,并在应用程序中(如果确实需要)用缺失的用户(那些根本没有关系的用户)填充结果,或者直接将它们排除在外。

\n