【问题标题】:Find missing contact date details查找缺少的联系日期详细信息
【发布时间】:2019-12-06 11:13:22
【问题描述】:

我有一张包含联系人详细信息的表格:

表:tblContacts

CREATE TABLE tblContacts
(
    Series INT,
    ContacNumber INT,
    CDate DATETIME
);

样本数据:

INSERT INTO tblContacts VALUES(12,123456,'2019-01-01');
INSERT INTO tblContacts VALUES(3,3456,'2019-01-01');
INSERT INTO tblContacts VALUES(12,123560,'2019-01-02');
INSERT INTO tblContacts VALUES(12,123459,'2019-01-05');
INSERT INTO tblContacts VALUES(3,3446,'2019-01-02');
INSERT INTO tblContacts VALUES(3,3486,'2019-01-03');
INSERT INTO tblContacts VALUES(3,34861,'2019-01-05');
INSERT INTO tblContacts VALUES(3,34862,'2019-01-07');
INSERT INTO tblContacts VALUES(12,127456,'2019-01-21');
INSERT INTO tblContacts VALUES(12,129456,'2019-02-03');
INSERT INTO tblContacts VALUES(12,126456,'2019-02-06');
INSERT INTO tblContacts VALUES(94,941256,'2019-01-01');
INSERT INTO tblContacts VALUES(94,944356,'2019-01-03');
INSERT INTO tblContacts VALUES(94,941356,'2019-01-07');
INSERT INTO tblContacts VALUES(94,943356,'2019-01-09');

我想找到那些在这些日期和通话前后 1 天或 2 天从未打电话的联系人。

注意:我可能会在通话前后折痕到任何级别,例如 1、2、3、4 等等。

预期输出:通话前后仅 1 天的以下输出。

ContacNumber    CDate
----------------------------------------
3486            2019-01-03
NULL            2019-01-04
34861           2019-01-05
NULL            2019-01-06
34862           2019-01-07
123560          2019-01-02
NULL            2019-01-03 - 2019-01-04
123459          2019-01-05
NULL            2019-01-06 - 2019-01-20
127456          2019-01-21
NULL            2019-01-22 - 2019-02-02
129456          2019-02-03
NULL            2019-02-04 - 2019-02-05
126456          2019-02-06
941256          2019-01-01
NULL            2019-01-02
944356          2019-01-03
NULL            2019-01-04 - 2019-01-06
941356          2019-01-07
NULL            2019-01-08
943356          2019-01-09

我的尝试:以下查询仅适用于通话前后 1 天,但不适用于 1 天以外的时间。

查询:

; WITH Stage_1_CTE AS
(
    SELECT  Series,
            ContacNumber,
            CAST(CDate AS DATE) CDate,
            ROW_NUMBER() OVER (PARTITION BY Series ORDER BY CAST(CDate AS DATE)) rnk1,
            (ROW_NUMBER() OVER (PARTITION BY Series ORDER BY CAST(CDate AS DATE)))/2 rnk2,
            (ROW_NUMBER() OVER (PARTITION BY Series ORDER BY CAST(CDate AS DATE)) + 1)/2 rnk3
    FROM tblContacts
    GROUP BY ContacNumber,CDate,Series
)
,
Stage_2_CTE AS
(
    SELECT *,
           CASE WHEN rnk1%2=1 THEN MAX(CASE WHEN rnk1%2=0 THEN CDate END) OVER (PARTITION BY Series,rnk2) 
                ELSE MAX(CASE WHEN rnk1%2=1 THEN CDate END) OVER (PARTITION BY Series,rnk3)
           END AS CDate_Prev,
           CASE WHEN rnk1%2=1 THEN MAX(CASE WHEN rnk1%2=0 THEN CDate END) OVER (PARTITION BY Series,rnk3) 
                ELSE MAX(CASE WHEN rnk1%2=1 THEN CDate END) OVER (PARTITION BY Series,rnk2)
           END AS CDate_Next
    FROM Stage_1_CTE
) 
,Stage_Final_CTE AS
(
    SELECT c.Series,
           c.ContacNumber, 
           c.CDate, 
           EndDate = ''
    FROM Stage_2_CTE c
    WHERE c.CDate <> DATEADD(DAY, +1, CDate_Prev) OR c.CDate <> DATEADD(DAY, -1, CDate_Next)
    UNION ALL
    SELECT Series,
           ContacNumber = NULL,
           CDate = DATEADD(DAY, 1, c.CDate),
           EndDate = ' - '+CAST(DATEADD(DAY, -1, CDate_Next) AS VARCHAR(10))
    FROM Stage_2_CTE c
    WHERE c.CDate <> DATEADD(DAY, -1, CDate_Next)
)
SELECT  ContacNumber, 
        CASE WHEN CAST(CDate AS VARCHAR(10)) =  REPLACE(EndDate,' - ','')  
        THEN CAST(CDate AS VARCHAR(10))
        ELSE
            CAST(CDate AS VARCHAR(10)) + CAST(EndDate AS VARCHAR(13))
        END CDate
FROM Stage_Final_CTE
GROUP BY ContacNumber,CDate,EndDate,Series
ORDER BY Series,CDate;  

【问题讨论】:

  • 您可以尝试使用Minimal Reproducable Example 编辑问题吗?我正在努力理解这个问题。您是在询问如何在上面提供的数据中收集指定日期前后 2 天的数据?
  • “我想找到那些在这些日期和通话前后 1 天或 2 天从未打电话的联系人。”在什么日期?如果联系人在表中,那么大概他们在那个日期打电话。
  • @GordonLinoff,我的意思是说表格中缺少的那些日期,例如,我需要为每个系列生成开始日期和结束日期之间的所有日期,然后找出缺少的日期,如预期结果。

标签: sql sql-server sql-server-2008-r2


【解决方案1】:

您可以生成数字,然后在行中添加。像这样的:

select contactnumber, cdate, 1 as isvalid
from tblcontacts
union all
select c.contactnumber, dateadd(day, v.n, c.contacctdate), 0
from tblcontacts c cross join
     (values (-1), (1)) v(n)
where not exists (select 1
                  from tblcontacts c2
                  where c2.contactnumber = c.contactnumber and
                        c2.date = dateadd(day, v.n, c.contacctdate)
                 );

注意:我添加了一个新列来确定该行是否有效。在第一列中重复 NULL 值是没有意义的,因为您不知道该行适用于哪个联系人号码。

【讨论】:

    【解决方案2】:

    您可以在Series 中获得下一个日期。

    这可用于查找日期之间的间隔。

    然后将这些差距粘贴到结果中。

    对于 Sql Server 2008,数据首先加载到临时表中。

    IF OBJECT_ID('tempdb..#tmpContacts', 'U') IS NOT NULL
        DROP TABLE #tmpContacts; 
    
    CREATE TABLE #tmpContacts
    (
     Series INT NOT NULL,
     Rn INT NOT NULL,
     ContacNumber INT NOT NULL,
     CDate DATE NOT NULL,
     PRIMARY KEY (Series, Rn)
    ); 
    
    INSERT INTO #tmpContacts (Series, ContacNumber, CDate, Rn)
    SELECT Series, ContacNumber
    , CAST(CDate AS DATE)
    , ROW_NUMBER() OVER (PARTITION BY Series ORDER BY CAST(CDate AS DATE)) Rn
    FROM tblContacts
    WHERE CDate >=  CAST('2019-01-01' AS DATE)
        AND CAST(CDate AS DATE) <=  EOMONTH(EOMONTH(CAST('2019-01-01' AS DATE)))
      GROUP BY Series, ContacNumber, CAST(CDate AS DATE);
    
    WITH CTE_CONTACTS AS
    (
      SELECT Series, ContacNumber
      , CDate
      , CDate AS nextCDate
      , CAST(0 AS BIT) AS IsGap
      FROM #tmpContacts
    
      UNION ALL
    
      SELECT t1.Series, null
      , DATEADD(day,1,t1.CDate)
      , DATEADD(day,-1,t2.CDate)
      , 1
      FROM #tmpContacts t1
      LEFT JOIN #tmpContacts t2
        ON t2.Series = t1.Series
       AND t2.Rn = t1.Rn + 1
       AND t1.CDate < DATEADD(day,-1,t2.CDate)
    )
    SELECT c.Series, ContacNumber
    , CONCAT(CONVERT(varchar,c.CDate,23), 
            CASE 
            WHEN c.IsGap=1
             AND DATEDIFF(day,c.CDate,c.nextCDate) > 0 
            THEN ' - ' + CONVERT(varchar,c.nextCDate,23) 
            END) AS Cdates
    FROM CTE_CONTACTS c
    ORDER BY c.Series, c.CDate;
    

    在 rextester 上测试 here

    在 Sql Server 2012+ 中,可以使用窗口函数 LEAD 代替。

    WITH CTE_CONTACTS AS
    (
      SELECT Series, ContacNumber
      , CAST(CDate AS DATE) AS CDate
      , LEAD(CAST(CDate AS DATE)) OVER (PARTITION BY Series ORDER BY CAST(CDate AS DATE)) AS nextCDate
      FROM tblContacts
      WHERE CDate >=  CAST('2019-01-01' AS DATE)
        AND CAST(CDate AS DATE) <=  EOMONTH(EOMONTH(CAST('2019-01-01' AS DATE)))
      GROUP BY Series, ContacNumber, CAST(CDate AS DATE)
    )
    , CTE_CONTACTS2 AS
    (
      SELECT Series, ContacNumber
      , CDate
      , nextCDate
      , CAST(0 AS BIT) AS IsGap
      FROM CTE_CONTACTS
    
      UNION ALL
    
      SELECT Series, null
       , DATEADD(day,1,CDate)
       , DATEADD(day,-1,nextCDate)
       , 1
      FROM CTE_CONTACTS c
      WHERE CDate < DATEADD(day,-1,nextCDate)
    )
    SELECT c.Series, ContacNumber
    , CONCAT(CONVERT(varchar,c.CDate,23), 
            CASE 
            WHEN c.IsGap=1 
             AND DATEDIFF(day,c.CDate,c.nextCDate) > 0 
            THEN ' - ' + CONVERT(varchar,c.nextCDate,23) 
            END) AS Cdates
    FROM CTE_CONTACTS2 c
    ORDER BY c.Series, c.CDate;
    

    对 rextester 的测试here

    【讨论】:

    • SQL server 2008 r2 中是否有“LEAD”功能?
    • @MAK 更新了一个我认为可以在 mssql 2008 r2 中工作的解决方案。但是没有 2008 版本来测试它。所以试试看吧。
    • 我对你的查询做了一些修改,看起来差不多完成了,只想在NULLcontactNumber.rextester.com/NDMTZ31670之前/之后得到1/2/3...n个数据跨度>
    • 用户输入n个数字来获取前后记录,我们可以输入变量名@BeforeAfterValue得到相应的结果。现在我正在获取 null 之前和之后的所有值。
    猜你喜欢
    • 1970-01-01
    • 2012-05-13
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2012-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多