2017-01-24 2 views
1

Microsoft SQL Server에서 위쪽 방향으로 NULL을 채우는 방법을 파악하려고합니다. 이것은 오라클에서 매우 간단하지만 사장님은 그것을 위해 가지 않았습니다. google 검색의 6 시간 후에 나는 마침내 게시하기로 결정했다. 결과 집합에서 Microsoft SQL Server에는 현재 수행중인 것보다 양방향으로 null 값을 채우는 더 좋은 방법이 있습니까?

....

VALUE 가능한 모든 날짜의 시간과 우리의 시간 스탬프 데이터입니다.

VALUE2는 Google의 내림 값 하향 조정 방법입니다.

VALUE3은 Google의 상한값 입력 방법입니다.

이 가짜 시시한 표를 감안할 때.

-- Test Data & Table 
DECLARE @TEST001 TABLE 
(TIME_STAMP datetime, 
    TagA integer, 
    TagB integer, 
    TagC integer) 
-- Insert test Values  
INSERT INTO @TEST001 
VALUES 
    ('2017-01-21 00:01:00.042', NULL, NULL, 87), 
    ('2017-01-21 00:04:10.155', NULL, 1239, NULL), 
    ('2017-01-21 00:04:10.959', NULL, NULL, 86), 
    ('2017-01-21 00:06:49.401', NULL, 1240, NULL), 
    ('2017-01-21 00:06:59.301', NULL, 1239, NULL), 
    ('2017-01-21 00:07:10.124', 108, NULL, NULL), 
    ('2017-01-21 00:12:11.789', 109, NULL, NULL), 
    ('2017-01-21 00:16:12.190', 108, NULL, NULL), 
    ('2017-01-21 00:16:13.987', 107, NULL, NULL), 
    ('2017-01-21 00:17:31.410', NULL, 1260, NULL), 
    ('2017-01-21 00:17:32.511', NULL, 1261, 87), 
    ('2017-01-21 00:17:32.966', NULL, 1262, NULL) 

우리의 가짜 시시한 테이블에서 TagA를 쿼리하는이 쿼리를 감안할 때. 오늘 매우 큰 데이터 세트와 함께 놀았 이것이 가장 빠른 일이 될 것으로

:

-- Start of Query used in VBS ADODB CONN. 
declare @s datetime 
declare @e datetime 
set @s = '2017-01-21 00:00:00' 
set @e = '2017-01-21 23:59:59' 
; 
-- We need to get all intervals between our two dates. 
WITH ALL_INTERVALS AS (
    SELECT TOP (datediff(mi,@s,@e)) 
    TIMES = dateadd(mi,CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])),@s) 
     FROM sys.all_objects AS s1 
     CROSS JOIN 
     sys.all_objects AS s2 
), 
-- We need to include both our real data and all possible intervals. 
ALL_TIMES AS (
SELECT 
    Time_Stamp as TIMES, 
    TagA AS VALUE 
FROM @TEST001 H 
WHERE Time_Stamp BETWEEN @s and @e 
UNION ALL 
SELECT 
    TIMES AS TIMES, 
    NULL AS VALUE 
    FROM ALL_INTERVALS 
), 
-- We need to find the real first value and fill all nulls with this value until we hit a new value. 
FILL_DOWN AS (SELECT 
      TIMES, 
      VALUE, 
      ISNULL(VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES < AT.TIMES AND VALUE IS NOT NULL ORDER BY TIMES DESC)) AS VALUE2 
      FROM ALL_TIMES AT 
), 
-- Our fill up method does not work if our first set of values is null. UGH, this does not work either....crap. It fills our first set 
-- With our last real value.... 
FILL_UP AS ( 
     SELECT 
      TIMES, 
      VALUE, 
      VALUE2, 
      ISNULL(VALUE2, (SELECT TOP 1 VALUE2 FROM FILL_DOWN WHERE TIMES > FD.TIMES AND VALUE2 IS NOT NULL ORDER BY TIMES DESC)) AS VALUE3 
      FROM FILL_DOWN FD 
) 
SELECT * 
FROM FILL_UP 
ORDER BY TIMES ASC 

TIMES     VALUE VALUE2 VALUE3 
2017-01-2100:01:00.000 NULL NULL 107 <---------- This should be 108 from our fake crappy table. 
2017-01-2100:01:00.043 NULL NULL 107 
2017-01-2100:02:00.000 NULL NULL 107 
2017-01-2100:03:00.000 NULL NULL 107 
2017-01-2100:04:00.000 NULL NULL 107 
2017-01-2100:04:10.157 NULL NULL 107 
2017-01-2100:04:10.960 NULL NULL 107 
2017-01-2100:05:00.000 NULL NULL 107 
2017-01-2100:06:00.000 NULL NULL 107 
2017-01-2100:06:49.400 NULL NULL 107 
2017-01-2100:06:59.300 NULL NULL 107 
2017-01-2100:07:00.000 NULL NULL 107 
2017-01-2100:07:10.123 108  108  108 
2017-01-2100:08:00.000 NULL 108  108 
2017-01-2100:09:00.000 NULL 108  108 
2017-01-2100:10:00.000 NULL 108  108 
2017-01-2100:11:00.000 NULL 108  108 
2017-01-2100:12:00.000 NULL 108  108 
2017-01-2100:12:11.790 109  109  109 
2017-01-2100:13:00.000 NULL 109  109 
2017-01-2100:14:00.000 NULL 109  109 
2017-01-2100:15:00.000 NULL 109  109 
2017-01-2100:16:00.000 NULL 109  109 
2017-01-2100:16:12.190 108  108  108 
2017-01-2100:16:13.987 107  107  107 
2017-01-2100:17:00.000 NULL 107  107 
2017-01-2100:17:31.410 NULL 107  107 
2017-01-2100:17:32.510 NULL 107  107 
2017-01-2100:17:32.967 NULL 107  107 
2017-01-2100:18:00.000 NULL 107  107 
2017-01-2100:19:00.000 NULL 107  107 
2017-01-2100:20:00.000 NULL 107  107 
2017-01-2100:21:00.000 NULL 107  107 
2017-01-2100:22:00.000 NULL 107  107 
2017-01-2100:23:00.000 NULL 107  107 
2017-01-2100:24:00.000 NULL 107  107 
2017-01-2100:25:00.000 NULL 107  107 
2017-01-2100:26:00.000 NULL 107  107 

편집 결과. 정확히 내가 원하는 순간을 정확하게 수행합니다. 이 작은 테이블에서는 속도 차이를 볼 수 없지만 큰 데이터 세트로 1 초 및 24 분이 많이 걸립니다! 도와 주신 모든 분들께 진심으로 감사드립니다.

-- Test Data & Table 
DECLARE @TEST001 TABLE 
(TIME_STAMP datetime, 
    TagA integer, 
    TagB integer, 
    TagC integer) 
-- Insert test Values  
INSERT INTO @TEST001 
VALUES 
    ('2017-01-21 00:01:00.042', NULL, NULL, 87), 
    ('2017-01-21 00:04:10.155', NULL, 1239, NULL), 
    ('2017-01-21 00:04:10.959', NULL, NULL, 86), 
    ('2017-01-21 00:06:49.401', NULL, 1240, NULL), 
    ('2017-01-21 00:06:59.301', NULL, 1239, NULL), 
    ('2017-01-21 00:07:10.124', 108, NULL, NULL), 
    ('2017-01-21 00:12:11.789', 109, NULL, NULL), 
    ('2017-01-21 00:16:12.190', 108, NULL, NULL), 
    ('2017-01-21 00:16:13.987', 107, NULL, NULL), 
    ('2017-01-21 00:17:31.410', NULL, 1260, NULL), 
    ('2017-01-21 00:17:32.511', NULL, 1261, 87), 
    ('2017-01-21 00:17:32.966', NULL, 1262, NULL) 
-- Start of Query used in VBS ADODB CONNN 
declare @s datetime 
declare @e datetime 
set @s = '2017-01-01 00:00:00' 
set @e = '2017-01-31 23:59:59' 
; 
WITH ALL_INTERVALS 
AS (SELECT TOP (datediff(mi,@s,@e)) 
    TIMES = dateadd(mi,CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])),@s), 
    NULL AS VALUE 
     FROM sys.all_objects AS s1 
     CROSS JOIN 
     sys.all_objects AS s2 
), 
ALL_TIMES 
AS (SELECT 
    H.TIME_STAMP as TIMES, 
    TagA AS VALUE 
    FROM @TEST001 H 
    WHERE TIME_STAMP BETWEEN @s and @e 
    UNION ALL 
    SELECT 
     AI.TIMES AS TIMES, 
     AI.VALUE AS VALUE 
     FROM ALL_INTERVALS AI 
), 
-- JUST INCASE OUR REAL TIME == INTERVAL TIME exactly. 
FILL_ACCROSS AS(SELECT TIMES AS TIMES, 
       ISNULL(AT.VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES = AT.TIMES AND VALUE IS NOT NULL ORDER BY TIMES ASC)) AS VALUE 
       FROM ALL_TIMES AT 

), 
-- FILL UP AND FILL DOWN. 
FILL_UP_DOWN AS (SELECT 
       TIMES, 
       VALUE, 
       ISNULL(FA.VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES > FA.TIMES AND VALUE IS NOT NULL ORDER BY TIMES ASC)) AS VALUE2, 
       ISNULL(FA.VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES < FA.TIMES AND VALUE IS NOT NULL ORDER BY TIMES DESC)) AS VALUE3 
       FROM FILL_ACCROSS FA 
) 
--Just a nice display of what is going on. 
select FD.TIMES, 
     FD.VALUE, 
     FD.VALUE2, 
     FD.VALUE3, 
     CASE 
      WHEN FD.VALUE3 IS NULL THEN (FD.VALUE2) 
      ELSE (FD.VALUE3) 
      END AS VALUE4 
    FROM FILL_UP_DOWN FD order by TIMES ASC 
+3

그룹화이 시도. 샘플 데이터를 줄이고 원하는 결과를 추가 할 수 있습니까? –

+0

상관 관계 서브 쿼리에서'DESC'에서'ASC'까지'VALUE3'을 얻기 위해 순서를 바꾸면 원하는 결과를 얻을 수 있습니다. 현재는 주어진 날짜 이후 마지막 Null이 아닌 값을 찾고있는 반면, 처음에는 Null이 아닌 값을 원합니다. – GarethD

+0

DESC를 FILL_DOWN 순으로 놓으십시오. – Serg

답변

1

당신이 대안을 찾고 있다면, 난 정말 당신이 원하는 무엇을 이해하지

-- Start of Query used in VBS ADODB CONN. 
declare @s datetime 
declare @e datetime 
set @s = '2017-01-21 00:00:00.001' 
set @e = '2017-01-21 23:59:59' 
; 
--select datepart(millisecond,@s); 
-- We need to get all intervals between our two dates. 
WITH ALL_INTERVALS AS (
    SELECT TOP (datediff(mi,@s,@e)) 
    TIMES = dateadd(mi,CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])),@s) 
     FROM sys.all_objects AS s1 
     CROSS JOIN 
     sys.all_objects AS s2 
), 
-- We need to include both our real data and all possible intervals. 
ALL_TIMES AS (
SELECT 
    Time_Stamp as TIMES, 
    TagA AS VALUE 
FROM @TEST001 H 
WHERE Time_Stamp BETWEEN @s and @e 
UNION ALL 
SELECT 
    TIMES AS TIMES, 
    NULL AS VALUE 
    FROM ALL_INTERVALS 
), 
g1 AS ( 
    SELECT 
     TIMES, 
     VALUE, 
     diff = case 
       when value=lag(value,1) over(order by TIMES) 
        or coalesce (value, lag(value,1) over(order by TIMES)) is null 
       then 0 else 1 end 
    FROM ALL_TIMES AT 
), 
g2 as ( 
    select TIMES, VALUE 
     , grp = sum(diff) over(order by TIMES) 
     , direction = case sum(diff) over(order by TIMES) when 0 then 1 else -1 end 
    from g1 
) 
select 
    TIMES, 
    VALUE, 
    grp, 
    ISNULL(VALUE, 
      (SELECT TOP 1 VALUE FROM g2 repl WHERE grp = g2.grp + g2.direction AND VALUE IS NOT NULL ORDER BY TIMES) 
      ) AS VALUE3 
from g2; 
+0

신난다, 나는 다른 접근법을 좋아한다, 게시 주셔서 감사합니다! 큰 데이터 세트를 사용할 때 속도가 원래 쿼리와 동일하다는 것을 알았습니다. 그러나 하루 종일 놀고 난 후에 나는 위의 편집을 생각해 냈습니다. 대규모 데이터 세트를 완성하는 데는 거의 1 초가 걸립니다. 건배! 주문을 취소하거나 변경해야 할 필요성을 지적 해 주셔서 다시 한 번 감사드립니다. 나는 그날 그렇게 잘하지 못했고 그것을 파악할 수 없었습니다. –