Sql 自连接以及不匹配的行

Sql self join along with non matching rows

我有一个table像这样

id | user | name | action | created_at
--------------------------------------------------
1  | 42    | eve | open   | 2020-01-06 06:17:42
2  | 42    | eve | close  | 2020-01-06 06:27:42
3  | 42    | eve | open   | 2020-01-06 06:37:42
4  | 42    | eve | close  | 2020-01-06 06:47:42
5  | 42    | eve | open   | 2020-01-06 06:57:42

我需要得到这个 table:

user | name | open | open_created_at     | close | close _created_at 
-----------------------------------------------------------------------
42   | eve  | open | 2020-01-06 06:17:42 | close | 2020-01-06 06:27:42
42   | eve  | open | 2020-01-06 06:37:42 | close | 2020-01-06 06:47:42
42   | eve  | open | 2020-01-06 06:57:42 | null  | null

这是 table 我得到的:

SELECT t1.user, t1.name, t1.action, t1.created_at, t2.action, t2.created_at 
FROM tabel t1, table t2
WHERE t1.user = t2.user AND t1.action = 'open' AND t2.action = 'close' AND t1.created_at < t2.created_at
GROUP BY t1.id


user | name | open | open_created_at     | close | close _created_at 
-----------------------------------------------------------------------
42   | eve  | open | 2020-01-06 06:17:42 | close | 2020-01-06 06:27:42
42   | eve  | open | 2020-01-06 06:37:42 | close | 2020-01-06 06:47:42

如何在同一列中获得匹配 open/close 的 table 以及没有匹配关闭列的行?

您需要使用 LEFT JOIN 而不是 INNER JOIN,并且您需要将 WHERE 子句移动到 JOIN 条件中。您还应该在 t2 值周围添加聚合函数以确保结果一致:

SELECT t1.user, t1.name, t1.action AS open, t1.created_at AS open_created_at,
       MIN(t2.action) AS close, MIN(t2.created_at) AS close_created_at
FROM log t1
LEFT JOIN log t2 ON t1.user = t2.user AND t2.action = 'close' AND t1.created_at < t2.created_at
WHERE t1.action = 'open'
GROUP BY t1.id, t1.name, t1.action, t1.created_at

输出:

user    name    open    open_created_at         close   close_created_at
42      eve     open    2020-01-06 06:17:42     close   2020-01-06 06:27:42
42      eve     open    2020-01-06 06:37:42     close   2020-01-06 06:47:42
42      eve     open    2020-01-06 06:57:42     (null)  (null)

Demo on SQLFiddle

另一种方法是将过程分为 3 个步骤:

  • 收集所有open的行,按created_at升序排列。在此过程中,创建一个变量,比如 @rank 以升序为每一行分配排名,如 1,2,3 等等。

  • 收集所有close的行,按created_at升序排列。在此过程中,创建一个变量,比如 @rank2 以升序为每一行分配排名,如 1,2,3 等等。

  • 现在,根据rank对两个表执行left join

查询片段:

select d1.user,d1.name,d1.action as open,d1.open_created_at,d2.action as close,d2.close_created_at
from 
(
  select @rank := @rank + 1 as rank,user,name,action,created_at as open_created_at
  from log,(select  @rank := 0) l1
  where action = 'open'
  order by created_at asc
) d1
left join
(
 select @rank2 := @rank2 + 1 as rank,user,name,action,created_at as close_created_at
  from log,(select  @rank2 := 0) l2
  where action = 'close'
  order by created_at asc

) d2
on d1.rank = d2.rank

DB Fiddle: https://www.db-fiddle.com/f/hYHyz45rtuiEXGQPbz3m6z/0