我想出了一个非常简单的例子来证明我想要解决的问题。
我需要选择那些......
的客户合同1)已过期或将在未来三个月内到期
和
2)尚未签订新合同。
所有过期合同以及新合同都保留在表格中。
根据这些业务规则,我的测试数据的预期结果将是返回合同ID 6(客户3),因为它是一个没有新合同和合同ID 7(客户4)的过期合同,因为它少于3运行几个月。
我已经看过一些解决方案是将表连接到自身的示例
e.g。 how do I query sql for a latest record date for each user
我想我可以为每个客户选择最近的合同,然后检查它的到期日期,但它只返回合同ID 6,而不是像我期望的那样返回7。我正在使用SQL 2008 R2。
我出错的任何想法?
SELECT [ContractID]
,[StartDate]
,[ExpiryDate]
,TC.[CustomerID]
FROM [Test].[dbo].[TestContract] TC
inner join
(
select CustomerID,
MAX(ExpiryDate) as MaxDate
From Test.dbo.TestContract
Group by CustomerID
)CM on TC.CustomerID = CM.CustomerID and TC.ExpiryDate = CM.MaxDate
Where TC.ExpiryDate < DateAdd(DAY, 30, GETDATE())
这是我的测试数据
ContractID StartDate ExpiryDate CustomerID
1 2017-02-01 00:00:00.000 2018-02-01 00:00:00.000 1
2 2016-01-01 00:00:00.000 2017-01-01 00:00:00.000 1
4 2016-01-01 00:00:00.000 2017-11-01 00:00:00.000 2
5 2017-11-01 00:00:00.000 2018-11-01 00:00:00.000 2
6 2016-10-01 00:00:00.000 2017-10-01 00:00:00.000 3
7 2016-12-01 00:00:00.000 2017-12-01 00:00:00.000 4
8 2015-12-01 00:00:00.000 2016-12-01 00:00:00.000 4
9 2017-06-01 00:00:00.000 2018-06-01 00:00:00.000 5
这是一个重新创建测试表和数据的脚本。
USE [Test]
GO
/****** Object: Table [dbo].[TestContract] Script Date: 10/05/2017 17:07:33 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TestContract](
[ContractID] [int] IDENTITY(1,1) NOT NULL,
[StartDate] [datetime] NOT NULL,
[ExpiryDate] [datetime] NOT NULL,
[CustomerID] [int] NOT NULL,
CONSTRAINT [PK_TestContract] PRIMARY KEY CLUSTERED
(
[ContractID] 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 IDENTITY_INSERT [dbo].[TestContract] ON
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (1, CAST(0x0000A70D00000000 AS DateTime), CAST(0x0000A87A00000000 AS DateTime), 1)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (2, CAST(0x0000A58000000000 AS DateTime), CAST(0x0000A6EE00000000 AS DateTime), 1)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (4, CAST(0x0000A58000000000 AS DateTime), CAST(0x0000A81E00000000 AS DateTime), 2)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (5, CAST(0x0000A81E00000000 AS DateTime), CAST(0x0000A98B00000000 AS DateTime), 2)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (6, CAST(0x0000A69200000000 AS DateTime), CAST(0x0000A7FF00000000 AS DateTime), 3)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (7, CAST(0x0000A6CF00000000 AS DateTime), CAST(0x0000A83C00000000 AS DateTime), 4)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (8, CAST(0x0000A56100000000 AS DateTime), CAST(0x0000A6CF00000000 AS DateTime), 4)
INSERT [dbo].[TestContract] ([ContractID], [StartDate], [ExpiryDate], [CustomerID]) VALUES (9, CAST(0x0000A78500000000 AS DateTime), CAST(0x0000A8F200000000 AS DateTime), 5)
SET IDENTITY_INSERT [dbo].[TestContract] OFF
答案 0 :(得分:2)
你说&#34;已过期或将在未来三个月内到期&#34;意味着应该有DateAdd(DAY, 90, GETDATE())
而不是30
在您的查询中修改后:
SELECT [ContractID]
,[StartDate]
,[ExpiryDate]
,TC.[CustomerID]
FROM [TestContract] TC
inner join
(
select CustomerID,
MAX(ExpiryDate) as MaxDate
From TestContract
Group by CustomerID
)CM on TC.CustomerID = CM.CustomerID and TC.ExpiryDate = CM.MaxDate
Where TC.ExpiryDate < DateAdd(DAY, 90, GETDATE())
一种选择:
select [ContractID],[StartDate],[ExpiryDate] ,[CustomerID]
from (select [ContractID],[StartDate],ExpiryDate ,TC.[CustomerID],
ROW_NUMBER() over (partition by customerid order by ExpiryDate desc) rn
FROM [TestContract] TC ) a
where rn =1
and ExpiryDate < DateAdd(DAY, 90, GETDATE())
答案 1 :(得分:2)
我理解这个问题的方法,不应该像ROW_NUMBER()这样的窗口函数。
使用加入方法......
SELECT
TC.ContractID
, TC.StartDate
, TC.ExpiryDate
, TC.CustomerID
FROM dbo.TestContract TC
LEFT JOIN dbo.TestContract TC2
ON TC2.CustomerID = TC.CustomerID
AND TC2.StartDate > TC.StartDate
WHERE
TC.ExpiryDate < dateadd(day, 90, getdate())
AND TC2.ContractID is null
但是,由于我们在select子句中不需要连接表中的任何列,我宁愿选择使用NOT EXISTS。虽然两者都不是很复杂,但我更容易理解,因为阅读时它更接近于业务规则,并且不需要考虑确定连接是否会产生额外的行......
SELECT
TC.ContractID
, TC.StartDate
, TC.ExpiryDate
, TC.CustomerID
FROM dbo.TestContract TC
WHERE
TC.ExpiryDate < dateadd(day, 90, getdate())
AND NOT EXISTS (
SELECT *
FROM dbo.TestContract TC2
WHERE
TC2.CustomerID = TC.CustomerID
AND TC2.StartDate > TC.StartDate
)
如果您有仅在其连接条件中使用的内部联接表,或仅在其自己的连接条件和where子句中使用的外部联接表,则通常可以使用EXISTS或NOT EXISTS重新编写查询
答案 2 :(得分:1)
在子查询中使用ROW_NUMBER()
获取每个客户的最新合同,然后检查expirydate
SELECT ContractID,StartDate,ExpiryDate,CustomerID
FROM (
SELECT ContractID,StartDate,ExpiryDate,CustomerID,
ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY ExpiryDate DESC) AS RN
FROM [YourTable]
) X
WHERE X.RN=1 AND X.ExpiryDate < DateAdd(DAY, 90, GETDATE())
答案 3 :(得分:0)
SELECT ContractID,StartDate,ExpiryDate,CustomerID
FROM (
SELECT ContractID,StartDate,ExpiryDate,CustomerID,
ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY ExpiryDate DESC) AS RN
FROM TestContract
) a
WHERE a.RN=1 AND ( a.ExpiryDate < DateAdd(DAY, 30, GETDATE()) OR a.ExpiryDate <= DateAdd(M, 3, GETDATE()) )