PAYATHON 2026

SQL:如何将多个查询合并并判断连续计费月份

数据小许

结论

先把每条计费记录统一转换为对应月份,再按客户统计不同计费月份的数量。如果从最早月份到最晚月份应包含的月份数与实际计费月份数相等,则计费月份连续,结果为 Concurrent;否则为 Non-Concurrent。

这里有两个前提:month 能转换为真实日期,并且查询中只包含已计费记录。不要直接根据 Jan、Feb 等月份名称判断连续性,因为月份名称无法区分年份。

SQL Server 示例

假设:

  • custName 是客户名称;
  • billing_date 是计费日期;
  • billed = 1 表示已经计费;
  • 同一客户在同一个月可能有多条记录。
WITH BillingMonths AS (
    SELECT DISTINCT
        custName,
        DATEFROMPARTS(
            YEAR(billing_date),
            MONTH(billing_date),
            1
        ) AS billing_month
    FROM db_name
    WHERE billed = 1
),
CustomerSummary AS (
    SELECT
        custName,
        COUNT(*) AS month_count,
        MIN(billing_month) AS first_month,
        MAX(billing_month) AS last_month
    FROM BillingMonths
    GROUP BY custName
)
SELECT
    custName,
    month_count,
    CASE
        WHEN DATEDIFF(MONTH, first_month, last_month) + 1 = month_count
            THEN 'Concurrent'
        ELSE 'Non-Concurrent'
    END AS billing_cycle
FROM CustomerSummary
ORDER BY custName;

查询结果如下:

custName       month_count  billing_cycle
Customer 1234  2            Concurrent
Customer 2345  3            Concurrent
Customer 3456  4            Non-Concurrent

如果需要把结果拼接成完整描述,可以写成:

WITH BillingMonths AS (
    SELECT DISTINCT
        custName,
        DATEFROMPARTS(
            YEAR(billing_date),
            MONTH(billing_date),
            1
        ) AS billing_month
    FROM db_name
    WHERE billed = 1
),
CustomerSummary AS (
    SELECT
        custName,
        COUNT(*) AS month_count,
        MIN(billing_month) AS first_month,
        MAX(billing_month) AS last_month
    FROM BillingMonths
    GROUP BY custName
)
SELECT
    CONCAT(
        custName,
        ' was billed for ',
        month_count,
        ' months ',
        CASE
            WHEN DATEDIFF(MONTH, first_month, last_month) + 1 = month_count
                THEN 'Concurrent'
            ELSE 'Non-Concurrent'
        END
    ) AS result
FROM CustomerSummary
ORDER BY custName;

判断逻辑

假设某客户在 1 月、2 月和 3 月都有计费记录:

实际月份数 = 3
最早月份到最晚月份的跨度 = DATEDIFF(MONTH, Jan, Mar) + 1 = 3

两个数值相等,说明这些月份是连续的。

如果计费月份是 2 月、4 月、6 月和 8 月:

实际月份数 = 4
最早月份到最晚月份的跨度 = DATEDIFF(MONTH, Feb, Aug) + 1 = 7

两个数值不相等,说明中间有月份缺失,属于非连续计费。

如果表中分别保存年份和月份

假设年份和月份分别保存在 billing_year 和 billing_month 字段中,其中月份取值为 1 到 12:

WITH BillingMonths AS (
    SELECT DISTINCT
        custName,
        DATEFROMPARTS(
            billing_year,
            billing_month,
            1
        ) AS billing_month
    FROM db_name
    WHERE billed = 1
)
SELECT
    custName,
    COUNT(*) AS month_count,
    CASE
        WHEN DATEDIFF(
                 MONTH,
                 MIN(billing_month),
                 MAX(billing_month)
             ) + 1 = COUNT(*)
            THEN 'Concurrent'
        ELSE 'Non-Concurrent'
    END AS billing_cycle
FROM BillingMonths
GROUP BY custName
ORDER BY custName;

MySQL 写法

WITH BillingMonths AS (
    SELECT DISTINCT
        custName,
        DATE_FORMAT(billing_date, '%Y-%m-01') AS billing_month
    FROM db_name
    WHERE billed = 1
)
SELECT
    custName,
    COUNT(*) AS month_count,
    CASE
        WHEN TIMESTAMPDIFF(
                 MONTH,
                 MIN(billing_month),
                 MAX(billing_month)
             ) + 1 = COUNT(*)
            THEN 'Concurrent'
        ELSE 'Non-Concurrent'
    END AS billing_cycle
FROM BillingMonths
GROUP BY custName
ORDER BY custName;

注意事项

  • 需要统计的是不同的计费月份,不是原始记录数,因此要使用 SELECT DISTINCT custName, billing_month。
  • 月份信息必须包含年份。2025-01 和 2026-01 属于两个不同的计费月份。
  • 如果 month 是数据库关键字或函数名,建议把字段改为 billing_month,也可以按照相应数据库的语法进行转义。
  • 上述查询用于判断某个客户的全部计费月份能否组成一个连续区间。如果需要分别列出同一客户的多段连续周期,则要使用 LAG() 或“日期减行号”的方法进行分段。
  • Apl 通常是 Apr 的误写。生产数据最好保存为真实日期或数字月份,不要依赖月份的英文缩写。

备注:内容仅供参考。