SQL Server:从多个客户的多个订单中选择最大的订单总数,并且每个订单上有多个项目

Posted

技术标签:

【中文标题】SQL Server:从多个客户的多个订单中选择最大的订单总数,并且每个订单上有多个项目【英文标题】:SQL Server: select the largest order total from multiple customers with multiple orders, and there are multiple items on each order 【发布时间】:2017-07-08 02:59:10 【问题描述】:

我真的被一个问题困住了,需要一点帮助。这是问题陈述:

“编写将显示所有客户、所有订单总数、订单数量、每个订单的平均总数、每个订单的平均商品数量(带小数点)、最大订单总数和每个客户的最小订单总数。即使客户没有下订单,也要显示每个客户。"

这些是表格:

the lovely tables

我已经走到这一步了,我对最大订单总数感到困惑。我正在考虑最高和最低订单总数的子查询,但我无法使其工作。

SELECT
     TC.intCustomerID
    ,TC.strLastName + ',' + ' ' + TC.strFirstName AS strCustomerName
    ,ISNULL(SUM( TCOI.intQuantity * TI.monPrice), 0) AS monOrderTotals
    ,COUNT(DISTINCT TCO.intOrderIndex) AS intNumberOfOrders
    ,ISNULL(SUM(TCOI.intQuantity * TI.monPrice) / COUNT(DISTINCT TCO.intOrderIndex), 0) AS monAverageOrderTotals
    ,(SELECT MAX(TCOI.intQuantity * TI.monPrice)
      FROM TItems AS TI, TCustomerOrderItems AS TCOI
      WHERE TI.intItemID = TCOI.intItemID 
      -- Cross-query join with two columns
      -- AND TC.intCustomerID = TCOI.intCustomerID 
      -- AND TCO.intOrderIndex = TCOI.intOrderIndex 
      ----GROUP BY 
      -- TCOI.intCustomerID 
      --,TCOI.intOrderIndex 
     ) AS monMostExpensiveOrder
FROM
    TCustomers AS TC
LEFT OUTER JOIN 
    TCustomerOrders AS TCO ON (TC.intCustomerID = TCO.intCustomerID)
LEFT OUTER JOIN 
    TCustomerOrderItems AS TCOI ON (TCO.intOrderIndex = TCOI.intOrderIndex)
LEFT OUTER JOIN 
    TItems AS TI ON (TCOI.intItemID = TI.intItemID)
GROUP BY
     TC.intCustomerID
    ,TC.strLastName
    ,TC.strFirstName

任何见解将不胜感激。

【问题讨论】:

关于您的第一个查询:Bad habits to kick : using old-style JOINs 当然可以,但有时我会这样写,因为它们在我的大脑中更有意义,但当我弄清楚事情时,我会回去正确地写它们。感谢您的反馈。 当商品价格发生变化时你会怎么做? “当前价格”和“订购时的价格”不是一回事,因为一个可以更改,另一个不能。所以你不能将价格标准化到项目表中。您可能希望它出现在 OrderItem 表中。 这个比较自以为是,不过Bad Habits to Kick : Using AS instead of = for column aliases - Aaron Bertrand 我不太担心当商品价格发生变化时该怎么办。这个模式被定义为问题陈述的一部分,而不是我自己想出的。我必须照原样处理这些表格。 【参考方案1】:

对我来说,在处理派生表(从子查询中选择)时,使用 common table expression 可以使代码更易于阅读和编写。

我认为这应该涵盖您正在尝试做的事情,但我不确定您希望以哪种方式计算每个订单的平均商品数量(按不同商品的数量或商品的数量):

with cte as (
  select 
      tc.intCustomerId
    , tc.strFirstName
    , tc.strLastName
    , tcoi.intOrderIndex
    , TotalPrice = isnull(sum(tcoi.intQuantity * ti.monPrice), 0 )
    , ItemCount  = count(*)
    , TotalItemQuantity = sum(tcoi.intQuantity)
  from TCustomers tc
    left join tCustomerOrderItems as tcoi
      on tc.intCustomerId = tcoi.intCustomerId
    left join tItems as ti
      on ti.intItemID = tcoi.intItemID 
)
select 
    intCustomerId
  , Name = isnull(strLastName+', ') + isnull(strFirstName,'')
  , countOrders   = count(intOrderIndex)
  , sumTotalPrice = sum(TotalPrice)
  , minTotalPrice = min(TotalPrice)
  , maxTotalPrice = max(TotalPrice)
  , avgTotalPrice = avg(TotalPrice)
  , avgItemCount  = (sum(ItemCount)+.0)/nullif(count(intOrderIndex),0)
  , avgItemQuant  = (sum(TotalItemQuantity)+.0)/nullif(count(intOrderIndex),0)
from cte
group by 
   intCustomerId
 , strFirstName
 , strLastName

要取出cte 部分,您只需将查询移至from

select 
    intCustomerId
  , Name = isnull(strLastName+', ') + isnull(strFirstName,'')
  , countOrders   = count(intOrderIndex)
  , sumTotalPrice = sum(TotalPrice)
  , minTotalPrice = min(TotalPrice)
  , maxTotalPrice = max(TotalPrice)
  , avgTotalPrice = avg(TotalPrice)
  , avgItemCount  = (sum(ItemCount)+.0)/nullif(count(intOrderIndex),0)
  , avgItemQuant  = (sum(TotalItemQuantity)+.0)/nullif(count(intOrderIndex),0)
from (
  select 
      tc.intCustomerId
    , tc.strFirstName
    , tc.strLastName
    , tcoi.intOrderIndex
    , TotalPrice = isnull(sum(tcoi.intQuantity * ti.monPrice), 0 )
    , ItemCount  = count(*)
    , TotalItemQuantity = sum(tcoi.intQuantity)
  from TCustomers tc
    left join tCustomerOrderItems as tcoi
      on tc.intCustomerId = tcoi.intCustomerId
    left join tItems as ti
      on ti.intItemID = tcoi.intItemID 
  ) as cte
group by 
   intCustomerId
 , strFirstName
 , strLastName

【讨论】:

哈哈...间隔 15 秒发布。两者都有 CTE。我认为对这些语法宝石的看法正在发生变化! @Mitch 让我们期待吧! 我还没有参加 CTE,所以我不知道我的导师会接受它作为解决方案的一部分,但感谢您给我更多的研究资料!【参考方案2】:

您首先需要计算每个订单和每个客户的总数。

我会说该模式在不存储订单总数方面存在缺陷,因为商品价格可能会发生变化,而TCustomerOrders 可能包括历史订单。也不建议为表和列名添加前缀。

WITH CustomerOrders AS
(
    SELECT 
        oi.intCustomerID as CustomerID,
        oi.intOrderIndex as OrderID,
        SUM(oi.intQuantity * i.monPrice) as SalesAmount,
        COUNT(DISTINCT oi.intItemID) as DistinctItemCount,
        SUM(oi.intQuantity) as ItemCount
    FROM TCustomerOrderItems as oi
    INNER JOIN TItems as i on oi.intItemID = i.intItemID
    GROUP BY oi.intCustomerID, oi.intOrderIndex
),
CustomerSales AS
(
    SELECT
        co.CustomerID,
        SUM(co.SalesAmount) as TotalSalesAmount,
        COUNT(*) as OrderCount,
        AVG(co.SalesAmount) as AvgOrderSalesAmount,
        -- If item count should be distinct SKU's, use DistinctItemCount
        -- Cast to numeric or another non-integer type to get fractional averages
        AVG(CAST(co.ItemCount as numeric(14,4)) as AvgItemCount,
        MIN(co.SalesAmount) as SmallestOrderSalesAmount,
        MAX(co.SalesAmount) as LargestOrderSalesAmount
    FROM CustomerOrders co
    GROUP BY co.CustomerID
)
SELECT
    c.intCustomerID as CustomerID,
    c.strFirstName as CustomerFirstName,
    c.strLastName as CustomerLastName,
    COALESCE(cs.TotalSalesAmount, 0) as TotalSalesAmount,
    COALESCE(cs.OrderCount, 0) as OrderCount,
    COALESCE(cs.AvgOrderSalesAmount, 0) as AvgOrderSalesAmount,
    COALESCE(cs.AvgItemCount, 0) as AvgItemCount,
    COALESCE(cs.SmallestOrderSalesAmount, 0) as SmallestOrderSalesAmount,
    COALESCE(cs.LargestOrderSalesAmount, 0) as LargestOrderSalesAmount
FROM TCustomers c
LEFT OUTER JOIN CustomerSales cs on c.intCustomerID = cs.CustomerID;

【讨论】:

就像架构是什么一样,我没有(现在)不给表和列添加前缀的选项。我知道这是一个备受争议的话题......

以上是关于SQL Server:从多个客户的多个订单中选择最大的订单总数,并且每个订单上有多个项目的主要内容,如果未能解决你的问题,请参考以下文章

SQL 语句帮助 - 为每个客户选择最新订单

SQL Server - 从子-父-子中选择并返回多个结果集

SQL语句帮助 - 为每个客户选择最新订单

选择 SQL Server 中排名第二的行

sql插入行,但从该行返回一个项目

Oracle SQL:将选择从一个表插入另一个表,其中用户有多个带有订单号的条目