补偿 SQL 查询中的 NULL 值

问题描述 投票:0回答:3

假设我有两个如下所示的数据表:

Dv测试结果

DVTR_DeviceNo DVTR_TestedOnAt DVTR_TesterNo
DV00001 2022-08-11 14:15:16.000 0001
DV00001 2022-08-19 21:08:16.000
DV00001 2022-09-22 08:14:32.000
DV00002 2023-06-03 18:18:03.000
DV00002 2023-08-15 19:01:36.000 0007
DV00003 2022-12-23 08:04:47.000 0014
DV00003 2023-01-03 10:09:51.000 0014
DV00003 2023-01-09 08:01:33.000 0014
DV00004 2023-03-14 11:49:02.000 0298
DV00004 2023-03-15 09:08:13.000 0298
DV00005 2022-04-28 16:23:14.000
DV00005 2022-08-14 08:20:56.000

测试员

T_TesterNo T_TesterName
0001 约翰
0007 斯泰西
0014 詹姆斯
0298 卡洛斯

我想找到最后一次测试每个设备的时间以及谁测试了它,按设备编号排序,没有任何设备编号重复。

我有以下代码:

SELECT DvTestResults.DVTR_DeviceNo, max.LastTimeTested, Tester.T_TesterName 
FROM DvTestResults
INNER JOIN
(
    SELECT DVTR_DeviceNo, MAX(DVTR_TestedOnAt) as LastTimeTested 
    FROM DvTestResults
    GROUP BY DVTR_DeviceNo
) as max
    on max.DVTR_DeviceNo = DvTestResults.DVTR_DeviceNo and max.LastTimeTested = DvTestResults.DVTR_TestedOnAt
INNER JOIN Tester ON Tester.TesterNo = DvTestResults.DVTR_TesterNo
ORDER BY DvTestResults.DVTR_DeviceNo

不幸的是,虽然代码在最后一个测试的 DVTR_TesterNo 不为 NULL 的单元上工作正常,但它没有给出它何时为 的值。如果我想列出最后一次测试设备编号以及谁测试了即使它出现了NULL,那将是一个很好的解决方案。优选地,就像本示例中的 DV00001 一样,测试人员通常与最后一个测试它的人(在本例中为 John)同名——即使他们没有登录进行 DV00001 所做的最后一个测试。所以,对于这个例子,我想要 DV00001 的输出:

DVTR_DeviceNo 最后一次测试 T_TesterName
DV00001 2022-09-22 08:14:32.000 约翰

对于像 DV00005 这样只曾经 匿名测试过的设备,我想要一个输出:

DVTR_DeviceNo 最后一次测试 T_TesterName
DV00005 2022-08-14 08:20:56.000 匿名

有人能帮忙吗?

sql sql-server greatest-n-per-group window-functions gaps-and-islands
3个回答
1
投票

获取每个设备的最新测试是一个典型的每组前 1 问题,我们可以通过

row_number()
和过滤来解决这个问题:

select *
from (
    select r.*,
        row_number() over(partition by DVTR_DeviceNo order by LastTimeTested) rn
    from DvTestResults r
) r
where rn = 1

然后我们会

left join
在测试人员的桌子上尝试并带来测试人员的名字。

优选地,就像本例中的 DV00001 一样,测试人员通常与最后测试它的人(在本例中为 John)同名

检索最新的测试器(因此忽略空值)是 SQL Server 中的一项更复杂的任务。我们可以使用

apply
,或者更多的窗口函数。后者将是:

select r.DVTR_DeviceNo, r.DVTR_TestedOnAt, coalesce(t.T_TestName, 'Anonymous') T_TesterName
from (
    select r.*,
        max(DVTR_TesterNo) over(partition by DVTR_DeviceNo, grp) LastDVTR_TesterNo
    from (
        select r.*,
            row_number()         over(partition by DVTR_DeviceNo order by LastTimeTested) rn,
            count(DVTR_TesterNo) over(partition by DVTR_DeviceNo order by LastTimeTested) grp
        from DvTestResults r
    ) r
) r
left join Tester t on t.T_TesterNo = r.LastDVTR_TesterNo
where r.rn = 1

相关:How to make

LAG()
ignore
NULL
s in SQL Server


1
投票

根据我对问题的新理解,这应该可行:

SELECT DVTR_DeviceNo, MAX(DVTR_TestedOnAt) As DVTR_TestedOnAt
    , coalesce(
        (
         SELECT T_TestName
         FROM (
            SELECT DVTR_TesterNo, DVTR_DeviceNo 
               , row_number() over 
                   (PARTITION BY DVTR_DeviceNo
                    ORDER BY case when DVTR_TesterNo IS NULL THEN 1 ELSE 0 END
                               ,DVTR_TestedOnAt DESC) rn     
            FROM DvTestResults
         ) dtr0
         LEFT JOIN Tester t ON t.T_TesterNo = dtr0.DVTR_TesterNo
         WHERE dtr0.rn = 1 AND dtr0.DVTR_DeviceNo = dtr.DVTR_DeviceNo
        ) 
      , 'Anonymous') T_TestName
FROM DvTestRestuls dtr
GROUP BY DVTR_DeviceNo

0
投票

对于 SQL Server 2022,我将使用

LAST_VALUE(dvtr_testerno IGNORE NULLS) OVER()
DVTestResults
的子查询(我称该子查询为
w_testerno
)中尽可能回填缺失的外键,然后与测试人员一起加入该子查询;由于
t_testername
:
IFNULL()

的结果 NULL 变为“匿名”
WITH
-- your input
dvtestresults(dvtr_deviceno,dvtr_testedonat,dvtr_testerno) AS (
          SELECT 'DV00001',TIMESTAMP '2022-08-11 14:15:16.000',0001
UNION ALL SELECT 'DV00001',TIMESTAMP '2022-08-19 21:08:16.000',NULL
UNION ALL SELECT 'DV00001',TIMESTAMP '2022-09-22 08:14:32.000',NULL
UNION ALL SELECT 'DV00002',TIMESTAMP '2023-06-03 18:18:03.000',NULL
UNION ALL SELECT 'DV00002',TIMESTAMP '2023-08-15 19:01:36.000',0007
UNION ALL SELECT 'DV00003',TIMESTAMP '2022-12-23 08:04:47.000',0014
UNION ALL SELECT 'DV00003',TIMESTAMP '2023-01-03 10:09:51.000',0014
UNION ALL SELECT 'DV00003',TIMESTAMP '2023-01-09 08:01:33.000',0014
UNION ALL SELECT 'DV00004',TIMESTAMP '2023-03-14 11:49:02.000',0298
UNION ALL SELECT 'DV00004',TIMESTAMP '2023-03-15 09:08:13.000',0298
UNION ALL SELECT 'DV00005',TIMESTAMP '2022-04-28 16:23:14.000',NULL
UNION ALL SELECT 'DV00005',TIMESTAMP '2022-08-14 08:20:56.000',NULL
)
,
tester(t_testerno,t_testername) aS (
          SELECT 0001,'John'
UNION ALL SELECT 0007,'Stacy'
UNION ALL SELECT 0014,'James'
UNION ALL SELECT 0298,'Carlos'
)
-- end of your input. Query starts here, replace following comma with "WITH"
,
w_testerno AS (
  SELECT
    dvtr_deviceno
  , dvtr_testedonat
  , LAST_VALUE(dvtr_testerno IGNORE NULLS) OVER(
      PARTITION BY dvtr_deviceno ORDER BY dvtr_testedonat
    ) AS dvtr_testerno
  FROM dvtestresults
)
SELECT 
  dvtr_deviceno
, dvtr_testedonat
, IFNULL(t_testername,'Anonymous') AS t_testermane
FROM      w_testerno
LEFT JOIN tester ON t_testerno = dvtr_testerno
ORDER BY 1,2;
dvtr_deviceno dvtr_testedonat t_testermane
DV00001 2022-08-11 14:15:16 约翰
DV00001 2022-08-19 21:08:16 约翰
DV00001 2022-09-22 08:14:32 约翰
DV00002 2023-06-03 18:18:03 匿名
DV00002 2023-08-15 19:01:36 斯泰西
DV00003 2022-12-23 08:04:47 詹姆斯
DV00003 2023-01-03 10:09 7621 :51 詹姆斯
DV00003 2023-01-09 08:01:33 詹姆斯
DV00004 2023-03-14 11:49:02 卡洛斯
DV00004 2023-03-15 09:08:13 卡洛斯
DV00005 2022-04-28 16:23:14 匿名
DV00005 2022-08-14 08:20:56 匿名
© www.soinside.com 2019 - 2024. All rights reserved.