【问题标题】: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返回特定行,在列中级联结果 - 如果没有 1 则多个 2,否则如果没有 2 则 3,否则如果没有 3,则多个 4
【发布时间】:2021-03-26 05:00:51
【问题描述】:

我正在使用 MS SQL 处理学生数据,并且需要遵循一些非常具体的规则。

样本表

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);

表格规则

该表显示了学生被安排在 1 到 4 之间的位置。
一次遭遇中可以有多个学生。
一场遭遇战中,位置 1 只能有一名学生。
一场遭遇战中只能有一名学生处于位置 3。
但是,在一次遭遇战中,多名学生可能处于位置 2 和 4。

业务规则

每次遇到的业务规则如下:

  • 如果遇到的学生在位置 1,则返回该遇到的 row(单数位置 1),删除该遇到的任何位置 2-4 行
  • ELSE if no position 1 THEN return the meet's rows for students (can be multiple) in position 2, remove any position 3 or 4 for that meet
  • ELSE 如果没有位置 1-2 THEN 为位置 3 的学生返回遭遇的,删除该遭遇的任何位置 4 行
  • ELSE 如果没有位置 1-3 THEN 为位置 4 的学生返回遭遇的

不太好用

studentId 值的串联是可以接受的,但并不理想。我有这个半工作与一系列不稳定的联合和 string_aggs。 positionId=3 的行是有问题的,正如我在代码中所说的那样。

此外,这种联合/不像架构在我的小型开发数据库中有效,但在生产数据库中会出现严重的性能问题:

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%';

我想要返回的东西

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

【问题讨论】:

    标签: sql sql-server subquery greatest-n-per-group sql-server-2017


    【解决方案1】:

    这是一个 top-1-per-group 问题...有关系。

    您可以在子查询中对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)
    

    【讨论】:

    • 感谢您的快速回复!如何比较这些选项和下面@venkataraman R 选项的性能?执行计划似乎表明相关子查询具有较少的处理元素和较少的总体估计行要处理;从性能的角度来看,这是赢家吗?
    • @jtrauma:这取决于您的数据,因此您可能需要评估数据库中的每个解决方案。相关子查询可以利用students(encouterid, positionid) 上的索引。另一个答案扫描表格两次,所以我不希望它比上述解决方案更快(但是 - 再次 - 你需要评估它)。
    【解决方案2】:

    感谢您获取测试数据。下面的查询工作正常。

    ;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

    【讨论】:

    • 如何比较您的解决方案与@GMB 的性能?谢谢!
    猜你喜欢
    • 2013-10-27
    • 1970-01-01
    • 2020-07-06
    • 2013-10-23
    • 2020-09-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2012-11-21
    相关资源
    最近更新 更多