将特定行转换为列的 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:55Demo 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()
或 join
s 可能比替代方案具有更好的性能。
【讨论】:
以上是关于将特定行转换为列的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章