如何根据UserAccount ID用以前的值填充SQL Server中的GAP

时间:2018-07-02 04:26:20

标签: sql sql-server sql-server-2012

我有一个表,其中有2个帐户ID,其开始月份和年份与以下数据不同

CREATE TABLE #Temp
(
    AccountId NVARCHAR(100),
    Churn NVARCHAR(100), 
    [Month] INT, 
    [Yr] INT
)

INSERT INTO #Temp 
VALUES ('Tst05716825', 'Active', 9, 2016), ('Tst05716825', 'Active', 12, 2016),
       ('Tst05716825', 'Suspend', 3, 2017), ('Tst05716825', 'Suspend', 8, 2017),
       ('Tst05716825', 'Terminate', 10, 2017), ('TstNew09567', 'Active', 11, 2017),
       ('TstNew09567', 'Suspend', 2, 2018), ('TstNew09567', 'Suspend', 4, 2018),
       ('TstNew09567', 'Terminate', 6, 2018),
         ('TstNw09567', 'Active', 3, 2016),
     ('TstNw09567', 'Terminate', 3, 2018);

SELECT * 
FROM #Temp

输出如下

AccountId   Churn     Month   Yr
-----------------------------------
Tst05716825 Active     9    2016
Tst05716825 Active    12    2016
Tst05716825 Suspend    3    2017
Tst05716825 Suspend    8    2017
Tst05716825 Terminate   10  2017
TstNew09567 Active      11  2017
TstNew09567 Suspend     2   2018
TstNew09567 Suspend     4   2018
TstNew09567 Terminate   6   2018
TstNw09567  Active      3   2016
TstNw09567  Terminate   3   2018

但是我需要为每个用户开始的月份按以前的值填写缺少的年份和月份,年份将从表中选择第一个月的值。需要输出如下:

AccountId   Churn   Month    Yr
Tst05716825 Active    9      2016
Tst05716825 Active    10     2016
Tst05716825 Active    11     2016
Tst05716825 Active    12     2016
Tst05716825 Active    1      2017
Tst05716825 Active    2      2017
Tst05716825 Suspend   3      2017
Tst05716825 Suspend   4      2017
Tst05716825 Suspend   5      2017
Tst05716825 Suspend   6      2017
Tst05716825 Suspend   7      2017
Tst05716825 Suspend   8      2017
Tst05716825 Suspend   9      2017
Tst05716825 Terminate 10     2017
TstNew09567 Active    11     2017
TstNew09567 Active    12     2017
TstNew09567 Active    1      2018
TstNew09567 Suspend   2      2018
TstNew09567 Suspend   3      2018
TstNew09567 Suspend   4      2018
TstNew09567 Suspend   5      2018
TstNew09567 Terminate 6      2018
TstNw09567  Active    3      2016
TstNw09567  Active    4      2016 till Feb 2018 as Active
TstNw09567  Terminate  3     2018

我需要帮助来解决此问题。我不想使用while循环,因为我们的数据量非常大。

2 个答案:

答案 0 :(得分:1)

尝试:我只是使用CURSORMIN MAXAccountId中的每个日期的JOIN日期来生成日期范围内的日期,重要的是TOP 1以获取先前的值详细信息

DECLARE @MinDt DATE, @MaxDt DATE, @AccountId VARCHAR(200)

IF OBJECT_ID('tempdb..#dates') IS NOT NULL
    DROP TABLE #dates

CREATE TABLE #dates(AccountId VARCHAR(200),dates DATE)

DECLARE b_cursor CURSOR FOR
    SELECT DISTINCT AccountId FROM #temp    
OPEN b_cursor
FETCH NEXT FROM b_cursor INTO @AccountId
WHILE @@FETCH_STATUS = 0   
BEGIN
    SELECT 
        @MinDt = MIN(CAST(CONCAT(Yr,'-',Month,'-',01) AS DATE)),
        @MaxDt = MAX(CAST(CONCAT(Yr,'-',Month,'-',01) AS DATE))
    FROM #Temp WHERE AccountId = @AccountId

    ;WITH account_detail(AccountId, account_dates, cnt)AS
    (
        SELECT @AccountId, @MinDt, 0 AS cnt
        UNION ALL 
        SELECT AccountId,DATEADD(MONTH, (cnt+1), @MinDt), cnt + 1 
        FROM account_detail r 
        WHERE DATEADD(MONTH, (cnt+1), @MinDt) <= @MaxDt
    )
    INSERT INTO #dates(AccountId, dates) 
    SELECT AccountId, account_dates FROM account_detail
    OPTION (MAXRECURSION 0)

    FETCH NEXT FROM b_cursor INTO @AccountId
END
CLOSE b_cursor
DEALLOCATE  b_cursor

SELECT 
    ISNULL(te.AccountId,t.AccountId) AS AccountId, 
    ISNULL(te.Churn, t.Churn) AS Churn, 
    MONTH(ur.dates) [Month],  
    YEAR(ur.dates) Yr
FROM #dates ur
LEFT JOIN #temp te ON te.Month = MONTH(ur.dates) AND te.Yr = YEAR(ur.dates) 
AND te.AccountId = ur.AccountId
OUTER APPLY(SELECT TOP 1 * 
            FROM #temp 
            WHERE CAST(CONCAT(Yr,'-',Month,'-',01) AS DATE) <= ur.dates 
                AND AccountId = ur.AccountId
            ORDER BY CAST(CONCAT(Yr,'-',Month,'-',01) AS DATE) DESC) t
ORDER BY ur.AccountId ASC

输出:

    AccountId       Churn       Month   Yr
Tst05716825     Active      9       2016
Tst05716825     Active      10      2016
Tst05716825     Active      11      2016
Tst05716825     Active      12      2016
Tst05716825     Active      1       2017
Tst05716825     Active      2       2017
Tst05716825     Suspend     3       2017
Tst05716825     Suspend     4       2017
Tst05716825     Suspend     5       2017
Tst05716825     Suspend     6       2017
Tst05716825     Suspend     7       2017
Tst05716825     Suspend     8       2017
Tst05716825     Suspend     9       2017
Tst05716825     Terminate   10      2017
TstNew09567     Active      11      2017
TstNew09567     Active      12      2017
TstNew09567     Active      1       2018
TstNew09567     Suspend     2       2018
TstNew09567     Suspend     3       2018
TstNew09567     Suspend     4       2018
TstNew09567     Suspend     5       2018
TstNew09567     Terminate   6       2018
TstNw09567      Active      3       2016
TstNw09567      Active      4       2016
TstNw09567      Active      5       2016
TstNw09567      Active      6       2016
TstNw09567      Active      7       2016
TstNw09567      Active      8       2016
TstNw09567      Active      9       2016
TstNw09567      Active      10      2016
TstNw09567      Active      11      2016
TstNw09567      Active      12      2016
TstNw09567      Active      1       2017
TstNw09567      Active      2       2017
TstNw09567      Active      3       2017
TstNw09567      Active      4       2017
TstNw09567      Active      5       2017
TstNw09567      Active      6       2017
TstNw09567      Active      7       2017
TstNw09567      Active      8       2017
TstNw09567      Active      9       2017
TstNw09567      Active      10      2017
TstNw09567      Active      11      2017
TstNw09567      Active      12      2017
TstNw09567      Active      1       2018
TstNw09567      Active      2       2018
TstNw09567      Terminate   3       2018

答案 1 :(得分:0)

基本上创建一个包含AccountId的日历cte。因此,您可以获得每个帐户的所有缺失月份和年份(也从该帐户的最小日期到最大日期)。

IF OBJECT_ID('tempdb..#temp') IS NOT NULL
    DROP TABLE #temp;

IF OBJECT_ID('tempdb..#calendar') IS NOT NULL
    DROP TABLE #calendar;

CREATE TABLE #Temp
    (
        AccountId NVARCHAR(100) ,
        Churn NVARCHAR(100) ,
        [Month] INT ,
        [Yr] INT
    );

INSERT INTO #Temp
VALUES ( 'Tst05716825', 'Active', 9, 2016 ) ,
       ( 'Tst05716825', 'Active', 12, 2016 ) ,
       ( 'Tst05716825', 'Suspend', 3, 2017 ) ,
       ( 'Tst05716825', 'Suspend', 8, 2017 ) ,
       ( 'Tst05716825', 'Terminate', 10, 2017 ) ,
       ( 'TstNew09567', 'Active', 11, 2017 ) ,
       ( 'TstNew09567', 'Suspend', 2, 2018 ) ,
       ( 'TstNew09567', 'Suspend', 4, 2018 ) ,
       ( 'TstNew09567', 'Terminate', 6, 2018 ) ,
       ( 'TstNw09567', 'Active', 3, 2016 ) ,
       ( 'TstNw09567', 'Terminate', 3, 2018 );



DECLARE @FromDate DATETIME ,
        @ToDate DATETIME;

SELECT @FromDate = MIN(CAST(CONCAT(Yr, '-', Month, '-', 01) AS DATE)) ,
       @ToDate = MAX(CAST(CONCAT(Yr, '-', Month, '-', 01) AS DATE))
FROM   #Temp;

DECLARE @MinDt DATE ,
        @MaxDt DATE;

SELECT   TOP ( DATEDIFF(MONTH, @FromDate, @ToDate) + 1 ) calendarDate = CAST(DATEADD(
                                                                                 MONTH ,
                                                                                 number ,
                                                                                 @FromDate) AS DATE) ,
                                                         Month = MONTH(
                                                                     DATEADD(
                                                                         MONTH ,
                                                                         number ,
                                                                         @FromDate)) ,
                                                         Year = YEAR(
                                                                    DATEADD(
                                                                        MONTH ,
                                                                        number ,
                                                                        @FromDate))
INTO     #calendar
FROM     [master].dbo.spt_values
WHERE    [type] = N'P'
ORDER BY number;

;WITH AccountCal
AS ( SELECT  DISTINCT t.AccountId ,
                      cal.calendarDate
     FROM    (   SELECT   MAX(calendarDate) AS calendarDate
                 FROM     #calendar c
                 GROUP BY c.Year ,
                          c.Month ) cal
             CROSS JOIN (   SELECT AccountId ,
                                   MIN(DATEFROMPARTS(Yr, Month, 1)) OVER ( PARTITION BY AccountId ) AS Mindate ,
                                   MAX(DATEFROMPARTS(Yr, Month, 1)) OVER ( PARTITION BY AccountId ) AS Maxdate 
                            FROM   #Temp ) t
     WHERE   cal.calendarDate
     BETWEEN t.Mindate AND t.Maxdate )


SELECT   cal.AccountId ,
         x.Churn ,
         MONTH(cal.calendarDate) AS Month ,
         YEAR(cal.calendarDate) AS Yr
FROM     AccountCal cal
         CROSS APPLY (   SELECT   TOP 1 Churn
                         FROM     #Temp t
                         WHERE    t.AccountId = cal.AccountId
                                  AND DATEFROMPARTS(t.Yr, t.Month, 1) <= cal.calendarDate
                         ORDER BY DATEFROMPARTS(t.Yr, t.Month, 1) DESC ) AS x
ORDER BY cal.AccountId ,
         cal.calendarDate;

结果:

+-------------+-----------+-------+------+
|  AccountId  |   Churn   | Month |  Yr  |
+-------------+-----------+-------+------+
| Tst05716825 | Active    |     9 | 2016 |
| Tst05716825 | Active    |    10 | 2016 |
| Tst05716825 | Active    |    11 | 2016 |
| Tst05716825 | Active    |    12 | 2016 |
| Tst05716825 | Active    |     1 | 2017 |
| Tst05716825 | Active    |     2 | 2017 |
| Tst05716825 | Suspend   |     3 | 2017 |
| Tst05716825 | Suspend   |     4 | 2017 |
| Tst05716825 | Suspend   |     5 | 2017 |
| Tst05716825 | Suspend   |     6 | 2017 |
| Tst05716825 | Suspend   |     7 | 2017 |
| Tst05716825 | Suspend   |     8 | 2017 |
| Tst05716825 | Suspend   |     9 | 2017 |
| Tst05716825 | Terminate |    10 | 2017 |
| TstNew09567 | Active    |    11 | 2017 |
| TstNew09567 | Active    |    12 | 2017 |
| TstNew09567 | Active    |     1 | 2018 |
| TstNew09567 | Suspend   |     2 | 2018 |
| TstNew09567 | Suspend   |     3 | 2018 |
| TstNew09567 | Suspend   |     4 | 2018 |
| TstNew09567 | Suspend   |     5 | 2018 |
| TstNew09567 | Terminate |     6 | 2018 |
| TstNw09567  | Active    |     3 | 2016 |
| TstNw09567  | Active    |     4 | 2016 |
| TstNw09567  | Active    |     5 | 2016 |
| TstNw09567  | Active    |     6 | 2016 |
| TstNw09567  | Active    |     7 | 2016 |
| TstNw09567  | Active    |     8 | 2016 |
| TstNw09567  | Active    |     9 | 2016 |
| TstNw09567  | Active    |    10 | 2016 |
| TstNw09567  | Active    |    11 | 2016 |
| TstNw09567  | Active    |    12 | 2016 |
| TstNw09567  | Active    |     1 | 2017 |
| TstNw09567  | Active    |     2 | 2017 |
| TstNw09567  | Active    |     3 | 2017 |
| TstNw09567  | Active    |     4 | 2017 |
| TstNw09567  | Active    |     5 | 2017 |
| TstNw09567  | Active    |     6 | 2017 |
| TstNw09567  | Active    |     7 | 2017 |
| TstNw09567  | Active    |     8 | 2017 |
| TstNw09567  | Active    |     9 | 2017 |
| TstNw09567  | Active    |    10 | 2017 |
| TstNw09567  | Active    |    11 | 2017 |
| TstNw09567  | Active    |    12 | 2017 |
| TstNw09567  | Active    |     1 | 2018 |
| TstNw09567  | Active    |     2 | 2018 |
| TstNw09567  | Terminate |     3 | 2018 |
+-------------+-----------+-------+------+