Извлечение строк, в которых определенные столбцы отличаются между строками - PullRequest
4 голосов
/ 19 марта 2019

У меня есть следующий SQL для получения строк с наивысшим (максимальным) и 2-м наивысшими датами. В данных есть несколько случаев, когда единственной разницей в «СТАРОЙ СТРОКЕ» и «НОВОЙ СТРОКЕ» является дата вступления в силу. То, что я хотел бы сделать, это показать только старые и новые строки для EMPLID, где BANK_CD и / или ACCOUNT_NUM отличаются между «старыми» и «новыми» строками.

   WITH CTE AS ( 
 SELECT A.EMPLID 
 , C.VENDOR_ID
 , B.FIRST_NAME 
 , B.LAST_NAME 
 , A.BANK_CD 
 , A.ACCOUNT_NUM 
 , A.ACCOUNT_TYPE 
 , A.PRIORITY
 , A.LAST_UPDATE_DATE 
 , A.EFFDT 
 , MAX(A.LAST_UPDATE_DATE) OVER(PARTITION BY A.EMPLID) AS MAX_UPDATE_DATE 
 , ROW_NUMBER() OVER(PARTITION BY A.EMPLID 
  ORDER BY A.EFFDT DESC 
  , D.EFFDT DESC) AS RN 
  FROM PS_DIRECT_DEPOSIT D INNER JOIN PS_DIR_DEP_DISTRIB A ON A.EMPLID = D.EMPLID 
   AND A.EFFDT = D.EFFDT INNER JOIN PS_EMPLOYEES B ON B.EMPLID = A.EMPLID INNER JOIN PS_GHS_DIR_DEP_VND C ON C.EMPLID = A.EMPLID 
     INNER JOIN PS_DIR_DEP_DISTRIB E ON E.EMPLID = A.EMPLID AND E.EFFDT = A.EFFDT AND E.PRIORITY = A.PRIORITY 
 WHERE B.EMPL_STATUS NOT IN ('T','R','D') 
   AND ((A.DEPOSIT_TYPE = 'P' 
   AND A.AMOUNT_PCT = 100) 
    OR A.PRIORITY = 999 
    OR A.DEPOSIT_TYPE = 'B') 
   AND D.EFF_STATUS = 'A' )

 SELECT CASE WHEN RN = 1 THEN 'NEW ROW' WHEN RN = 2 THEN 'OLD ROW' END AS 
 'ROW_TYPE' , * 
 FROM CTE 
 WHERE RN IN (1, 2) 
 AND MAX_UPDATE_DATE >= GETDATE() - 8

Вот пример вывода, который я получаю сейчас:

ROW_TYPE    EMPLID     VENDOR_ID    FIRST_NAME    LAST_NAME    BANK_CD    ACCOUNT_NUM    ACCOUNT_TYPE    PRIORITY    LAST_UPDATE_DATE    EFFDT       MAX_UPDATE_DATE    RN
NEW ROW     12345      XYZ123       John          Smith        111111122  45678          C               999         03/12/2019          03/12/2019  03/12/2019         1
OLD ROW     12345      XYZ123       John          Smith        111111122  45678          C               999         10/25/2017          10/25/2017  10/25/2017         2
NEW ROW     47831      A86464       Samm          Bulle        754566654  98865          C               999         03/12/2019          06/08/2018  03/12/2019         1
OLD ROW     47831      A86464       Samm          Bulle        754566654  45678          C               999         10/25/2017          06/08/2018  10/25/2017         2
NEW ROW     32456      KG4561       Kilo          Renne        875123311  32146          C               300         09/02/2018          09/02/2018  09/02/2018         1
OLD ROW     32456      KG4561       Kilo          Renne        971215477  78131          C               310         12/21/2017          12/21/2017  12/21/2017         2

В вышеприведенном примере я НЕ хотел бы выводить первые два набора строк EMPLID (верхние 4 строки), поскольку они имеют одинаковые BANK_CD и ACCOUNT_NUM между старой и новой строкой. Последний набор строк, которые я хотел бы сохранить в своем выводе, как вы видите, BANK_CD и ACCOUNT_NUM различны (хотя могут быть и другие).

Я попытался добавить самообъединение для PS_DIR_DEP_DISTRIB, а затем добавить в предложение WHERE, где AND (A.BANK_CD <> E.BANK_CD OR A.ACCOUNT_NUM <> E.ACCOUNT_NUM) следующим образом:

   WITH CTE AS ( 
 SELECT A.EMPLID 
 --, C.VENDOR_ID
 , B.FIRST_NAME 
 , B.LAST_NAME 
 , A.BANK_CD 
 , A.ACCOUNT_NUM 
 , A.ACCOUNT_TYPE 
 , A.PRIORITY
 , A.LAST_UPDATE_DATE 
 , A.EFFDT 
 , MAX(A.LAST_UPDATE_DATE) OVER(PARTITION BY A.EMPLID) AS MAX_UPDATE_DATE 
 , ROW_NUMBER() OVER(PARTITION BY A.EMPLID 
  ORDER BY A.EFFDT DESC 
  , D.EFFDT DESC) AS RN 
  FROM PS_DIRECT_DEPOSIT D INNER JOIN PS_DIR_DEP_DISTRIB A ON A.EMPLID = D.EMPLID 
   AND A.EFFDT = D.EFFDT INNER JOIN PS_EMPLOYEES B ON B.EMPLID = A.EMPLID INNER JOIN PS_GHS_DIR_DEP_VND C ON C.EMPLID = A.EMPLID 
 ---ADDING SELF JOIN BELOW---
     INNER JOIN PS_DIR_DEP_DISTRIB E ON E.EMPLID = A.EMPLID AND E.EFFDT = 
     A.EFFDT AND E.PRIORITY = A.PRIORITY 
 WHERE B.EMPL_STATUS NOT IN ('T','R','D') 
   AND ((A.DEPOSIT_TYPE = 'P' 
   AND A.AMOUNT_PCT = 100) 
    OR A.PRIORITY = 999 
    OR A.DEPOSIT_TYPE = 'B') 
   AND D.EFF_STATUS = 'A' 
---ADDING NEW WHERE CONDITION BELOW---
   AND (E.ACCOUNT_NUM <> A.ACCOUNT_NUM OR E.BANK_CD <> A.BANK_CD )) 
 SELECT CASE WHEN RN = 1 THEN 'NEW ROW' WHEN RN = 2 THEN 'OLD ROW' END AS 'ROW_TYPE' 
 , * 
  FROM CTE 
  WHERE RN IN (1, 2) 
  AND MAX_UPDATE_DATE >= GETDATE() - 8

Когда я делаю вышеуказанные изменения, я не получаю никаких данных вообще. Что я делаю не так?

3/20/19 Редактировать:

Следуя предложенному Тареком ответу ниже, мне кажется, что мне не хватает некоторых строк, которые я ожидаю увидеть в конечном выводе. Похоже, что добавленные новые поля BANK_CD_prev и ACCOUNT_NUM_prev возвращают значения NULL в строках, которые я ожидаю, что они будут отображены, поэтому, почему последний запрос не получает их. Как с этим можно справиться?

ROW_TYPE    EMPLID    FIRST_NAME    LAST_NAME   BANK_CD       ACCOUNT_NUM     ACCOUNT_TYPE  PRIORITY    LAST_UPDATE_DATE       EFFDT        MAX_UPDATE_DATE  RN    BANK_CD_prev   ACCOUNT_NUM_prev  
NEW ROW     56789     Test          User        874556411     54765           C             999         2019-02-28             2019-02-28   2019-02-28       1     NULL           NULL
OLD ROW     56789     Test          User        874556411     98451           C             999         2017-10-09             2017-10-09   2017-10-09       1     874556411      54765
NEW ROW     56789     Sampl         Test        756561623     46331           C             999         2018-03-12             2018-03-12   2018-03-12       1     NULL           NULL
OLD ROW     56789     Test          User        756561623     46331           C             999         2015-05-18             2015-05-18   2015-05-18       1     756561623      46331

Обратите внимание, что в приведенном выше примере выполняется запрос без окончательных условий для (AND BANK_CD <> BANK_CD_prev AND ACCOUNT_NUM <> ACCOUNT_NUM_prev), чтобы я мог понять, почему мне не хватало некоторых строк. В этом случае вы можете увидеть первые EMPLID ' s две строки, где ACCOUNT_NUM отличается, и поэтому я должен выводить обе строки. 2-я EMPLID (две последние строки) немного отличается тем, что BANK_CD и ACCOUNT_NUM одинаковы для обеих строк, поэтому я не ожидал, что это будет вывод (что не так, так что это правильно), но все равно показывает NULL как в первом наборе строк.

3/20/19 Edit 2: Вы можете видеть, что в BANK_CD_COUNT и ACCOUNT_NUM_COUNT есть какие-то странные цифры, которые, как мне кажется, вызывают проблемы.

ROW_TYPE    EMPLID  FIRST_NAME  LAST_NAME   BANK_CD     ACCOUNT_NUM  ACCOUNT_TYPE   PRIORITY    LAST_UPDATE_DATE    EFFDT        MAX_UPDATE_DATE    RN   BANK_CD_COUNT  ACCOUNT_NUM_COUNT
NEW ROW     812682  Test        User        031308302   8675309      C              999         2019-03-09          2019-03-09   2019-03-09          1   1101            1
OLD ROW     812682  Test        User        231379393   0001236408   C           999            2018-04-11          2018-03-03   2019-03-09          2   476            1

Эта ссылка содержит сценарии создания таблиц и вставки для всех, кто может помочь воспроизвести это.

https://pastebin.com/cZLhMmet

Ответы [ 2 ]

3 голосов
/ 22 марта 2019

Один из способов сделать это - использовать функции LEAD и LAG. Нам нужны оба здесь.

Вот ваш оригинальный запрос, который я отформатировал и закомментировал фильтр по дате.

WITH 
CTE
AS 
(
    SELECT
        A.EMPLID
        ,C.VENDOR_ID
        ,B.FIRST_NAME
        ,B.LAST_NAME
        ,A.BANK_CD
        ,A.ACCOUNT_NUM
        ,A.ACCOUNT_TYPE
        ,A.PRIORITY
        ,A.LAST_UPDATE_DATE
        ,A.EFFDT
        ,MAX(A.LAST_UPDATE_DATE) OVER (PARTITION BY A.EMPLID) AS MAX_UPDATE_DATE
        ,ROW_NUMBER() OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC, D.EFFDT DESC) AS RN
    FROM 
        PS_DIRECT_DEPOSIT D
        INNER JOIN PS_DIR_DEP_DISTRIB A 
            ON A.EMPLID = D.EMPLID
            AND A.EFFDT = D.EFFDT
        INNER JOIN PS_EMPLOYEES B ON B.EMPLID = A.EMPLID
        INNER JOIN PS_GHS_DIR_DEP_VND C ON C.EMPLID = A.EMPLID
        INNER JOIN PS_DIR_DEP_DISTRIB E 
            ON E.EMPLID = A.EMPLID
            AND E.EFFDT = A.EFFDT
            AND E.PRIORITY = A.PRIORITY
    WHERE 
        B.EMPL_STATUS NOT IN ('T','R','D')
        AND 
        (
            (
                A.DEPOSIT_TYPE = 'P'
                AND A.AMOUNT_PCT = 100
            )
            OR A.PRIORITY = 999
            OR A.DEPOSIT_TYPE = 'B'
        )
        AND D.EFF_STATUS = 'A'
)
SELECT 
    CASE 
        WHEN RN = 1 THEN 'NEW ROW'
        WHEN RN = 2 THEN 'OLD ROW'
    END AS 'ROW_TYPE'
    ,*
FROM CTE
WHERE 
    RN IN (1,2)
    --AND MAX_UPDATE_DATE >= GETDATE() - 8
;

Этот запрос возвращает следующий результат на основе данных образца:

+----------+---------+-----------+------------+-----------+----------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+
| ROW_TYPE | EMPLID  | VENDOR_ID | FIRST_NAME | LAST_NAME | BANK_CD  | ACCOUNT_NUM | ACCOUNT_TYPE | PRIORITY |    LAST_UPDATE_DATE     |          EFFDT          |     MAX_UPDATE_DATE     | RN |
+----------+---------+-----------+------------+-----------+----------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+
| NEW ROW  |  097432 | 3471B     | H          | Adam      | 09146115 |    13404891 | C            |      999 | 2016-06-10 00:00:00.000 | 2016-06-10 00:00:00.000 | 2016-06-10 00:00:00.000 |  1 |
| OLD ROW  |  097432 | 3471B     | H          | Adam      | 09146115 |    13404891 | C            |      999 | 2016-06-10 00:00:00.000 | 2014-11-05 00:00:00.000 | 2016-06-10 00:00:00.000 |  2 |
| NEW ROW  |  100765 | 1272B     | V          | Milo      |  2358414 |      040925 | S            |      310 | 2014-03-05 00:00:00.000 | 2011-04-27 00:00:00.000 | 2014-03-05 00:00:00.000 |  1 |
| OLD ROW  |  100765 | 1272B     | V          | Milo      |  2358414 |      040925 | S            |      300 | 2014-03-05 00:00:00.000 | 2001-08-23 00:00:00.000 | 2014-03-05 00:00:00.000 |  2 |
| NEW ROW  | 1045632 | 3870A     | V          | Olo       |  2345794 |   179410860 | C            |      999 | 2018-08-16 00:00:00.000 | 2018-08-16 00:00:00.000 | 2018-08-16 00:00:00.000 |  1 |
| OLD ROW  | 1045632 | 3870A     | V          | Olo       |  2345794 |   179410860 | C            |      310 | 2011-02-25 00:00:00.000 | 2011-02-25 00:00:00.000 | 2018-08-16 00:00:00.000 |  2 |
| NEW ROW  |   21345 | 12345A    | J          | Smith     |  0224547 |    59167824 | C            |      999 | 2019-02-28 00:00:00.000 | 2019-02-28 00:00:00.000 | 2019-02-28 00:00:00.000 |  1 |
| OLD ROW  |   21345 | 12345A    | J          | Smith     |  0224547 |      591678 | C            |      999 | 2017-11-08 00:00:00.000 | 2017-10-17 00:00:00.000 | 2019-02-28 00:00:00.000 |  2 |
| NEW ROW  |   26110 | 1272B     | S          | Sams      |  8208302 |      822328 | C            |      999 | 2019-02-08 00:00:00.000 | 2019-02-08 00:00:00.000 | 2019-02-08 00:00:00.000 |  1 |
| OLD ROW  |   26110 | 1272B     | S          | Sams      |  8208302 |      822328 | C            |      315 | 2014-03-05 00:00:00.000 | 2012-07-30 00:00:00.000 | 2019-02-08 00:00:00.000 |  2 |
+----------+---------+-----------+------------+-----------+----------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+

Я включил его здесь, чтобы вы могли увидеть разницу с приведенным ниже.

Вот новый запрос с добавленными функциями LEAD и LAG (нам нужно получить как предыдущую, так и следующую строки) с дополнительным фильтром в предложении WHERE.

LEAD и LAG возвращают NULL, когда достигают конца окна. Например, LAG (prev value) вернул бы NULL для первой строки окна, поскольку «предыдущей» строки еще нет.

Итак, для первой строки (rn = 1) нам нужно сравнить ее значение со «следующим».

Для второй строки (rn = 2) нам нужно сравнить ее значение с «prev».

Окончательный запрос

WITH 
CTE
AS 
(
    SELECT
        A.EMPLID
        ,C.VENDOR_ID
        ,B.FIRST_NAME
        ,B.LAST_NAME
        ,A.BANK_CD
        ,A.ACCOUNT_NUM
        ,A.ACCOUNT_TYPE
        ,A.PRIORITY
        ,A.LAST_UPDATE_DATE
        ,A.EFFDT
        ,MAX(A.LAST_UPDATE_DATE) OVER (PARTITION BY A.EMPLID) AS MAX_UPDATE_DATE
        ,ROW_NUMBER() OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC, D.EFFDT DESC) AS RN

        ,LAG(A.BANK_CD) OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC, D.EFFDT DESC) AS BANK_CD_prev
        ,LEAD(A.BANK_CD) OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC, D.EFFDT DESC) AS BANK_CD_next
        ,LAG(A.ACCOUNT_NUM) OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC, D.EFFDT DESC) AS ACCOUNT_NUM_prev
        ,LEAD(A.ACCOUNT_NUM) OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC, D.EFFDT DESC) AS ACCOUNT_NUM_next
    FROM 
        PS_DIRECT_DEPOSIT D
        INNER JOIN PS_DIR_DEP_DISTRIB A 
            ON A.EMPLID = D.EMPLID
            AND A.EFFDT = D.EFFDT
        INNER JOIN PS_EMPLOYEES B ON B.EMPLID = A.EMPLID
        INNER JOIN PS_GHS_DIR_DEP_VND C ON C.EMPLID = A.EMPLID
        INNER JOIN PS_DIR_DEP_DISTRIB E 
            ON E.EMPLID = A.EMPLID
            AND E.EFFDT = A.EFFDT
            AND E.PRIORITY = A.PRIORITY
    WHERE 
        B.EMPL_STATUS NOT IN ('T','R','D')
        AND 
        (
            (
                A.DEPOSIT_TYPE = 'P'
                AND A.AMOUNT_PCT = 100
            )
            OR A.PRIORITY = 999
            OR A.DEPOSIT_TYPE = 'B'
        )
        AND D.EFF_STATUS = 'A'
)
SELECT 
    CASE 
        WHEN RN = 1 THEN 'NEW ROW'
        WHEN RN = 2 THEN 'OLD ROW'
    END AS 'ROW_TYPE'
    ,*
FROM CTE
WHERE 
    RN IN (1,2)
    AND 
    (
        (
            rn = 1
            AND BANK_CD <> BANK_CD_next
        )
        OR
        (
            rn = 1
            AND ACCOUNT_NUM <> ACCOUNT_NUM_next
        )

        OR

        (
            rn = 2
            AND BANK_CD <> BANK_CD_prev
        )
        OR
        (
            rn = 2
            AND ACCOUNT_NUM <> ACCOUNT_NUM_prev
        )
    )
    --AND MAX_UPDATE_DATE >= GETDATE() - 8
;

Окончательный результат

+----------+--------+-----------+------------+-----------+---------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+--------------+--------------+------------------+------------------+
| ROW_TYPE | EMPLID | VENDOR_ID | FIRST_NAME | LAST_NAME | BANK_CD | ACCOUNT_NUM | ACCOUNT_TYPE | PRIORITY |    LAST_UPDATE_DATE     |          EFFDT          |     MAX_UPDATE_DATE     | RN | BANK_CD_prev | BANK_CD_next | ACCOUNT_NUM_prev | ACCOUNT_NUM_next |
+----------+--------+-----------+------------+-----------+---------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+--------------+--------------+------------------+------------------+
| NEW ROW  |  21345 | 12345A    | J          | Smith     | 0224547 |    59167824 | C            |      999 | 2019-02-28 00:00:00.000 | 2019-02-28 00:00:00.000 | 2019-02-28 00:00:00.000 |  1 | NULL         | 0224547      | NULL             | 591678           |
| OLD ROW  |  21345 | 12345A    | J          | Smith     | 0224547 |      591678 | C            |      999 | 2017-11-08 00:00:00.000 | 2017-10-17 00:00:00.000 | 2019-02-28 00:00:00.000 |  2 | 0224547      | NULL         | 59167824         | NULL             |
+----------+--------+-----------+------------+-----------+---------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+--------------+--------------+------------------+------------------+
0 голосов
/ 20 марта 2019

Я бы просто добавил следующие аналитические функции в ваш CTE, чтобы вернуть предшественника:

LAG(A.BANK_CD) OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC) AS BANK_CD_prev,
LAG(A.ACCOUNT_NUM) OVER (PARTITION BY A.EMPLID ORDER BY A.EFFDT DESC) AS ACCOUNT_NUM_prev

Тогда вам просто нужно сослаться на это в вашем основном запросе, и все готово

FROM CTE 
WHERE RN IN (1, 2) 
AND MAX_UPDATE_DATE >= GETDATE() - 8
AND BANK_CD <> BANK_CD_prev
AND ACCOUNT_NUM <> ACCOUNT_NUM_prev

пожалуйста, ответьте, если это решило вашу проблему. Поскольку вы не предоставили пример для тестирования, сказать сложно.

РЕДАКТИРОВАТЬ: Я имею в виду: использовать это решение с вашим первоначальным подходом вообще без самостоятельного соединения. это не поможет вам в вашей проблеме.

...