Sql server 合并/拆分具有优先级的重叠日期范围
我有三张桌子。一张表格告诉我某个供应商何时签订合同。第二条告诉我我们与所有供应商签订的基本费用表。第三种情况告诉我,某一特定合同中的一项费用是否有不同的合同费率。这些表如下所示:Sql server 合并/拆分具有优先级的重叠日期范围,sql-server,tsql,intervals,aggregation,schedule,Sql Server,Tsql,Intervals,Aggregation,Schedule,我有三张桌子。一张表格告诉我某个供应商何时签订合同。第二条告诉我我们与所有供应商签订的基本费用表。第三种情况告诉我,某一特定合同中的一项费用是否有不同的合同费率。这些表如下所示: CREATE TABLE [dbo].[Facility]( [FacilityID] [bigint] IDENTITY(1,1) NOT NULL, [ProviderID] [varchar](50) NOT NULL, [VendorID] [bigint] NOT NULL,
CREATE TABLE [dbo].[Facility](
[FacilityID] [bigint] IDENTITY(1,1) NOT NULL,
[ProviderID] [varchar](50) NOT NULL,
[VendorID] [bigint] NOT NULL,
[FacilityName] [varchar](300) NOT NULL,
[FacilityAddress1] [varchar](300) NOT NULL,
[FacilityAddress2] [varchar](300) NOT NULL,
[FacilityCity] [varchar](300) NOT NULL,
[FacilityState] [char](2) NOT NULL,
[FacilityZip] [varchar](10) NOT NULL,
[ContractEffectiveDate] [date] NOT NULL,
[ContractTermDate] [date] NOT NULL,
CONSTRAINT [PK_Facility] PRIMARY KEY CLUSTERED
(
[FacilityID] 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
CREATE TABLE [dbo].[BaseFeeSchedule](
[BaseFeeScheduleID] [int] IDENTITY(1,1) NOT NULL,
[FeeCode] [varchar](10) NOT NULL,
[Description] [varchar](800) NOT NULL,
[Rate] [money] NOT NULL,
[CategoryID] [int] NOT NULL,
[RateEffectiveDate] [date] NOT NULL,
[RateTermDate] [date] NOT NULL,
CONSTRAINT [PK_BaseFeeSchedule] PRIMARY KEY CLUSTERED
(
[BaseFeeScheduleID] 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
CREATE TABLE [dbo].[OverrideFeeSchedule](
[OverrideFeeScheduleID] [bigint] IDENTITY(1,1) NOT NULL,
[FacilityID] [bigint] NOT NULL,
[FeeCode] [varchar](10) NOT NULL,
[OverrideRate] [money] NOT NULL,
[RateEffectiveDate] [date] NOT NULL,
[RateTermDate] [date] NOT NULL,
CONSTRAINT [PK_OverrideFeeSchedule] PRIMARY KEY CLUSTERED
(
[OverrideFeeScheduleID] 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
ALTER TABLE [dbo].[OverrideFeeSchedule] WITH CHECK ADD CONSTRAINT [FK_OverrideFeeSchedule_Facility] FOREIGN KEY([FacilityID])
REFERENCES [dbo].[Facility] ([FacilityID])
GO
ALTER TABLE [dbo].[OverrideFeeSchedule] CHECK CONSTRAINT [FK_OverrideFeeSchedule_Facility]
GO
CREATE TABLE [dbo].[FeeSchedule](
[FeeScheduleID] [int] IDENTITY(1,1) NOT NULL,
[VendorID] [int] NULL,
[FeeCd] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL,
[ContractedAmount] [money] NOT NULL,
[ProgramTypeID] [int] NULL,
CONSTRAINT [PK_FeeSchedule] PRIMARY KEY CLUSTERED
(
[FeeScheduleID] 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
我们有一个现有系统,其中一个表如下所示:
CREATE TABLE [dbo].[Facility](
[FacilityID] [bigint] IDENTITY(1,1) NOT NULL,
[ProviderID] [varchar](50) NOT NULL,
[VendorID] [bigint] NOT NULL,
[FacilityName] [varchar](300) NOT NULL,
[FacilityAddress1] [varchar](300) NOT NULL,
[FacilityAddress2] [varchar](300) NOT NULL,
[FacilityCity] [varchar](300) NOT NULL,
[FacilityState] [char](2) NOT NULL,
[FacilityZip] [varchar](10) NOT NULL,
[ContractEffectiveDate] [date] NOT NULL,
[ContractTermDate] [date] NOT NULL,
CONSTRAINT [PK_Facility] PRIMARY KEY CLUSTERED
(
[FacilityID] 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
CREATE TABLE [dbo].[BaseFeeSchedule](
[BaseFeeScheduleID] [int] IDENTITY(1,1) NOT NULL,
[FeeCode] [varchar](10) NOT NULL,
[Description] [varchar](800) NOT NULL,
[Rate] [money] NOT NULL,
[CategoryID] [int] NOT NULL,
[RateEffectiveDate] [date] NOT NULL,
[RateTermDate] [date] NOT NULL,
CONSTRAINT [PK_BaseFeeSchedule] PRIMARY KEY CLUSTERED
(
[BaseFeeScheduleID] 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
CREATE TABLE [dbo].[OverrideFeeSchedule](
[OverrideFeeScheduleID] [bigint] IDENTITY(1,1) NOT NULL,
[FacilityID] [bigint] NOT NULL,
[FeeCode] [varchar](10) NOT NULL,
[OverrideRate] [money] NOT NULL,
[RateEffectiveDate] [date] NOT NULL,
[RateTermDate] [date] NOT NULL,
CONSTRAINT [PK_OverrideFeeSchedule] PRIMARY KEY CLUSTERED
(
[OverrideFeeScheduleID] 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
ALTER TABLE [dbo].[OverrideFeeSchedule] WITH CHECK ADD CONSTRAINT [FK_OverrideFeeSchedule_Facility] FOREIGN KEY([FacilityID])
REFERENCES [dbo].[Facility] ([FacilityID])
GO
ALTER TABLE [dbo].[OverrideFeeSchedule] CHECK CONSTRAINT [FK_OverrideFeeSchedule_Facility]
GO
CREATE TABLE [dbo].[FeeSchedule](
[FeeScheduleID] [int] IDENTITY(1,1) NOT NULL,
[VendorID] [int] NULL,
[FeeCd] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL,
[ContractedAmount] [money] NOT NULL,
[ProgramTypeID] [int] NULL,
CONSTRAINT [PK_FeeSchedule] PRIMARY KEY CLUSTERED
(
[FeeScheduleID] 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
该表在代码中用于确定每个供应商的正确付款率。我的工作是更新该表,但事实证明这是有问题的,因为不同的设施在不同的日期签订了合同。每份合同都包括基本费用表。然而,合同允许某些费用被覆盖,当有折扣时,不同的费用通常低于正常合同费用,当需要增加附加费时,有时更高。这三个表是我构建的用来存储所有当前数据的表,我一直在使用它们来构建软件所需的FeeSchedule表。处理更改很容易,但我的任务是验证FeeSchedule表中的数据是否准确
FeeSchedule表不仅包括我唯一更改的新数据,还包括以前的数据。因此,计划是获取三个表中的数据,运行查询以合并日期范围,其中OverrideFeeSchedule表中的费用优先于BaseFeeSchedule表中的费用
例如:
INSERT INTO Facility(VendorID,ContractEffectiveDate,ContractTermDate,...)
VALUES(1,'1/1/2017','12/31/9999',...) --Assume FacilityID=1
INSERT INTO BaseFeeSchedule(FeeCode,Rate,RateEffectiveDate,RateTermDate,...)
VALUES('1',100,'1/1/2015','10/15/2016',...),
('1',120,'10/16/2016','4/5/2018',...),
('1',140,'4/6/2018','12/31/9999',...)
INSERT INTO OverrideFeeSchedule(FacilityID,FeeCode,OverrideRate,RateEffectiveDate,RateTermDate,...)
VALUES(1,'1',50,'3/1/2017','5/31/2018',...),
(1,'1',70,'7/1/2018','12/31/9999',...)
And from this data, I would want:
INSERT INTO FeeSchedule(VendorID, FeeCd, StartDate,EndDate,ContractedAmount)
VALUES(1,'1','1/1/2017','2/28/2017',120), --From BaseFeeSchedule
(1,'1','3/1/2017','5/31/2018',50), --From OverrideFeeSchedule
(1,'1','6/1/2018','6/30/2018',140), --From BaseFeeSchedule
(1,'1','7/1/2018','12/31/9999',70) --From OverrideFeeSchedule
我已经验证,OverrideFeeSchedule表中没有重叠的单个设施/FeeCode组合的数据,BaseFeeSchedule中也没有重叠的单个FeeCode的数据。我目前的解决方案需要很长时间。我正在做以下工作:
建立一个表格,记录自第一个合同设施开始的每天。BigTable只是一个有大约一百万条记录的表,从第一次与供应商签订合同到一年后的每一天,我只取足够的记录。然而,由于最大递归大约是20000天,当从第一个签约供应商到从今天开始的一年的范围超过20000天时,我可能会因为违反最大递归而得到错误。所以,我希望有一个不同的解决方案
SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY A.TableID) - 1,B.MinDate) CheckDate
INTO #DatesToCheck
FROM BigTable A
CROSS JOIN
(SELECT MIN(ContractEffectiveDate) MinDate
FROM Facility) B
WHERE DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY A.TableID) - 1,B.MinDate) < DATEADD(YEAR,1,GETDATE())
将此表连接到另一个表,以构建一个庞大的表,其中包括每天、当天签约的每个设施、当天应收取的每个费用代码以及当天的具体费率。我不会为这个连接的代码操心,但是它并不难写
接下来,我使用此处描述的技术合并日期范围:
虽然这项技术可行,但速度非常慢。有没有更直接的方法来生成我正在寻找的结果集?基本上,我正在寻找如何修改该链接中的方法,以考虑不同优先级的潜在重叠,如我提供的示例中所示。我希望我正确地理解了这一点 首先,您应该实现一个数字/日期表。这不是绝对必要的,但在很多情况下都很漂亮。你 有了这样的列表,您可以尝试以下内容:
DECLARE @endDate DATE='20191231';
WITH DailyBaseRate AS
(
SELECT CoveredDays.CalendarDate
,CONCAT('base ',bfs.RateEffectiveDate) AS RateKey
,bfs.FeeCode
,bfs.Rate
FROM BaseFeeSchedule bfs
CROSS APPLY(SELECT * FROM RunningNumbers rn WHERE rn.CalendarDate<=@endDate AND rn.CalendarDate>=bfs.RateEffectiveDate AND rn.CalendarDate<=bfs.RateTermDate) CoveredDays
)
,OverrideRates AS
(
SELECT CoveredDays.CalendarDate
,o.FacilityID
,CONCAT('override ',o.RateEffectiveDate) AS RateKey
,o.FeeCode
,o.OverrideRate
FROM OverrideFeeSchedule o
CROSS APPLY(SELECT * FROM RunningNumbers rn WHERE rn.CalendarDate<=@endDate AND rn.CalendarDate>=o.RateEffectiveDate AND rn.CalendarDate<=o.RateTermDate) CoveredDays
)
,EffectiveRates AS
(
SELECT f.*
,dbr.CalendarDate
,COALESCE(ovr.RateKey, dbr.RateKey) AS EffectiveRateKey
,COALESCE(ovr.FeeCode, dbr.FeeCode) AS EffectiveFeeCode
,COALESCE(ovr.OverrideRate, dbr.Rate) AS EffectiveRate
FROM dbo.Facility f
CROSS JOIN DailyBaseRate dbr
LEFT JOIN OverrideRates ovr ON ovr.FacilityID=f.FacilityID AND ovr.CalendarDate=dbr.CalendarDate
WHERE dbr.CalendarDate<=@endDate
AND dbr.CalendarDate>=f.ContractEffectiveDate
AND dbr.CalendarDate<=f.ContractTermDate
)
SELECT FacilityID,FacilityName
,EffectiveRateKey,EffectiveFeeCode,EffectiveRate
,MIN(CalendarDate) AS FromDate
,MAX(CalendarDate) AS ToDate
FROM EffectiveRates
GROUP BY FacilityID,FacilityName,EffectiveRateKey,EffectiveFeeCode,EffectiveRate
ORDER BY FacilityID,FromDate;
简言之,这个想法
第一个CTE会将您的基本日程转换为每天一行的日期列表,其中包含每天的当前代码和费率
第二个CTE将执行相同的操作,但使用覆盖计划
第三个CTE将交叉连接您的设施和基本计划。如果有许多设施,并且没有额外的行,那么这可能会变得相当大
该集合被过滤到实际使用的范围
最后,我们可以按一些列进行分组,并使用“最小”和“最大”选择间隔边界
提示:我们需要EffectiveRateKey来避免以相同的速率和代码将不同的间隔组合在一起。作为一个副作用,你可以看到,从哪个来源的速度采取
Hint2:由于我们永远不知道引擎将以何种顺序运行,请考虑索引,使用索引临时表而不是CTE可能会有很大帮助…这看起来很棒,而且比我目前正在做的工作效率更高。昨晚,我有了另一个想法。我可以使用三通技术。第一次通过将添加设施合同范围内的所有覆盖率。接下来,我可以在第一个覆盖日期之前获取基本利率。最后,我可以通过使用LEAD获取下一个RateEffectiveDate并查找RateTermDate和下一个RateEffectiveDate之间的基准利率来获取覆盖日期之间的基准利率。这样会更有效率吗?