检索行之间特定列不同的行
Posted
技术标签:
【中文标题】检索行之间特定列不同的行【英文标题】:Retrieve rows where specific columns are different between rows 【发布时间】:2019-03-19 17:47:29 【问题描述】:我有以下 SQL 来获取最高(最大)和第二高日期行。数据中有一些情况,“旧行”和“新行”的唯一区别是生效日期。我想做的只是显示每个 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 子句 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 编辑:
按照下面 Tarek 的建议答案,我似乎遗漏了一些我希望在最终输出中出现的行。似乎添加的新字段 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
的 ACCOUNT_NUM 不同的两行,因此我应该输出两行。第二个EMPLID
(最后两行)有点不同,因为BANK_CD
和ACCOUNT_NUM
对于两行都是相同的,所以我不希望这会被输出(它不是,所以这是正确的)但仍然显示NULL
's 在第一组行中。
3/20/19 编辑 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
【问题讨论】:
嗯...字段 MAX_UPDATE_DATE 的输出应该重复每个 EMPLID 是此 ID 的最大日期。查看演示:rextester.com/QEJB48817 【参考方案1】:一种方法是使用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 |
+----------+---------+-----------+------------+-----------+----------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+
我把它放在这里,所以你可以看到下面的区别。
这是在WHERE
子句中添加了LEAD
和LAG
函数的新查询(我们需要同时获取上一行和下一行)以及额外的过滤器。
LEAD
和 LAG
在到达窗口末尾时返回 NULL。例如,LAG
(上一个值)将为窗口的第一行返回 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 |
+----------+--------+-----------+------------+-----------+---------+-------------+--------------+----------+-------------------------+-------------------------+-------------------------+----+--------------+--------------+------------------+------------------+
【讨论】:
这似乎工作得很好!一个问题,在最终结果输出中,我注意到“新行”的BANK_CD_next
和ACCOUNT_NUM_next
的值包含ACCOUNT_NUM
的“旧行”值。在“旧行”上反之亦然,ACCOUNT_NUM_prev
的值是“新行”中的ACCOUNT_NUM
。也许只是字段的标签(对我来说)有点令人困惑,我只是想确保它们是正确的?
@Nick,一切都按照RN
排序。您将第一行的 rn=1
标记为“新行”。 rn=1
之后的行是 rn=2
的行。因此,rn=1
的下一行是 rn=2
所在的行。 ACCOUNT_NUM_next
列包含当前行中“下一个”行的值。因此,对于rn=1
,ACCOUNT_NUM_next
列包含来自rn=2
的值。反之亦然,“prev”。当我们将两个不同行的值放入同一行时,我们可以在 WHERE
子句中比较它们。
感谢弗拉基米尔,领先和落后的分区是按降序 (DESC) 顺序排列的,所以我认为这就是造成我困惑的原因。我会将此标记为已接受的答案。
很高兴听到您的问题最终会得到解决,很抱歉因为我参加了会议而没有回复。【参考方案2】:
我只需将以下分析函数添加到您的 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
如果解决了您的问题,请回复。由于您没有提供测试示例,因此很难说。
编辑:我的意思是:将该解决方案与您的初始方法一起使用,而无需自加入。它不会帮助您解决问题。
【讨论】:
感谢 Tarek 的反馈。我添加了您的建议,但是我似乎在主查询中缺少一些带有额外条件的行。我缺少的行(应该返回)在BANK_CD_prev
和 ACCOUNT_NUM_prev
列中返回 NULL 值。我会在上面附上一个例子。
此外,当我使用您的更改运行此程序时,我只会返回旧行,而我想同时查看旧行和新行。以上是关于检索行之间特定列不同的行的主要内容,如果未能解决你的问题,请参考以下文章