转动年份时,T-SQL查询相对周编号问题

时间:2015-01-13 17:05:44

标签: sql sql-server tsql

我有这个视图,它有一个名为WeekNr的特定的相对周编号列。源表中的两列(WeekNr和CURWEEK)使用通常的日历周编号保存:1,2,3 ...到52.

此视图用于Excel文件,并且企业希望查看从-12到+6的相对周编号:过去12周和6周预测,相对于第0周,即当前周(今天,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
)

更新,我创建了日历表,为什么它会给我一周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

1 个答案:

答案 0 :(得分:2)

首先,如果您使用代码生成Excel文件,那么使用Excel公式而不是使用SQL进行相对编号可能会更好。

其次,周数不能识别周数。要识别一周,您需要年份和周数,或者您需要一个日期。这意味着除了本周之外,您可能还需要更改表来存储年份。

这里我们使用日历表来简化这样的查询。最小的简化日历表可能如下所示。我们使用ISO年和周,但不要让你感到困惑。您可以使用任何类型的编号。 (为PostgreSQL编写,但SQL Server的原理是相同的。)

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

它返回19行,这似乎是您的案例的正确数字。 (注意我自己的错误。)将它放入公共表表达式或视图中,您可以使用简单的算术对相对周数进行编号。 (我使用过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或视图中,然后加入日历表以获取每周的日期。

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-01-13周 - 是ISO第3周,而不是第2周,因为您为它们编号。你自己的日历表应该反映你自己的逻辑,而不是我的。

此查询返回133行,这似乎是正确的数字。每个ISO周有七天,因此7 * 19行是基于ISO周的日历表的正确数字。您自己的要求可能会偶尔为您提供一个具有不同天数的周。