将特定行转换为列的 SQL 查询

Posted

技术标签:

【中文标题】将特定行转换为列的 SQL 查询【英文标题】:SQL Query to convert Particular Rows into Column 【发布时间】:2019-10-08 07:29:37 【问题描述】:

假设数据

ID          Date                    Mode
1           2019-09-20 09:28      IN
2           2019-09-20 19:00      IN
3           2019-09-20 19:00      IN
4           2019-09-20 19:00      IN
5           2019-09-20 19:01      IN
6           2019-09-20 19:01      IN
7           2019-09-20 19:01      Out
8           2019-09-20 20:28      IN
9           2019-09-20 20:35      IN
10          2019-09-20 20:50      Out
11          2019-09-20 20:55      Out
12          2019-09-20 21:30      IN

转换成最小签到到最大签出的周期行

我使用迭代来实现期望的结果,但想要优化查询(设置基数或 CTE)以提高性能,

这就是我想要的

ID      DateIN              ID          DateOut
01      2019-09-20 09:28    07          2019-09-20 19:01
08      2019-09-20 20:28    11          2019-09-20 20:55

【问题讨论】:

您确定您使用的是 SQL Server 2008 吗?它不再受支持。 【参考方案1】:

这是一个基于间隙和孤岛的问题。

首先,您可以使用以下查询生成连续记录组:

SELECT [Mode], MIN(id) min_id, MAX(id) max_id, MIN([Date]) min_date, MAX([Date]) max_date
FROM (
    SELECT
        id,
        [Date],
        [Mode],
        ROW_NUMBER() OVER(ORDER BY [Date]) rn1,
        ROW_NUMBER() OVER(PARTITION BY [Mode] ORDER BY [Date]) rn2
    FROM mytable
) x
GROUP BY [Mode], (rn1 - rn2)

这会产生:

模式 | min_id |最大 ID |最小日期 |最大日期 :--- | -----: | -----: | :--------------- | :--------------- 输入 | 1 | 6 | 2019-09-20 09:28 | 2019-09-20 19:01 出| 7 | 7 | 2019-09-20 19:01 | 2019-09-20 19:01 输入 | 8 | 9 | 2019-09-20 20:28 | 2019-09-20 20:35 出| 10 | 11 | 2019-09-20 20:50 | 2019-09-20 20:55 输入 | 12 | 12 | 2019-09-20 21:30 | 2019-09-20 21:30

然后,您可以将此查询转换为 cte 并自行加入它以生成预期的结果集:

WITH cte AS (
    SELECT [Mode], MIN(id) min_id, MAX(id) max_id, MIN([Date]) min_date, MAX([Date]) max_date
    FROM (
        SELECT
            id,
            [Date],
            [Mode],
            ROW_NUMBER() OVER(ORDER BY [Date]) rn1,
            ROW_NUMBER() OVER(PARTITION BY [Mode] ORDER BY [Date]) rn2
        FROM mytable
    ) x
    GROUP BY [Mode], (rn1 - rn2)
)
SELECT c1.min_id IdIn, c1.min_date DateIN, c2.max_id IdOut, c2.max_date DateOut
FROM cte c1
INNER JOIN cte c2 
    ON  c1.mode = 'IN'
    AND c2.mode = 'Out'
    AND c2.min_id = c1.max_id + 1

输出:

标识 |日期输入 |出处 |约会 ---: | :--------------- | ----: | :--------------- 1 | 2019-09-20 09:28 | 7 | 2019-09-20 19:01 8 | 2019-09-20 20:28 | 11 | 2019-09-20 20:55

Demo on DB Fiddle

【讨论】:

INSERT INTO mytable(ID,Date,Mode) VALUES (2,'2019-09-20 19:00','IN'); INSERT INTO mytable(ID,Date,Mode) VALUES (3,'2019-09-20 19:00','Out'); INSERT INTO mytable(ID,Date,Mode) VALUES (6,'2019-09-20 19:01','IN'); INSERT INTO mytable(ID,Date,Mode) VALUES (7,'2019-09-20 19:01','Out'); INSERT INTO mytable(ID,Date,Mode) VALUES (8,'2019-09-20 20:28','IN'); INSERT INTO mytable(ID,Date,Mode) VALUES (9,'2019-09-20 20:35','IN'); INSERT INTO mytable(ID,Date,Mode) VALUES (10,'2019-09-20 20:50','Out'); INSERT INTO mytable(ID,Date,Mode) VALUES (11,'2019-09-20 20:55','IN');【参考方案2】:

您可以使用 CTE 尝试以下选项-

您可以查看DEMO HERE

WITH CTE AS(
    SELECT A.id,A.Date,A.Mode
    FROM your_table A
    LEFT JOIN your_table  B ON A.ID = B.ID - 1
    WHERE A.Mode <> B.Mode OR (A.Mode = 'In' AND B.Mode = 'In')
)

SELECT 
(
    SELECT MIN (ID) 
    FROM CTE
    WHERE ID > (SELECT ISNULL(MAX(ID),0) FROM CTE WHERE ID < A.ID AND mode = 'Out') 
    AND ID < A.ID
) [ID],
(
    SELECT MIN (Date) 
    FROM CTE 
    WHERE ID > (SELECT ISNULL(MAX(ID),0) FROM CTE WHERE ID < A.ID AND mode = 'Out') 
    AND ID < A.ID
) [In],
A.ID,A.Date [Out]
FROM CTE A
WHERE A.Mode = 'Out'

【讨论】:

【参考方案3】:

SQL Server 2008 不再是受支持的产品。所有受支持的 SQL Server 版本都支持 lag()lead()

您可以轻松地为此目的使用这些功能。有一个简单的观察:

当上一条记录为 'OUT'(或不存在)时,您需要一条 'IN' 记录。 当下一条记录为'IN'(或不存在)时,您需要'OUT' 记录

过滤到这些记录后,您可以将每个 'OUT' 记录和之前的 'IN' 值作为最终结果:

select prev_id, prev_dt as date_in, id, dt as date_out
from (select io.*,
             lag(dt) over (order by dt) as prev_dt,
             lag(id) over (order by dt) as prev_id
      from (select t.*,
                   lag(mode) over (order by dt) as prev_mode,
                   lead(mode) over (order by dt) as next_mode
            from t
           ) io
      where mode = 'IN' and (prev_mode is null or prev_mode = 'OUT') or
            mode = 'OUT' and (next_mode is null or next_mode = 'IN')
     ) io
where mode = 'OUT';

Here 是一个 dbfiddle。

我提供此功能是因为仅使用不带聚合的 lag()/lead()joins 可能比替代方案具有更好的性能。

【讨论】:

以上是关于将特定行转换为列的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

如何将mysql行转换为列的简单方法

使用带有子查询的最大查询将SQL / PL行转换为列

建议将行转换为列的方法

如何在 SQL 中将行转换为列值 [关闭]

如何展平 SQL 查询的结果 - 将行转换为列?

MySQL - 根据每列的最新日期和 id 列表将行转换为列