检测是否缺少月份并使用 select 语句自动插入它们 (MSSQL)
Detect if a month is missing and insert them automatically with a select statement (MSSQL)
我正在尝试编写一个 select 语句来检测月份是否不存在并自动插入值为 0 的月份。它应该插入从第一个条目到最后一个条目的所有缺失月份。
示例:
我的 table 看起来像这样:
声明后应该是这样的:
您需要一个递归 CTE
来获取 table 中的所有年份(以及缺失的年份,如果有的话),另一个获取所有月份编号 1-12。
这些 CTE 的 CROSS
连接将与 table 的 LEFT
连接连接并最终过滤,以便第一个 year/month 之前的行和最后一个 year/month 被排除在外:
WITH
limits AS (
SELECT MIN(year) min_year, -- min year in the table
MAX(year) max_year, -- max year in the table
MIN(DATEFROMPARTS(year, monthnum, 1)) min_date, -- min date in the table
MAX(DATEFROMPARTS(year, monthnum, 1)) max_date -- max date in the table
FROM tablename
),
years(year) AS ( -- recursive CTE to get all the years of the table (and the missing ones if any)
SELECT min_year FROM limits
UNION ALL
SELECT year + 1
FROM years
WHERE year < (SELECT max_year FROM limits)
),
months(monthnum) AS ( -- recursive CTE to get all the month numbers 1-12
SELECT 1
UNION ALL
SELECT monthnum + 1
FROM months
WHERE monthnum < 12
)
SELECT y.year, m.monthnum,
DATENAME(MONTH, DATEFROMPARTS(y.year, m.monthnum, 1)) month,
COALESCE(value, 0) value
FROM months m CROSS JOIN years y
LEFT JOIN tablename t
ON t.year = y.year AND t.monthnum = m.monthnum
WHERE DATEFROMPARTS(y.year, m.monthnum, 1)
BETWEEN (SELECT min_date FROM limits) AND (SELECT max_date FROM limits)
ORDER BY y.year, m.monthnum
参见demo。
您不应将日期组件存储在两个单独的列中;相反,您应该只有一列,具有适当的 date
类数据类型。
一种方法是使用递归查询生成 table 中最早和最晚日期之间的所有月份开始,然后用 left join
填充 table。
在 SQL 服务器中:
with cte as (
select min(datefromparts(year, monthnum, 1)) as dt,
max(datefromparts(year, monthnum, 1)) as dt_max
from mytable
union all
select dateadd(month, 1, dt)
from cte
where dt < dt_max
)
select c.dt, coalesce(t.value, 0) as value
from cte c
left join mytable t on datefromparts(t.year, t.month, 1) = c.dt
如果您的数据分布超过 100 个月,您需要在查询末尾添加 option(maxrecursion 0)
。
如果您愿意,可以在最后 select
中提取日期组件:
select
year(c.dt) as yr,
month(c.dt) as monthnum,
datename(month, c.dt) as monthname,
coalesce(t.value, 0) as value
from ...
我正在尝试编写一个 select 语句来检测月份是否不存在并自动插入值为 0 的月份。它应该插入从第一个条目到最后一个条目的所有缺失月份。
示例: 我的 table 看起来像这样:
声明后应该是这样的:
您需要一个递归 CTE
来获取 table 中的所有年份(以及缺失的年份,如果有的话),另一个获取所有月份编号 1-12。
这些 CTE 的 CROSS
连接将与 table 的 LEFT
连接连接并最终过滤,以便第一个 year/month 之前的行和最后一个 year/month 被排除在外:
WITH
limits AS (
SELECT MIN(year) min_year, -- min year in the table
MAX(year) max_year, -- max year in the table
MIN(DATEFROMPARTS(year, monthnum, 1)) min_date, -- min date in the table
MAX(DATEFROMPARTS(year, monthnum, 1)) max_date -- max date in the table
FROM tablename
),
years(year) AS ( -- recursive CTE to get all the years of the table (and the missing ones if any)
SELECT min_year FROM limits
UNION ALL
SELECT year + 1
FROM years
WHERE year < (SELECT max_year FROM limits)
),
months(monthnum) AS ( -- recursive CTE to get all the month numbers 1-12
SELECT 1
UNION ALL
SELECT monthnum + 1
FROM months
WHERE monthnum < 12
)
SELECT y.year, m.monthnum,
DATENAME(MONTH, DATEFROMPARTS(y.year, m.monthnum, 1)) month,
COALESCE(value, 0) value
FROM months m CROSS JOIN years y
LEFT JOIN tablename t
ON t.year = y.year AND t.monthnum = m.monthnum
WHERE DATEFROMPARTS(y.year, m.monthnum, 1)
BETWEEN (SELECT min_date FROM limits) AND (SELECT max_date FROM limits)
ORDER BY y.year, m.monthnum
参见demo。
您不应将日期组件存储在两个单独的列中;相反,您应该只有一列,具有适当的 date
类数据类型。
一种方法是使用递归查询生成 table 中最早和最晚日期之间的所有月份开始,然后用 left join
填充 table。
在 SQL 服务器中:
with cte as (
select min(datefromparts(year, monthnum, 1)) as dt,
max(datefromparts(year, monthnum, 1)) as dt_max
from mytable
union all
select dateadd(month, 1, dt)
from cte
where dt < dt_max
)
select c.dt, coalesce(t.value, 0) as value
from cte c
left join mytable t on datefromparts(t.year, t.month, 1) = c.dt
如果您的数据分布超过 100 个月,您需要在查询末尾添加 option(maxrecursion 0)
。
如果您愿意,可以在最后 select
中提取日期组件:
select
year(c.dt) as yr,
month(c.dt) as monthnum,
datename(month, c.dt) as monthname,
coalesce(t.value, 0) as value
from ...