T-SQL查询翻年时的相对周编号问题
T-SQL query relative week numbering issue when turning the year
我的这个视图有一个特定的、相对的周编号列,称为 WeekNr。源 table 中的两列(WeekNr 和 CURWEEK)使用通常的日历周编号:1、2、3... 到 52。
此视图用在 Excel 文件中,业务部门希望查看从 -12 到 +6 的相对周数:过去 12 周和 6 周投影,相对于当前周(今天, 2015 年 1 月 13 日,这是日历的第 2 周)。
所以,这段代码工作得很好,直到今年年初。 52 之后,WeekNr - CURWEEK 不再按预期工作。
你对此有什么优雅的解决方案吗?
CREATE VIEW [dbo].[vw_NOSSCE_HealthKPIs_DEV]
AS
(
SELECT
[Region]
,[Country]
,[GlobalMaterialCode]
,[Material]
,[MaterialCode]
,[PlantCode]
,[Plant]
,[Brand]
,
(WeekNr - (SELECT TOP 1 CURWEEK FROM [TO_BDB].[dbo].[TO_BDB_NOSSCE_FACT_NORMALIZED]) ) as
WeekNr
,[MEDICAL_NEED]
,[TOP_SELLER]
,[PHARMABU_CODE]
,[PharmaBU]
,[SourceLocationCode]
,[SourceLocation]
,[InventoryTotal]
,[MaxStockQtyTotal]
,[TotalSafetyStockTotal]
,[DangerTreshold]
,[FORECAST_FLG]
,[DangerZoneDesc]
FROM [TO_BDB].[dbo].[TO_BDB_NOSSCE_PAST12_HIST]
WHERE (WeekNr - (SELECT TOP 1 CURWEEK
FROM [TO_BDB].[dbo].[TO_BDB_NOSSCE_FACT_NORMALIZED]) )
BETWEEN -12 and 6
GROUP BY [Region]
,[Country]
,[GlobalMaterialCode]
,[Material]
,[MaterialCode]
,[PlantCode]
,[Plant]
,[Brand]
,WeekNr, [MEDICAL_NEED]
,[TOP_SELLER]
,[PHARMABU_CODE]
,[PharmaBU]
,[SourceLocationCode]
,[SourceLocation]
,[InventoryTotal]
,[MaxStockQtyTotal]
,[TotalSafetyStockTotal]
,[DangerTreshold]
,[FORECAST_FLG]
,[DangerZoneDesc]
UNION ALL
SELECT * FROM [dbo].[vw_NOSSCE_HealthKPIs] WHERE WeekNr > 0
)
更新,我创建了日历 table,为什么它给我第 53 周?
CREATE TABLE [dbo].[TO_BDB_NOSSCE_CALENDAR](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DATE] [date] NOT NULL,
[YEAR] AS (datepart(year,[DATE])) PERSISTED,
[SEMESTER] AS (case when datepart(month,[DATE])<(7) then '1' else '2' end) PERSISTED NOT NULL,
[TRIMESTER] AS (case when datepart(month,[DATE])<(4) then '1' else case when datepart(month,[DATE])<(7) then '2' else case when datepart(month,[DATE])<(10) then '3' else '4' end end end) PERSISTED NOT NULL,
[MONTH] AS (case when len(CONVERT([varchar](2),datepart(month,[DATE]),0))=(1) then '0'+CONVERT([varchar](2),datepart(month,[DATE]),0) else CONVERT([varchar](2),datepart(month,[DATE]),0) end) PERSISTED,
[WEEK] AS (case when len(CONVERT([varchar](2),datepart(week,[DATE]),0))=(1) then '0'+CONVERT([varchar](2),datepart(week,[DATE]),0) else CONVERT([varchar](2),datepart(week,[DATE]),0) end),
[DAY] AS (case when len(CONVERT([varchar](2),datepart(day,[DATE]),0))=(1) then '0'+CONVERT([varchar](2),datepart(day,[DATE]),0) else CONVERT([varchar](2),datepart(day,[DATE]),0) end) PERSISTED,
[WEEKNUMBER] AS (datepart(week,[DATE])),
PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
首先,如果您使用代码生成 Excel 文件,最好使用 Excel 公式而不是使用 SQL 进行相对编号。
其次,周数不代表周数。要确定一周,您需要年份和周数,或者您需要一个日期。这意味着您可能需要更改 table 以存储除周之外的年份。
这里我们使用日历 table 来简化像这样的查询。一个最小的、简化的日历 table 可能看起来像这样。我们使用 ISO 年和周,但不要让您感到困惑。您可以使用任何您想要的编号。 (为 PostgreSQL 编写,但原理与 SQL 服务器相同。)
create table calendar (
cal_date date primary key,
iso_year integer not null,
iso_week integer not null
);
create index on calendar (iso_year, iso_week);
我们使用检查约束(未显示)来保证 iso_year 和 iso_week 的值对于 cal_date 的每个值都是正确的。很少(高度信任)的人有插入和删除行的权限。
在标准 SQL 中,我可能会写这样的东西来获得我们感兴趣的绝对周数。
select distinct iso_year, iso_week
from calendar
where cal_date between cast(current_date - interval '12 weeks' as date)
and cast(current_date + interval '6 weeks' as date)
order by iso_year, iso_week;
iso_year iso_week
2014 43
2014 44
2014 45
...
2015 7
2015 8
2015 9
它 returns 19 行,这似乎是适合您的情况的数字。 (注意我的失误。)将其放入常见的 table 表达式或视图中,您可以使用简单的算术计算相对周数。 (我使用了 CTE,所以这个答案或多或少是独立的。)
with absolute_weeks as (
select distinct iso_year, iso_week
from calendar
where cal_date between cast(current_date - interval '12 weeks' as date)
and cast(current_date + interval '6 weeks' as date)
)
select *, (row_number() over (order by iso_year, iso_week) - 13) as relative_week
from absolute_weeks
order by iso_year, iso_week;
iso_year iso_week relative_week
--
2014 43 -12
2014 44 -11
2014 45 -10
...
2015 7 4
2015 8 5
2015 9 6
如果您的数据包含年份和星期,您可以在 iso_year 和 iso_week 上加入您的查询。如果您的数据有日期,您可以将之前的查询移动到 CTE 或视图中,并加入日历 table 以获取每周的日期。
with absolute_weeks as (
select distinct iso_year, iso_week
from calendar
where cal_date between cast(current_date - interval '12 weeks' as date)
and cast(current_date + interval '6 weeks' as date)
), relative_weeks as (
select *, (row_number() over (order by iso_year, iso_week) - 13) as relative_week
from absolute_weeks
)
select c.cal_date, c.iso_year, c.iso_week, r.relative_week
from calendar c
inner join relative_weeks r on c.iso_year = r.iso_year and c.iso_week = r.iso_week
order by c.cal_date;
cal_date iso_year iso_week relative_week
--
2014-10-20 2014 43 -12
2014-10-21 2014 43 -12
2014-10-22 2014 43 -12
...
2015-01-12 2015 3 0
2015-01-13 2015 3 0
2015-01-14 2015 3 0
...
2015-02-27 2015 9 6
2015-02-28 2015 9 6
2015-03-01 2015 9 6
当前的一周——2015 年 1 月 13 日的那一周——是 ISO 的第 3 周,而不是你编号的第 2 周。你自己的日历 table 应该反映你自己的逻辑,而不是我的。
这个查询returns133行,这个数字好像是对的。每个 ISO 周有 7 天,因此 7 * 19 行是基于 ISO 周的日历 table 的正确数字。您自己的要求可能会偶尔给您提供不同天数的一周。
我的这个视图有一个特定的、相对的周编号列,称为 WeekNr。源 table 中的两列(WeekNr 和 CURWEEK)使用通常的日历周编号:1、2、3... 到 52。
此视图用在 Excel 文件中,业务部门希望查看从 -12 到 +6 的相对周数:过去 12 周和 6 周投影,相对于当前周(今天, 2015 年 1 月 13 日,这是日历的第 2 周)。
所以,这段代码工作得很好,直到今年年初。 52 之后,WeekNr - CURWEEK 不再按预期工作。
你对此有什么优雅的解决方案吗?
CREATE VIEW [dbo].[vw_NOSSCE_HealthKPIs_DEV]
AS
(
SELECT
[Region]
,[Country]
,[GlobalMaterialCode]
,[Material]
,[MaterialCode]
,[PlantCode]
,[Plant]
,[Brand]
,
(WeekNr - (SELECT TOP 1 CURWEEK FROM [TO_BDB].[dbo].[TO_BDB_NOSSCE_FACT_NORMALIZED]) ) as
WeekNr
,[MEDICAL_NEED]
,[TOP_SELLER]
,[PHARMABU_CODE]
,[PharmaBU]
,[SourceLocationCode]
,[SourceLocation]
,[InventoryTotal]
,[MaxStockQtyTotal]
,[TotalSafetyStockTotal]
,[DangerTreshold]
,[FORECAST_FLG]
,[DangerZoneDesc]
FROM [TO_BDB].[dbo].[TO_BDB_NOSSCE_PAST12_HIST]
WHERE (WeekNr - (SELECT TOP 1 CURWEEK
FROM [TO_BDB].[dbo].[TO_BDB_NOSSCE_FACT_NORMALIZED]) )
BETWEEN -12 and 6
GROUP BY [Region]
,[Country]
,[GlobalMaterialCode]
,[Material]
,[MaterialCode]
,[PlantCode]
,[Plant]
,[Brand]
,WeekNr, [MEDICAL_NEED]
,[TOP_SELLER]
,[PHARMABU_CODE]
,[PharmaBU]
,[SourceLocationCode]
,[SourceLocation]
,[InventoryTotal]
,[MaxStockQtyTotal]
,[TotalSafetyStockTotal]
,[DangerTreshold]
,[FORECAST_FLG]
,[DangerZoneDesc]
UNION ALL
SELECT * FROM [dbo].[vw_NOSSCE_HealthKPIs] WHERE WeekNr > 0
)
更新,我创建了日历 table,为什么它给我第 53 周?
CREATE TABLE [dbo].[TO_BDB_NOSSCE_CALENDAR](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DATE] [date] NOT NULL,
[YEAR] AS (datepart(year,[DATE])) PERSISTED,
[SEMESTER] AS (case when datepart(month,[DATE])<(7) then '1' else '2' end) PERSISTED NOT NULL,
[TRIMESTER] AS (case when datepart(month,[DATE])<(4) then '1' else case when datepart(month,[DATE])<(7) then '2' else case when datepart(month,[DATE])<(10) then '3' else '4' end end end) PERSISTED NOT NULL,
[MONTH] AS (case when len(CONVERT([varchar](2),datepart(month,[DATE]),0))=(1) then '0'+CONVERT([varchar](2),datepart(month,[DATE]),0) else CONVERT([varchar](2),datepart(month,[DATE]),0) end) PERSISTED,
[WEEK] AS (case when len(CONVERT([varchar](2),datepart(week,[DATE]),0))=(1) then '0'+CONVERT([varchar](2),datepart(week,[DATE]),0) else CONVERT([varchar](2),datepart(week,[DATE]),0) end),
[DAY] AS (case when len(CONVERT([varchar](2),datepart(day,[DATE]),0))=(1) then '0'+CONVERT([varchar](2),datepart(day,[DATE]),0) else CONVERT([varchar](2),datepart(day,[DATE]),0) end) PERSISTED,
[WEEKNUMBER] AS (datepart(week,[DATE])),
PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
首先,如果您使用代码生成 Excel 文件,最好使用 Excel 公式而不是使用 SQL 进行相对编号。
其次,周数不代表周数。要确定一周,您需要年份和周数,或者您需要一个日期。这意味着您可能需要更改 table 以存储除周之外的年份。
这里我们使用日历 table 来简化像这样的查询。一个最小的、简化的日历 table 可能看起来像这样。我们使用 ISO 年和周,但不要让您感到困惑。您可以使用任何您想要的编号。 (为 PostgreSQL 编写,但原理与 SQL 服务器相同。)
create table calendar (
cal_date date primary key,
iso_year integer not null,
iso_week integer not null
);
create index on calendar (iso_year, iso_week);
我们使用检查约束(未显示)来保证 iso_year 和 iso_week 的值对于 cal_date 的每个值都是正确的。很少(高度信任)的人有插入和删除行的权限。
在标准 SQL 中,我可能会写这样的东西来获得我们感兴趣的绝对周数。
select distinct iso_year, iso_week
from calendar
where cal_date between cast(current_date - interval '12 weeks' as date)
and cast(current_date + interval '6 weeks' as date)
order by iso_year, iso_week;
iso_year iso_week 2014 43 2014 44 2014 45 ... 2015 7 2015 8 2015 9
它 returns 19 行,这似乎是适合您的情况的数字。 (注意我的失误。)将其放入常见的 table 表达式或视图中,您可以使用简单的算术计算相对周数。 (我使用了 CTE,所以这个答案或多或少是独立的。)
with absolute_weeks as (
select distinct iso_year, iso_week
from calendar
where cal_date between cast(current_date - interval '12 weeks' as date)
and cast(current_date + interval '6 weeks' as date)
)
select *, (row_number() over (order by iso_year, iso_week) - 13) as relative_week
from absolute_weeks
order by iso_year, iso_week;
iso_year iso_week relative_week -- 2014 43 -12 2014 44 -11 2014 45 -10 ... 2015 7 4 2015 8 5 2015 9 6
如果您的数据包含年份和星期,您可以在 iso_year 和 iso_week 上加入您的查询。如果您的数据有日期,您可以将之前的查询移动到 CTE 或视图中,并加入日历 table 以获取每周的日期。
with absolute_weeks as (
select distinct iso_year, iso_week
from calendar
where cal_date between cast(current_date - interval '12 weeks' as date)
and cast(current_date + interval '6 weeks' as date)
), relative_weeks as (
select *, (row_number() over (order by iso_year, iso_week) - 13) as relative_week
from absolute_weeks
)
select c.cal_date, c.iso_year, c.iso_week, r.relative_week
from calendar c
inner join relative_weeks r on c.iso_year = r.iso_year and c.iso_week = r.iso_week
order by c.cal_date;
cal_date iso_year iso_week relative_week -- 2014-10-20 2014 43 -12 2014-10-21 2014 43 -12 2014-10-22 2014 43 -12 ... 2015-01-12 2015 3 0 2015-01-13 2015 3 0 2015-01-14 2015 3 0 ... 2015-02-27 2015 9 6 2015-02-28 2015 9 6 2015-03-01 2015 9 6
当前的一周——2015 年 1 月 13 日的那一周——是 ISO 的第 3 周,而不是你编号的第 2 周。你自己的日历 table 应该反映你自己的逻辑,而不是我的。
这个查询returns133行,这个数字好像是对的。每个 ISO 周有 7 天,因此 7 * 19 行是基于 ISO 周的日历 table 的正确数字。您自己的要求可能会偶尔给您提供不同天数的一周。