【问题标题】:Improve the query by removing the sub query通过删除子查询改进查询
【发布时间】:2017-12-11 13:14:28
【问题描述】:

我有一张客户表

+--------+---------+ |身份证 |姓名 | +--------+---------+ | 1 |一个 | | 2 |乙 | | 3 | c | | 4 | d | | 5 | 3 | | 6 | f | | 7 |克 | +--------+---------+

还有一张订单表

+-----+------+--------------+ |身份证 | C_Id |订购日期 | +-----+------+--------------+ | 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 月有任何购买,我需要从结果中排除客户(获取当年的所有订单),我也需要此结果一年。

【问题讨论】:

  • 为什么不想要子查询?是什么让您认为没有子查询的另一种解决方案会更快?
  • 如果省略子查询会发生什么?看起来不应该包含 MONTH(OrderDate) == MONTH(GETDATE()) - 1 的行。
  • @AhmadWabbi 我被要求编写不带子查询的查询。我需要学习其他可能的方法来获得预期的结果
  • @daniu 如果删除子查询意味着我将获得 11 月份尚未购买的客户,但结果中仍然获得客户 3 和 4,这不是我的预期结果。如果客户在 11 月购买,我需要将他们排除在结果之外。

标签: sql-server subquery query-optimization


【解决方案1】:

我认为您需要使用另一种方式来检查早期购买,因为使用MONTH(OrderDate) &lt;&gt; 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)

【讨论】:

  • 非常感谢您的回复,太好了。但是我需要订单数量和月份。您发布的查询返回客户订单的第一个日期和最后一个日期。谢谢我从你的回答中学到了 EOMONTH()
  • 我在答案的末尾又添加了一个查询。但它使用子查询。我认为没有子查询会更困难。
【解决方案2】:

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

对于这个查询,我们需要“本月”和“上个月”的日期(不是月份数字),我们可以从使用 getdate() 开始

这里有一个有用的“技巧”是计算本月的第一天,我们可以通过计算从零开始的月数 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
;

【讨论】:

  • 感谢您的时间和努力。这是问题的确切需要。 如果客户在 11 月有任何购买,我需要从结果中排除客户(获取当年的所有订单),我还需要仅今年的结果
  • 为什么不首先说明真正的问题?为什么你不能调整我给你的东西来满足这些不同的要求——这并不难。试试看。
  • dateadd(year, datediff(year,0, getdate() ), 0) 为您提供当年一月的第一天
  • 实际上我在帖子中提到了真正的问题,为了更清楚我更新了问题。我也试过你给出的查询很好。但我需要订单表中的所有客户,不包括上个月购买的客户(不是订单)。我想知道除 SubQuery 之外的替代方法来实现这一目标的主要事情。抱歉,如果您有任何困惑。
  • 而您仍然看不到如何简单地改变几件事就可以实现这一目标?
猜你喜欢
  • 2017-06-13
  • 1970-01-01
  • 2014-02-16
  • 2012-07-24
  • 2021-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多