Return 特定行,列中的级联结果 - 如果没有 1 则多个 2,否则如果没有 2 则 3,否则如果没有 3 则多个 4
Return specific rows, cascading results in column - If no 1 then multiple 2s, else if no 2s then 3, else if no 3, then multiple 4s
我在 MS SQL 中处理学生数据,需要遵循一些非常具体的规则。
样本Table
CREATE TABLE students (
encounterId INT,
studentId INT,
positionId INT
);
INSERT INTO students
VALUES
(100,20,1),
(100,32,2),
(100,14,2),
(101,18,1),
(101,87,2),
(101,78,3),
(102,67,2),
(102,20,2),
(103,33,3),
(103,78,4),
(104,16,1),
(104,18,4),
(105,67,4),
(105,18,4),
(105,20,4);
Table 规则
table 显示学生遇到的情况,其中学生被置于 1 和 4 之间的位置。
一次遭遇可以有多个学生。
一场遭遇战只能有1名学生。
一场遭遇战中3号位只能有一个学生
但是一次遭遇战中2、4号位可以有多个同学。
商业规则
每次相遇业务规则如下:
- 如果遇到的位置 1 有学生,return 那次遇到的 行 (单个位置 1),删除那次遇到的任何位置 2-4 行
- ELSE if no position 1 THEN return 位置 2 的学生(可以是多个)的相遇 行 ,删除该相遇的任何位置 3 或 4
- ELSE if no positions 1-2 THEN return 位置 3 的学生遇到的 行 ,删除该遇到的任何位置 4 行
- ELSE if no positions 1-3 THEN return 遇到的 rows for students in position 4
不太好用
studentId 值的串联是acceptable,但并不理想。我有这个半工作与一系列不稳定的工会和 string_aggs。 positionId=3 的行有问题,正如我在代码中所说的那样。
此外,这种类似于 union/not 的架构适用于我的小型开发数据库,但在生产数据库中会出现严重的性能问题:
WITH tAll
AS ( SELECT
encounterId,
studentId,
positionId
FROM
students)
SELECT
encounterId,
CAST(studentId AS VARCHAR) AS [studentId],
1 AS [ord]
FROM
tAll
WHERE
positionId = 1
UNION
SELECT
encounterId,
CAST(studentId AS VARCHAR),
2 AS [ord]
FROM
(
SELECT
encounterId,
STRING_AGG(studentId, ',') AS [studentId],
STRING_AGG(positionId, ',') AS [positionId]
FROM
tAll
GROUP BY
encounterId
) t2
WHERE
positionId NOT LIKE '%1%'
AND positionId NOT LIKE '%3%'
AND positionId NOT LIKE '%4%'
UNION
SELECT
encounterId,
CAST(studentId AS VARCHAR),
3 AS [ord]
FROM
--tAll WHERE positionId=3
--Limiting to positionId=3 includes results (101,18,1) AND (101,78,3).. I just want (101,18,1)
--Using the below code instead, but this creates other problems
(
SELECT
encounterId,
STRING_AGG(studentId, ',') AS [studentId],
STRING_AGG(positionId, ',') AS [positionId]
FROM
tAll
GROUP BY
encounterId
) t3
WHERE
positionId NOT LIKE '%1%'
AND positionId NOT LIKE '%2%'
AND positionId NOT LIKE '%4%'
--This excludes 103 entirely since it has both positionId values of 3 AND 4... I just want (103,33,3)
UNION
SELECT
encounterId,
CAST(studentId AS VARCHAR),
4 AS [ord]
FROM
(
SELECT
encounterId,
STRING_AGG(studentId, ',') AS [studentId],
STRING_AGG(positionId, ',') AS [positionId]
FROM
tAll
GROUP BY
encounterId
) t4
WHERE
positionId NOT LIKE '%1%'
AND positionId NOT LIKE '%2%'
AND positionId NOT LIKE '%3%';
我想要什么returned
encounterId
studentId
ord
100
20
1
101
18
1
102
67
2
102
20
2
103
33
3
104
16
1
105
67
4
105
18
4
105
20
4
这是每组前 1 名的问题...有平局。
您可以在子查询中使用 window 函数 rank()
对每次遭遇中的学生进行排名,然后在外部查询中过滤每组的顶部记录:
select *
from (
select s.*,
rank() over(partition by encounterid order by positionid) rn
from students s
) s
where rn = 1
order by encounterid
另一个选项使用 with ties
- 但您无法控制结果集中行的顺序:
select top (1) with ties *
from students s
order by rank() over(partition by encounterid order by positionid)
另一种典型的解决方案是使用相关子查询进行过滤:
select *
from students s
where positionid = (select min(s1.positionid) from students s1 where s1.encounterid = s.encounterid)
感谢您获取测试数据。以下查询工作正常。
;with cte_minposition as
(
SELECT encounterId, min(positionid) as min_position FROM students
group by encounterId
)
SELECT * FROM students as s
inner join cte_minposition as m
on s.positionId <= m.min_position and s.encounterId = m.encounterId
encounterId
studentId
positionId
encounterId
min_position
100
20
1
100
1
101
18
1
101
1
102
67
2
102
2
102
20
2
102
2
103
33
3
103
3
104
16
1
104
1
105
67
4
105
4
105
18
4
105
4
105
20
4
105
4
我在 MS SQL 中处理学生数据,需要遵循一些非常具体的规则。
样本Table
CREATE TABLE students (
encounterId INT,
studentId INT,
positionId INT
);
INSERT INTO students
VALUES
(100,20,1),
(100,32,2),
(100,14,2),
(101,18,1),
(101,87,2),
(101,78,3),
(102,67,2),
(102,20,2),
(103,33,3),
(103,78,4),
(104,16,1),
(104,18,4),
(105,67,4),
(105,18,4),
(105,20,4);
Table 规则
table 显示学生遇到的情况,其中学生被置于 1 和 4 之间的位置。
一次遭遇可以有多个学生。
一场遭遇战只能有1名学生。
一场遭遇战中3号位只能有一个学生
但是一次遭遇战中2、4号位可以有多个同学。
商业规则
每次相遇业务规则如下:
- 如果遇到的位置 1 有学生,return 那次遇到的 行 (单个位置 1),删除那次遇到的任何位置 2-4 行
- ELSE if no position 1 THEN return 位置 2 的学生(可以是多个)的相遇 行 ,删除该相遇的任何位置 3 或 4
- ELSE if no positions 1-2 THEN return 位置 3 的学生遇到的 行 ,删除该遇到的任何位置 4 行
- ELSE if no positions 1-3 THEN return 遇到的 rows for students in position 4
不太好用
studentId 值的串联是acceptable,但并不理想。我有这个半工作与一系列不稳定的工会和 string_aggs。 positionId=3 的行有问题,正如我在代码中所说的那样。
此外,这种类似于 union/not 的架构适用于我的小型开发数据库,但在生产数据库中会出现严重的性能问题:
WITH tAll
AS ( SELECT
encounterId,
studentId,
positionId
FROM
students)
SELECT
encounterId,
CAST(studentId AS VARCHAR) AS [studentId],
1 AS [ord]
FROM
tAll
WHERE
positionId = 1
UNION
SELECT
encounterId,
CAST(studentId AS VARCHAR),
2 AS [ord]
FROM
(
SELECT
encounterId,
STRING_AGG(studentId, ',') AS [studentId],
STRING_AGG(positionId, ',') AS [positionId]
FROM
tAll
GROUP BY
encounterId
) t2
WHERE
positionId NOT LIKE '%1%'
AND positionId NOT LIKE '%3%'
AND positionId NOT LIKE '%4%'
UNION
SELECT
encounterId,
CAST(studentId AS VARCHAR),
3 AS [ord]
FROM
--tAll WHERE positionId=3
--Limiting to positionId=3 includes results (101,18,1) AND (101,78,3).. I just want (101,18,1)
--Using the below code instead, but this creates other problems
(
SELECT
encounterId,
STRING_AGG(studentId, ',') AS [studentId],
STRING_AGG(positionId, ',') AS [positionId]
FROM
tAll
GROUP BY
encounterId
) t3
WHERE
positionId NOT LIKE '%1%'
AND positionId NOT LIKE '%2%'
AND positionId NOT LIKE '%4%'
--This excludes 103 entirely since it has both positionId values of 3 AND 4... I just want (103,33,3)
UNION
SELECT
encounterId,
CAST(studentId AS VARCHAR),
4 AS [ord]
FROM
(
SELECT
encounterId,
STRING_AGG(studentId, ',') AS [studentId],
STRING_AGG(positionId, ',') AS [positionId]
FROM
tAll
GROUP BY
encounterId
) t4
WHERE
positionId NOT LIKE '%1%'
AND positionId NOT LIKE '%2%'
AND positionId NOT LIKE '%3%';
我想要什么returned
encounterId | studentId | ord |
---|---|---|
100 | 20 | 1 |
101 | 18 | 1 |
102 | 67 | 2 |
102 | 20 | 2 |
103 | 33 | 3 |
104 | 16 | 1 |
105 | 67 | 4 |
105 | 18 | 4 |
105 | 20 | 4 |
这是每组前 1 名的问题...有平局。
您可以在子查询中使用 window 函数 rank()
对每次遭遇中的学生进行排名,然后在外部查询中过滤每组的顶部记录:
select *
from (
select s.*,
rank() over(partition by encounterid order by positionid) rn
from students s
) s
where rn = 1
order by encounterid
另一个选项使用 with ties
- 但您无法控制结果集中行的顺序:
select top (1) with ties *
from students s
order by rank() over(partition by encounterid order by positionid)
另一种典型的解决方案是使用相关子查询进行过滤:
select *
from students s
where positionid = (select min(s1.positionid) from students s1 where s1.encounterid = s.encounterid)
感谢您获取测试数据。以下查询工作正常。
;with cte_minposition as
(
SELECT encounterId, min(positionid) as min_position FROM students
group by encounterId
)
SELECT * FROM students as s
inner join cte_minposition as m
on s.positionId <= m.min_position and s.encounterId = m.encounterId
encounterId | studentId | positionId | encounterId | min_position |
---|---|---|---|---|
100 | 20 | 1 | 100 | 1 |
101 | 18 | 1 | 101 | 1 |
102 | 67 | 2 | 102 | 2 |
102 | 20 | 2 | 102 | 2 |
103 | 33 | 3 | 103 | 3 |
104 | 16 | 1 | 104 | 1 |
105 | 67 | 4 | 105 | 4 |
105 | 18 | 4 | 105 | 4 |
105 | 20 | 4 | 105 | 4 |