通过删除子查询来改进查询

时间:2017-12-11 13:14:28

标签: sql-server subquery query-optimization

我有一个客户表

+--------+---------+
| Id     |  Name   |
+--------+---------+
| 1      |   A     |
| 2      |   b     |
| 3      |   c     |
| 4      |   d     |
| 5      |   3     |
| 6      |   f     |
| 7      |   g     |
+--------+---------+

和订单表

+-----+------+--------------------------+
| ID  | C_Id | OrderDate                |
+-----+------+--------------------------+
| 1   | 1    |  2017-05-12 00:00:00.000 |
| 2   | 2    |  2017-12-12 00:00:00.000 |
| 3   | 3    |  2017-11-12 00:00:00.000 |
| 4   | 4    |  2017-12-12 00:00:00.000 |
| 5   | 1    |  2017-12-12 00:00:00.000 |
| 6   | 2    |  2017-12-12 00:00:00.000 |
| 7   | 3    |  2017-12-12 00:00:00.000 |
| 8   | 4    |  2017-11-12 00:00:00.000 |
| 9   | 2    |  2017-06-12 00:00:00.000 |
| 10  | 3    |  2017-07-12 00:00:00.000 |
+-----+------+--------------------------+

我需要上个月没有购买的顾客的结果。 这是客户3和4在上个月(11月)购买的订单表。结果不应包括客户3和4,即使他们在前几个月购买。

我有这个查询可以完美地返回结果。

SELECT C_ID , MONTH(OrderDate) from [Order]
WHERE MONTH(OrderDate) <> MONTH(GETDATE()) - 1 
AND C_ID NOT IN (
SELECT C_ID FROM [Order] 
WHERE MONTH(OrderDate) = MONTH(GETDATE()) - 1)

任何人都可以帮我写这个查询而不使用子查询

编辑: 为了更清晰,我需要将客户从结果中排除(获取当年的所有订单),如果他们在11月份有任何购买,我也需要结果仅一年。

2 个答案:

答案 0 :(得分:0)

我认为您需要使用其他方式检查早期购买,因为MONTH(OrderDate) <> MONTH(GETDATE()) - 1如果购买的时间不同,则会遇到问题。 你需要扩大你的条件。例如

(
     (MONTH(OrderDate)<MONTH(DATEADD(MONTH,-1,GETDATE())) AND YEAR(OrderDate)=YEAR(DATEADD(MONTH,-1,GETDATE())))
  OR YEAR(OrderDate)<YEAR(DATEADD(MONTH,-1,GETDATE()))
)

或者您可以使用EOMONTH函数(来自SQL Server 2012)。我认为这种变体会更有用。

SELECT
  C_ID,
  MONTH(OrderDate)
FROM [Order]
WHERE EOMONTH(OrderDate)<EOMONTH(DATEADD(MONTH,-1,GETDATE())) -- check for month and year
  AND C_ID NOT IN (
          SELECT C_ID FROM [Order] 
          WHERE EOMONTH(OrderDate)=EOMONTH(DATEADD(MONTH,-1,GETDATE())) -- check for month and year
        )

我认为使用变量

更有用
DECLARE @lastMonth date=EOMONTH(DATEADD(MONTH,-1,GETDATE()))

SELECT
  C_ID,
  MONTH(OrderDate)
FROM [Order]
WHERE EOMONTH(OrderDate)<@lastMonth -- check for month and year
  AND C_ID NOT IN (
          SELECT C_ID FROM [Order] 
          WHERE EOMONTH(OrderDate)=@lastMonth -- check for month and year
        )

没有子查询的变体

SELECT
  C_ID,
  MIN(OrderDate) FirstOrderDate,
  MAX(OrderDate) LastOrderDate
FROM [Order]
WHERE OrderDate<=EOMONTH(DATEADD(MONTH,-1,GETDATE()))
GROUP BY C_ID
HAVING EOMONTH(MAX(OrderDate))<EOMONTH(DATEADD(MONTH,-1,GETDATE()))

或者

DECLARE @lastMonth date=EOMONTH(DATEADD(MONTH,-1,GETDATE()))

SELECT
  C_ID,
  MIN(OrderDate) FirstOrderDate,
  MAX(OrderDate) LastOrderDate
FROM [Order]
WHERE OrderDate<=@lastMonth
GROUP BY C_ID
HAVING EOMONTH(MAX(OrderDate))<@lastMonth

但在这里我只返回MIN(OrderDate)MAX(OrderDate)但也许它适合你。

我认为带有子查询的变体更糟糕。我认为它更清楚。

DECLARE @lastMonth date=EOMONTH(DATEADD(MONTH,-1,GETDATE()))

SELECT
  C_ID,
  YEAR(OrderDate) [Year],
  MONTH(OrderDate) [Month]
  COUNT(ID) OrderCount
FROM [Order]
WHERE EOMONTH(OrderDate)<@lastMonth
  --AND YEAR(OrderDate)=YEAR(@lastMonth) -- if you need only orders from this year
  AND C_ID IN(
          SELECT DISTINCT C_ID
          FROM [Order]
          WHERE EOMONTH(OrderDate)=@lastMonth
        )
GROUP BY C_ID,YEAR(OrderDate),MONTH(OrderDate)

答案 1 :(得分:0)

使用过时信息时,您不能只使用月份编号,因为第1个月是在第12个月(去年)之后。因此,使用日期,而不是月份数。

对于此查询,我们需要&#34;本月&#34;和#34;上个月&#34;按他们的日期(而不是月份数字),我们可以通过使用getdate()

开始

一个有用的&#34;技巧&#34;这里是计算本月的第一天,我们可以通过计算从零datediff(month,0, getdate() )开始的月数然后将该数字添加到零dateadd(month, ..., 0)来完成。因此,一旦我们有了本月的第一个月,就可以通过减去或增加1个月来轻松计算上个月的第一个月和下个月的第一个月。

因此,对于可在任何版本的SQL Server中使用的解决方案:

SQL Fiddle

MS SQL Server 2014架构设置

CREATE TABLE Orders
    ([ID] int, [C_Id] int, [OrderDate] datetime)
;

INSERT INTO Orders
    ([ID], [C_Id], [OrderDate])
VALUES
    (1, 1, '2017-05-12 00:00:00'),
    (2, 2, '2017-12-12 00:00:00'),
    (3, 3, '2017-11-12 00:00:00'),
    (4, 4, '2017-12-12 00:00:00'),
    (5, 1, '2017-12-12 00:00:00'),
    (6, 2, '2017-12-12 00:00:00'),
    (7, 3, '2017-12-12 00:00:00'),
    (8, 4, '2017-11-12 00:00:00'),
    (9, 2, '2017-06-12 00:00:00'),
    (10, 3, '2017-07-12 00:00:00')
;


CREATE TABLE Customers
    ([Id] int, [Name] varchar(1))
;

INSERT INTO Customers
    ([Id], [Name])
VALUES
    (1, 'A'),
    (2, 'b'),
    (3, 'c'),
    (4, 'd'),
    (5, '3'),
    (6, 'f'),
    (7, 'g')
;

查询1

declare @this_month datetime = dateadd(month, datediff(month,0, getdate() ), 0)
declare @last_month datetime = dateadd(month,-1,@this_month)

select
      c.Id
    , c.name
    , count(case when o.OrderDate >= @last_month and o.OrderDate < @this_month then 1 end) last_month
    , count(case when o.OrderDate >= @this_month then 1 end) this_month
from customers c
LEFT join orders o on c.id = o.c_id
                  and OrderDate >= @last_month
                  and OrderDate < dateadd(month,1,@this_month)
group by c.Id, c.name
having count(case when o.OrderDate >= @last_month and o.OrderDate < @this_month then 1 end) = 0
and count(case when o.OrderDate >= @this_month then 1 end) > 0

<强> Results

| Id | name | last_month | this_month |
|----|------|------------|------------|
|  1 |    A |          0 |          1 |
|  2 |    b |          0 |          2 |

----

declare @this_year  datetime = dateadd(year, datediff(year,0, getdate() ), 0)
declare @this_month datetime = dateadd(month, datediff(month,0, getdate() ), 0)
declare @last_month datetime = dateadd(month,-1,@this_month)

select
      c.Id
    , c.name
    , count(case when o.OrderDate >= @last_month and o.OrderDate < @this_month then 1 end) last_month
    , count(o.OrderDate) this_year
from customers c
LEFT join orders o on c.id = o.c_id
                  and OrderDate >= @this_year
                  and OrderDate < dateadd(year,1,@this_year)
group by c.Id, c.name
having count(case when o.OrderDate >= @last_month and o.OrderDate < @this_month then 1 end) = 0
and count(o.OrderDate) > 0
;