根据同一个表中的其他行选择行

时间:2017-10-05 06:48:01

标签: sql-server

我想出了一个非常简单的例子来证明我想要解决的问题。

我需要选择那些......

的客户合同

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

4 个答案:

答案 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()) )