【问题标题】:SELECT MIN Date(using WINDOW function) and MAX Date(with related field using JOIN) togetherSELECT MIN Date(使用WINDOW函数)和MAX Date(使用JOIN相关字段)一起
【发布时间】:2017-05-19 15:35:29
【问题描述】:

表 T1

+----+------------+------------+--------+------+
| ID |   Sdate    |   Edate    | Reason | Type |
+----+------------+------------+--------+------+
|  1 | 5/30/2016  |            |        | A    |
|  1 | 1/19/2016  | 12/15/2016 | USD    | B    |
|  1 | 11/20/2016 | 10/1/2016  | IT     | B    |
|  2 | 10/25/2016 |            |        | A    |
|  2 | 9/22/2016  | 7/11/2016  | SD     | B    |
|  2 | 2/13/2016  |            |        | A    |
|  2 | 1/1/2016   | 4/3/2016   | IT     | B    |
+----+------------+------------+--------+------+

对于每个 ID,我需要为 Type=A 选择最小 Sdate,为 Type=B 选择最大 Edate,以及与 Type=B 关联的出院原因。不仅有两种类型,但我以 A 和 B 为例

所需的输出:

+-------------+-----------+------------+------------------+
| Customer ID | Startdate |  Enddate   | Discharge Reason |
+-------------+-----------+------------+------------------+
|           1 | 5/30/2016 | 12/15/2016 | USD              |
|           2 | 2/13/2016 | 7/11/2016  | SD               |
+-------------+-----------+------------+------------------+

尝试:

WITH CTE AS
(
SELECT
    ID
    ,SDate
    ,EDate
    ,Reason
    ,Type
FROM T1
)

SELECT
     CTE.ID AS [Customer ID]
    ,MIN(CASE WHEN CTE.Type='A' THEN CTE.SDate END)
        OVER (PARTITION BY CTE.CID) AS StartDate
    ,CTE.EDate AS Enddate
    ,CTE.Reason AS [Discharge Reason]

FROM CTE
INNER JOIN (SELECT CTE.ID, MAX(CTE.EDate) AS EEDate 
FROM CTE WHERE CTE.Type='B' GROUP BY CTE.ID) CTE2
    ON CTE.ID=CTE2.ID AND CTE.Edate=CTE2.EEDAte

这给了我一个空白的 Startdate 列,因为外部相关子查询上的 INNER JOIN 用于获取 Max Edate 和关联的 Discharge Reason,导致查询忽略 Type=A,因此没有 Startdates。我如何使这些协同工作?

【问题讨论】:

  • 请编辑您的问题以在 弗拉迪默的回答。因为在您的问题中,您特别提到 Type=[A, B] 好像这些是唯一的选择。

标签: sql sql-server sql-server-2012 greatest-n-per-group window-functions


【解决方案1】:

你可以试试这个:

SELECT
    t.ID
    ,t.SDate
    ,t.EDate
    ,Reason
FROM (
SELECT  ID, MIN(CASE WHEN Type='A' THEN SDate END) as SDate , max(CASE WHEN Type='B' THEN EDate END ) as EDate
FROM T1
group by ID) t 
INNER JOIN T1 on t.ID = T1.id and t.EDate = T1.Edate and type = 'B'

WITH CTE AS
(
SELECT  ID, MIN(CASE WHEN Type='A' THEN SDate END) as SDate , max(CASE WHEN Type='B' THEN EDate END ) as EDate
FROM T1
group by ID
)
SELECT
     CTE.ID AS [Customer ID]
    ,CTE.SDate AS StartDate
    ,CTE.EDate AS Enddate
    ,T1.Reason AS [Discharge Reason]

FROM CTE
INNER JOIN T1 
    ON CTE.ID=T1.ID AND CTE.Edate=T1.EDAte and T1.type = 'B'

如果没有重复的最大日期类型 B。如果有,您需要稍微调整一下。

【讨论】:

    【解决方案2】:

    这是一个经典的top-n-per-group 问题,你需要做两次。

    样本数据

    DECLARE @T TABLE (ID int, Sdate date, Edate date, Reason varchar(50), Type char(1));
    INSERT INTO @T (ID, Sdate, Edate, Reason, Type) VALUES
    (1, '2016-05-30', NULL         , NULL , 'A'),
    (1, '2016-01-19', '2016-12-15' , 'USD', 'B'),
    (1, '2016-11-20', '2016-10-01' , 'IT' , 'B'),
    (2, '2016-10-25', NULL         , NULL , 'A'),
    (2, '2016-09-22', '2016-07-11' , 'SD' , 'B'),
    (2, '2016-02-13', NULL         , NULL , 'A'),
    (2, '2016-01-01', '2016-04-03' , 'IT' , 'B');
    

    对行进行两次编号。

    PARTITION BY ID ORDER BY Type, Sdate 将为每个ID 和最小的SdateType=A 提供行号1。

    PARTITION BY ID ORDER BY Type DESC, Edate DESC 将为每个ID 和最大的EdateType=B 提供第1 行。

    SELECT
        ID
        ,Sdate
        ,Edate
        ,Reason
        ,Type
        ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type, Sdate) AS rnA
        ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type DESC, Edate DESC) AS rnB
    FROM @T
    ORDER BY ID, Type, Sdate
    ;
    

    中间结果

    +----+------------+------------+--------+------+-----+-----+
    | ID |   Sdate    |   Edate    | Reason | Type | rnA | rnB |
    +----+------------+------------+--------+------+-----+-----+
    |  1 | 2016-05-30 | NULL       | NULL   | A    |   1 |   3 |
    |  1 | 2016-01-19 | 2016-12-15 | USD    | B    |   2 |   1 |
    |  1 | 2016-11-20 | 2016-10-01 | IT     | B    |   3 |   2 |
    |  2 | 2016-02-13 | NULL       | NULL   | A    |   1 |   3 |
    |  2 | 2016-10-25 | NULL       | NULL   | A    |   2 |   4 |
    |  2 | 2016-01-01 | 2016-04-03 | IT     | B    |   3 |   2 |
    |  2 | 2016-09-22 | 2016-07-11 | SD     | B    |   4 |   1 |
    +----+------------+------------+--------+------+-----+-----+
    

    现在我们需要获取带有rnA=1rnB=1 的行,并将它们放在一起,通过ID 连接。

    查询

    WITH
    CTE
    AS
    (
        SELECT
            ID
            ,Sdate
            ,Edate
            ,Reason
            ,Type
            ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type, Sdate) AS rnA
            ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type DESC, Edate DESC) AS rnB
        FROM @T
    )
    ,CTE_A
    AS
    (
        SELECT
            ID
            ,Sdate
        FROM CTE
        WHERE rnA = 1
    )
    ,CTE_B
    AS
    (
        SELECT
            ID
            ,Edate
            ,Reason
        FROM CTE
        WHERE rnB = 1
    )
    SELECT
        CTE_A.ID
        ,CTE_A.Sdate
        ,CTE_B.Edate
        ,CTE_B.Reason
    FROM
        CTE_A
        INNER JOIN CTE_B ON CTE_B.ID = CTE_A.ID
    ORDER BY ID
    ;
    

    结果

    +----+------------+------------+--------+
    | ID |   Sdate    |   Edate    | Reason |
    +----+------------+------------+--------+
    |  1 | 2016-05-30 | 2016-12-15 | USD    |
    |  2 | 2016-02-13 | 2016-07-11 | SD     |
    +----+------------+------------+--------+
    

    【讨论】:

    • 您认为除了按字母顺序和确定行号之外,还有其他选择 Type=A 或 B 的方法吗?在原始表中,类型更多,更复杂和动态。或者我是否必须不情愿地硬编码过滤器中的类型才能使行号起作用?
    • @AS91,这取决于Type 背后的逻辑有多复杂。最后,您需要能够将A 类型的行与B 分开。上面的查询通过在一个SELECT 中计算两组行号来减少一些角落。如果它更复杂,有两个 SELECTs 和适当的过滤器,只留下 AB 行。最后通过ID 加入结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-05
    • 2017-05-07
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2019-06-25
    • 2020-11-01
    相关资源
    最近更新 更多