【问题标题】:Counting records for each day where a child record has a certain value计算子记录具有特定值的每一天的记录
【发布时间】:2022-01-13 20:24:03
【问题描述】:

我有两个表(见最后创建表和数据的 SQL 脚本):

带列的订单表

  • 身份证
  • 日期

带有列的OrderItems 表

  • 订单编号
  • ItemTypeID

我需要选择每天至少有一个 ItemTypeID=6000 的商品的订单数和订单中根本没有 ItemTypeID=6000 的订单数。 到目前为止,我得到了这个,但我不知道如何继续:

SELECT
    DATEADD(dd, 0, DATEDIFF(dd, 0, OrderDate)) AS OrderDate,
    COUNT(DISTINCT(Orders.ID)) AS TotalOrders
FROM
    Orders
JOIN
    OrderItems
ON
    Orders.ID = OrderItems.OrderID 
WHERE
    OrderItems.ItemTypeID = 6000
GROUP BY
    DATEADD(dd, 0, DATEDIFF(dd, 0, OrderDate))

创建测试数据的脚本:

CREATE TABLE OrderItems(
    [ID] [int] NOT NULL,
    [OrderID] [int] NOT NULL,
    [ItemTypeID] [int] NOT NULL
)
CREATE TABLE Orders(
    [ID] [int] NOT NULL,
    [OrderDate] [date] NOT NULL
)
GO
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (1, 1, 1000)
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (2, 1, 6000)
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (3, 2, 1000)
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (4, 3, 1000)
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (5, 3, 1000)
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (6, 4, 1000)
INSERT [OrderItems] ([ID], [OrderID], [ItemTypeID]) VALUES (7, 4, 6000)
INSERT [Orders] ([ID], [OrderDate]) VALUES (1, CAST(N'2021-12-01' AS Date))
INSERT [Orders] ([ID], [OrderDate]) VALUES (2, CAST(N'2021-12-01' AS Date))
INSERT [Orders] ([ID], [OrderDate]) VALUES (3, CAST(N'2021-12-02' AS Date))
INSERT [Orders] ([ID], [OrderDate]) VALUES (4, CAST(N'2021-12-03' AS Date))
GO

预期结果应如下所示:

OrderDate   OrdersWithItem  OrdersWithoutItem
2021-12-01  1               1
2021-12-02  0               1
2021-12-03  1               0

【问题讨论】:

  • 请为所提供的样本数据提供您想要的结果。
  • 想想! OrderDate 定义为 DATE。表达式DATEADD(dd, 0, DATEDIFF(dd, 0, OrderDate)) 没有任何用处。也不需要它,因为您可以将日期时间值转换为日期来完成相同的事情,而不会产生复杂性和不可读性。你真正节省了多少精力输入“dd”而不是“day”?

标签: sql sql-server tsql


【解决方案1】:

一个稍微不同的答案:

我们首先准备一个子查询(称为 Typ6000),它只返回那些至少包含一个 Type=6000 的项目的订单 (OrderID)。然后,我们在计算每天的订单时对照此(左连接)检查每个订单。

考虑 OrdersWithItem 列是如何派生的:

如果订单包含一个或多个项目类型 6000,则 OrderId 将在 Typ6000 中,并且 Typ6000.OrderIDcount(case when strong> 函数。Typ6000.OrderID is NOT NULL 将为 true 并且 CASE 表达式将返回 Order.ID. COUNT 函数将简单地计算商品类型为 6000 的订单的 ID。

如果订单不包含任何商品类型:6000 则 OrderId 将不在子查询中,并且 Typ6000.OrderID 将为 NULL强>测试时。 Typ6000.OrderID is NOT NULLCASE WHEN 将是 false,CASE WHEN 函数的结果将是 NULL强>。 COUNT 函数忽略 NULL 值,因此 OrdersWithItem 列不会计算没有商品类型 6000 的订单。

OrdersWithoutItem 的派生方式类似。

select 
    O.OrderDate,
    count(case when Typ6000.OrderID is not null then O.ID else NULL end) as OrdersWithItem,
    count(case when Typ6000.OrderID is null then O.ID else NULL end) as OrdersWithoutItem
    
from #Orders O
     
     left join
     (
      select distinct OrderId
      from #OrderItems OI
      where OI.ItemTypeID=6000
     )  Typ6000
     on Typ6000.OrderId=O.ID

group by O.OrderDate

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:

您可以在 COUNT 等聚合函数中使用逻辑

SELECT
  OrderDate
, COUNT(DISTINCT CASE WHEN ItemTypeID = 6000 THEN Orders.ID END) AS OrdersWithItem 
, COUNT(DISTINCT Orders.ID) - COUNT(DISTINCT CASE WHEN ItemTypeID = 6000 THEN Orders.ID END) AS OrdersWithoutItem
FROM Orders
JOIN OrderItems
  ON Orders.ID = OrderItems.OrderID 
GROUP BY OrderDate
ORDER BY OrderDate
OrderDate OrdersWithItem OrdersWithoutItem
2021-12-01 1 1
2021-12-02 0 1
2021-12-03 1 0

dbfiddle here

上的演示

【讨论】:

  • 这些都不正确,您的示例中应该总共有 4 个订单。您需要先按Order.Id 分组。它也可能比有条件的DISTINCT 更有效(优化器不会将其识别为已排序)。例如看这个小提琴dbfiddle.uk/…
  • @Charlieface 好吧,我误会了。修复。现在好点了吗?
  • 最初我是在计算 OrdersWithoutItem 非 6000 的订单。而现在,它不计算具有 6000 的项目。
  • 现在看起来,但正如我所说,分组两次可能更有效,因为(给定正确的索引)将没有排序。优化器无法将CASE 识别为按排序顺序,因此需要另一个排序
【解决方案3】:

(类似于 xQbert 的回答)我通常使用 CROSS APPLY 来计算一个中间值,然后我可以用它来提供以后的逻辑 - 在这种情况下,一个标志指示订单是否具有所需的项目。

试试:

SELECT
    O.OrderDate,
    OrdersWithItem = COUNT(CASE WHEN X.HasItem = 1 THEN 1 END),
    OrdersWithoutItem = COUNT(CASE WHEN X.HasItem = 0 THEN 1 END)
FROM Orders O
CROSS APPLY (
    SELECT HasItem = CASE WHEN EXISTS(
        SELECT * FROM OrderItems OI WHERE OI.OrderID = O.ID AND OI.ItemTypeID = 6000
        ) THEN 1 ELSE 0 END
) X
GROUP BY O.OrderDate
ORDER BY O.OrderDate

请注意,上述“COUNT(CASE...)”样式中的“THEN 1”中的“1”是任意的。它只需要与隐含的“ELSE NULL”情况区分开来。

【讨论】:

    【解决方案4】:

    已测试:DBFiddle.uk Example

    我的理论是,我们可以使用外部应用来获取具有所需商品的订单商品的前 1 行。计算那些,然后简单地从总计数中减去那个计数来得到那些没有的。 当订单没有相关商品时,我们使用 coalesce 处理来自外部应用的 NULL 结果。

    您确实需要添加到您的测试数据中,因为您没有达到足够的测试用例来了解解决方案是否能满足您的所有需求。

    SELECT O.OrderDate
         , count(Z.hasItem)  OrdersWithItem
         , count(*)-count(Z.HasItem) as OrdersWithoutItems
    FROM Orders O
    OUTER APPLY (SELECT TOP 1 1 as hasItem
                 FROM OrderItems OI
                 WHERE OI.ItemTypeID=6000 
                   AND O.ID = OI.OrderID
                 ORDER BY OI.ID ) z
    GROUP BY O.OrderDate
    

    【讨论】:

    • 将 hasDate 重命名为 hasItem,这样更有意义...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 2021-09-23
    • 1970-01-01
    • 2022-10-04
    • 2012-04-17
    • 2019-08-29
    • 2013-02-16
    相关资源
    最近更新 更多