【问题标题】:Count of rows by month within period期间内按月计算的行数
【发布时间】:2018-05-10 20:15:09
【问题描述】:

我正在开发一个 T-SQL 函数来返回特定时期内每个月的两个类别(注册和验证)的计数。仅当每个月的计数至少大于 1 时,以下函数才有效。我想做的是给定源表中找到的最低日期的开始日期、今天的结束日期和给定的月数,返回给定用户的注册计数和验证计数月份开始和结束期间之间的 id。

理想情况下,我希望将范围周期数据格式化为MMM-YYYY。对于在此期间没有计数的任何月份,我希望这些值显示为 0。

RELEASED_DATETIMEINTERACTION_SESSION_T 表中的DATETIME 列。

有效期为 7 个月的数据

RANGEPERIOD | ENROLLS | VERIFIES
------------+---------+---------
Nov-2017    |    15   |    15
Dec-2017    |   150   |  2582
Jan-2018    |     0   |     0
Feb-2018    |     0   |    98
Mar-2018    |    10   |     0
Apr-2018    |     8   |     0
May-2018    |    12   |    85

我的代码:

CREATE FUNCTION [dbo].[SVE_GET_ENROL_VERIFY_COUNT_MONTH_R]
     (@DEV_ID INT, @NUM_MONTHS INT)
RETURNS @D TABLE (RANGEPERIOD VARCHAR(10),
                  TOTAL_ENROLLMENTS INT,
                  TOTAL_VERIFICATIONS INT)
AS
BEGIN
    DECLARE @cur VARCHAR(12); -- stores the current record date the cursor points to
    DECLARE @sdate DATETIME;  -- start date of range
    DECLARE @edate DATETIME;  -- end date of range
    DECLARE @tot_enrls INT;   -- stores the @cur date's enrollment count
    DECLARE @tot_verfs INT;   -- stores the @cur date's verification count

    SELECT @sdate = MIN(RELEASED_DATETIME)
    FROM SVE_INTERACTION_SESSION_T 
    WHERE (DEVELOPER_OWNER_ID = @DEV_ID OR DEVELOPER_USER_ID = @DEV_ID) 
      AND RELEASED_DATETIME >= DATEADD(MONTH, -@NUM_MONTHS + 1, GETDATE())

    SELECT @edate = GETDATE();

    INSERT INTO @D (RANGEPERIOD)
        (SELECT
             RANGEPERIOD = CONCAT(SUBSTRING(datename(month, I.RELEASED_DATETIME), 1, 3 ), '-', YEAR(I.RELEASED_DATETIME))
         FROM
             SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) 
         INNER JOIN 
             SVE_APPLICATIONS_T AS A WITH (NOLOCK) ON I.APPLICATION_ID = A._ID
         WHERE
             (I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID) 
             AND I.RELEASED_CODE = 'C' 
             AND A.IS_DELETED = 0 
             AND I.RELEASED_DATETIME >= @sdate 
             AND I.RELEASED_DATETIME < DATEADD(day, 1, @edate)
         GROUP BY 
             YEAR(I.RELEASED_DATETIME), MONTH(I.RELEASED_DATETIME), 
             DATENAME(MONTH, I.RELEASED_DATETIME)
      );

    -- Start of cursor
    DECLARE DTY CURSOR FOR
        SELECT RANGEPERIOD FROM @d

    OPEN DTY

    -- Fetch the first row
    FETCH NEXT FROM DTY INTO @cur

    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @tot_enrls = (SELECT COUNT(DISTINCT I._ID) AS TOT_ENROLLS
                          FROM SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) 
                          INNER JOIN SVE_ENROLL_SESSION_T AS E WITH (NOLOCK) ON E.INTERACTION_SESSION_ID = I._ID 
                          INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK) ON I.APPLICATION_ID = A._ID
                          WHERE
                              (I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID) 
                              AND E.INSTANCE_ID = 0 
                              AND E.COMPLETION_STATUS = 'T' 
                              AND I.RELEASED_CODE = 'C' 
                              AND A.IS_DELETED = 0 
                              AND CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = @cur
            );

        SET @tot_verfs = (
            SELECT
                COUNT( DISTINCT I._ID ) AS TOT_VERIFYS
            FROM
                SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_VERIFY_SESSION_T AS V WITH (NOLOCK)
            ON
                V.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
            ON
                I.APPLICATION_ID = A._ID
            WHERE
                ( I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID ) AND
                V.INSTANCE_ID = 0 AND
                V.COMPLETION_STATUS = 'S' AND
                I.RELEASED_CODE = 'C' AND
                A.IS_DELETED = 0 AND
                CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = @cur
            );

     -- Updates the fields in the return table
        UPDATE
            @d
        SET
            TOTAL_ENROLLMENTS = @tot_enrls,
            TOTAL_VERIFICATIONS = @tot_verfs
        WHERE
            RANGEPERIOD = @cur

     -- Fetch the next row and repeat the above process
        FETCH NEXT FROM DTY INTO @cur
    END

 -- Updates the return table by setting all null values to 0 for better visuals
    UPDATE @d SET TOTAL_ENROLLMENTS = 0, TOTAL_VERIFICATIONS = 0 WHERE TOTAL_ENROLLMENTS IS NULL

    RETURN
END

值得注意的是,我已经做了几天类似的事情,而且效果很好。唯一的区别是我将日历表用于日期范围功能。我知道秘诀在于从日历表构建的临时表中,我试图修改月份函数以适应它,但此时我很慌张

这是一天功能,效果很好

ALTER FUNCTION [dbo].[SVE_GET_ENROL_VERIFY_COUNT_R]( @DEV_ID INT, @NUM_DAYS INT )
RETURNS @D TABLE
(
    RANGEDATE           DATE,
    TOTAL_ENROLLMENTS   INT,
    TOTAL_VERIFICATIONS INT
)
AS
BEGIN
    DECLARE @s DATE;          -- start date of range
    DECLARE @e DATE;          -- end date of range
    DECLARE @cur DATE;        -- stores the current record date the cursor points to
    DECLARE @tot_enrls INT;   -- stores the @cur date's enrollment count
    DECLARE @tot_verfs INT;   -- stores the @cur date's verification count

    SET @e = getdate();       -- stores today's date as the end date
    SET @s = DATEADD( dd, -( @NUM_DAYS-1 ), @e ); -- Subtract 1 from the incoming day and setup the accurate range of days

    INSERT INTO @d ( RANGEDATE )
    (
        SELECT
            RANGEDATE = c.d
        FROM
            Calendar AS c LEFT OUTER JOIN SVE_INTERACTION_SESSION_T as I WITH (NOLOCK)
        ON
            I.RELEASED_DATETIME >= @s AND I.RELEASED_DATETIME < = @e AND
            c.d = CONVERT( DATE, I.RELEASED_DATETIME )
        WHERE
            c.d >= @s AND c.d <= @e
        GROUP BY c.d
    );

 -- Start of cursor
    DECLARE DTY CURSOR FOR

    SELECT RANGEDATE FROM @d

    OPEN DTY

 -- Fetch the first row
    FETCH NEXT FROM DTY INTO @cur

    WHILE @@FETCH_STATUS = 0

    BEGIN
        -- Gets the developer's enrollment count for the current record's day
        SET @tot_enrls = (
            SELECT
                COUNT( DISTINCT I._ID ) AS TOT_ENROLLS
            FROM
                SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_ENROLL_SESSION_T AS E WITH (NOLOCK)
            ON
                E.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
            ON
                I.APPLICATION_ID = A._ID
            WHERE
                ( I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID ) AND
                E.INSTANCE_ID = 0 AND
                E.COMPLETION_STATUS = 'T' AND
                I.RELEASED_CODE = 'C' AND
                A.IS_DELETED = 0 AND
                I.RELEASED_DATETIME >= @cur AND 
                I.RELEASED_DATETIME < dateadd(day,1,@cur)
            );

        -- Gets the developer's verification count for the current record's day
        SET @tot_verfs = (
            SELECT
                COUNT( DISTINCT I._ID ) AS TOT_VERIFYS
            FROM
                SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_VERIFY_SESSION_T AS V WITH (NOLOCK)
            ON
                V.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
            ON
                I.APPLICATION_ID = A._ID
            WHERE
                ( I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID ) AND
                V.INSTANCE_ID = 0 AND
                V.COMPLETION_STATUS = 'S' AND
                I.RELEASED_CODE = 'C' AND
                A.IS_DELETED = 0 AND
                I.RELEASED_DATETIME >= @cur AND 
                I.RELEASED_DATETIME < dateadd(day,1,@cur)
            );

     -- Updates the fields in the return table
        UPDATE
            @d
        SET
            TOTAL_ENROLLMENTS = @tot_enrls,
            TOTAL_VERIFICATIONS = @tot_verfs
        WHERE
            RANGEDATE = @cur

     -- Fetch the next row and repeat the above process
        FETCH NEXT FROM DTY INTO @cur
    END

 -- Updates the return table by setting all null values to 0 for better visuals
    UPDATE @d SET TOTAL_ENROLLMENTS = 0, TOTAL_VERIFICATIONS = 0 WHERE TOTAL_ENROLLMENTS IS NULL

    RETURN
END

这是我修改月份期间使用日历表的样子,但它是史诗般的失败,因为它只返回一行。

INSERT INTO @d ( RANGEPERIOD )
(
    SELECT
        RANGEPERIOD = CONCAT( SUBSTRING( datename( month, c.d ), 1, 3 ), '-', YEAR( c.d ) )
    FROM
        Calendar AS c LEFT OUTER JOIN SVE_INTERACTION_SESSION_T as I WITH (NOLOCK)
    ON
        (YEAR(I.RELEASED_DATETIME) >= YEAR(@s) AND MONTH(I.RELEASED_DATETIME) >= MONTH(@s)) AND (YEAR(I.RELEASED_DATETIME) <= YEAR(@e) AND MONTH(I.RELEASED_DATETIME) <= MONTH(@e)) AND
        c.d = CONVERT( DATE, I.RELEASED_DATETIME )
    WHERE
        (YEAR(c.d) >= YEAR(@s) AND MONTH(c.d) >= MONTH(@s)) AND (YEAR(c.d) <= YEAR(@e) AND MONTH(c.d) <= MONTH(@e))
    GROUP BY c.d
);

谁能指出我正确的方向,或提供帮助?

【问题讨论】:

    标签: sql sql-server tsql sql-server-2016


    【解决方案1】:

    我不能给你一个具体的观点,但我可以给你一些指导来解决你的问题。 我假设您需要这种格式的结果。 (MMM-YYYY)

    在您的修改查询中,您可以删除 SVE_INTERACTION_SESSION_T 表。

    因为

    1. 您没有从该表中选择列。
    2. 您使用的是 LEFT JOIN,那么您的结果不会受到您的条件的影响。结果仍然来自整个日历表

      SELECT RANGEPERIOD = CONCAT(SUBSTRING(datename(MONTH, c.d), 1, 3), '-', YEAR(c.d))
      FROM Calendar AS c
      WHERE (YEAR(c.d) >= YEAR(@s) AND MONTH(c.d) >= MONTH(@s))
        AND (YEAR(c.d) <= YEAR(@e) AND MONTH(c.d) <= MONTH(@e))
      GROUP BY c.d
      

    【讨论】:

    • 嗯,您提出的查询没有为我返回任何结果。让我问一下,如果向您提出这个数据请求,您将如何处理它,而不考虑我所做的一切。我考虑过使用两张临时表,一张用于返回期间之间的所有天数,然后在计算完计数后,返回该表并将结果分组。然而,这对我来说似乎效率低下,我最不想做的就是减慢查询速度。
    • 感谢您强调我在该查询中不需要 INTERACTION_SESSION 表这一事实。我现在想出了一个解决方案,我将日历中的日期放入一个临时表中,然后将这些日期分组到另一个表中,并使用我需要的格式。但是,我很想知道还有什么方法可以做到这一点。
    【解决方案2】:

    如果有人对此解决方案感兴趣,请参阅下面的代码。这可能不是最有效的,但目前我想不出另一种解决方案。

    CREATE FUNCTION [dbo].[SVE_GET_ENROL_VERIFY_COUNT_MONTH_R]( @DEV_ID INT, @NUM_MONTHS INT )
    RETURNS @M TABLE
    (
        RANGEPERIOD         VARCHAR(10),
        TOTAL_ENROLLMENTS   INT,
        TOTAL_VERIFICATIONS INT
    )
    AS
    BEGIN
        -- We need to declare a temporary table to hold the days from the period
        DECLARE @D TABLE(
            RANGEDATE         DATETIME,
            TOTAL_ENROLLMENTS   INT,
            TOTAL_VERIFICATIONS INT
        )
    
        DECLARE @cur VARCHAR(12); -- stores the current record date the cursor points to
        DECLARE @sdate DATETIME;  -- start date of range
        DECLARE @edate DATETIME;  -- end date of range
        DECLARE @tot_enrls INT;   -- stores the @cur date's enrollment count
        DECLARE @tot_verfs INT;   -- stores the @cur date's verification count
    
        SELECT @sdate = DATEADD( MONTH, -( @NUM_MONTHS-1 ), GETDATE() )
        SELECT @edate = GETDATE(); 
    
        -- First fill the temp day table with dates from the calendar
        INSERT INTO @D ( RANGEDATE ) (
            SELECT
                RANGEDATE = CONVERT( DATE, c.d )
            FROM
                Calendar AS c
            WHERE
                c.d >= @sdate AND c.d <= @edate
            GROUP BY c.d
        )
    
        -- Next we fill our return table with our grouped by formatted dates 
        INSERT INTO @M ( RANGEPERIOD ) (
            SELECT
                RANGEPERIOD = CONCAT( SUBSTRING( datename( month, D.RANGEDATE ), 1, 3 ), '-', YEAR( D.RANGEDATE ) )
            FROM
                @D AS D
            GROUP BY year( D.RANGEDATE ), month( D.RANGEDATE ), datename( month, D.RANGEDATE )
        )
    
     -- Start of cursor
        DECLARE DTY CURSOR FOR
    
        SELECT RANGEPERIOD FROM @M
    
        OPEN DTY
    
     -- Fetch the first row
        FETCH NEXT FROM DTY INTO @cur
    
        WHILE @@FETCH_STATUS = 0
    
        BEGIN
            SET @tot_enrls = (
                SELECT
                    COUNT( DISTINCT I._ID ) AS TOT_ENROLLS
                FROM
                    SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_ENROLL_SESSION_T AS E WITH (NOLOCK)
                ON
                    E.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
                ON
                    I.APPLICATION_ID = A._ID
                WHERE
                    ( I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID ) AND
                    E.INSTANCE_ID = 0 AND
                    E.COMPLETION_STATUS = 'T' AND
                    I.RELEASED_CODE = 'C' AND
                    A.IS_DELETED = 0 AND
                    CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = @cur
                );
    
            SET @tot_verfs = (
                SELECT
                    COUNT( DISTINCT I._ID ) AS TOT_VERIFYS
                FROM
                    SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_VERIFY_SESSION_T AS V WITH (NOLOCK)
                ON
                    V.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
                ON
                    I.APPLICATION_ID = A._ID
                WHERE
                    ( I.DEVELOPER_OWNER_ID = @DEV_ID OR I.DEVELOPER_USER_ID = @DEV_ID ) AND
                    V.INSTANCE_ID = 0 AND
                    V.COMPLETION_STATUS = 'S' AND
                    I.RELEASED_CODE = 'C' AND
                    A.IS_DELETED = 0 AND
                    CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = @cur
                );
    
         -- Updates the fields in the return table
            UPDATE
                @M
            SET
                TOTAL_ENROLLMENTS = @tot_enrls,
                TOTAL_VERIFICATIONS = @tot_verfs
            WHERE
                RANGEPERIOD = @cur
    
         -- Fetch the next row and repeat the above process
            FETCH NEXT FROM DTY INTO @cur
        END
    
     -- Updates the return table by setting all null values to 0 for better visuals
        UPDATE @M SET TOTAL_ENROLLMENTS = 0, TOTAL_VERIFICATIONS = 0 WHERE TOTAL_ENROLLMENTS IS NULL
    
        RETURN
    END
    

    【讨论】:

      猜你喜欢
      • 2013-11-28
      • 2020-09-27
      • 2013-04-07
      • 1970-01-01
      • 1970-01-01
      • 2021-07-20
      • 2019-06-07
      • 1970-01-01
      相关资源
      最近更新 更多