MAK
MAK

Reputation: 7260

Find missing contact date details

I have the following table with Contacts details:

Table: tblContacts

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

Sample data:

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');

I want to find those contacts who never called on those dates and 1 or 2 days before and after call.

Note: I may crease before and after calls to any level like 1,2,3,4 so on.

Expected Output: The following output for just 1 day before and after calls.

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

My try: Following query works only for 1 day before and after calls but not for other than 1.

Query:

; 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;  

Upvotes: 0

Views: 83

Answers (2)

LukStorms
LukStorms

Reputation: 29677

You could get the next date in a Series.

Which can be used to find the gaps between the dates.

Then glue those gaps to the result.

For Sql Server 2008 the data is first loaded into a temporary table.

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;

Test on rextester here

In Sql Server 2012+ the window function LEAD can be used instead.

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;

A test on rextester here

Upvotes: 1

Gordon Linoff
Gordon Linoff

Reputation: 1270593

You can generate numbers and then add in rows. Something like this:

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)
                 );

Note: I added a new column to determine if the row is valid. It doesn't make sense to repeat NULL values in the first column, because you don't know what contact number the row applies to.

Upvotes: 1

Related Questions