【问题标题】:Best way to loop through all possible combinations across multiple tables?遍历多个表中所有可能组合的最佳方法?
【发布时间】:2013-12-14 16:09:00
【问题描述】:

我有两个简单的表格:

OrderStatusOrderStage 分别有一个主键整数列 OrderStatusIDOrderStageID。每个表返回大约 5 行。

我要做的是创建一个 SELECT 语句来计算每个 OrderStatusID 和 OrderStageID 组合的订单数。这是一个例子:

SELECT 
 COUNT(OrderID)
FROM
 Order
WHERE
 OrderStatusID = '1' and OrderStageID = '1'


SELECT 
 COUNT(OrderID)
FROM
 Order
WHERE
 OrderStatusID = '1' and OrderStageID = '2'

......
SELECT 
 COUNT(OrderID)
FROM
 Order
WHERE
 OrderStatusID = '4' and OrderStageID = '5'

我不得不写出 5x5 的 SQL 语句来计算每个可能组合中的订单数,这让我变得很乏味。如果有人向OrderStatusOrderStage 表添加更多行,那么我将不得不继续重新访问此代码以添加新组合。

此数据的最终呈现方式将显示在网页上的“树”中,这与 Outlook 如何在邮件面板中显示收件箱中的电子邮件计数、已发送邮件、已删除邮件等非常相似。我将 ColdFusion 用于我的网页。如果我使用 GROUP BY 语句来获取数据,那么我将如何在 ColdFusion 中分别显示每个结果行? <cfquery group=""> 会起作用吗?

对于给定的 OrderStatusID 和 OrderStageID 组合,如果 Order 表中没有行,它应该返回 0 或 NULL。这就是我为每个可能的组合使用单独的 SELECT 语句的原因。

【问题讨论】:

  • 嗯,为什么不使用 group by 子句

标签: sql sql-server coldfusion sql-server-2012


【解决方案1】:

我确信有更巧妙的方法可以为 SQL Server 2012 编写它,但基本上是通过CROSS JOIN 来获取所有可能的状态和阶段组合(即 25 行)。 然后orders进行外部连接以获取每个组合的计数:

SQL Fiddle

** 通常最好避免使用order 之类的关键字作为表名

  SELECT osg.OrderStageID, ost.OrderStatusID, COUNT(o.OrderID) AS TotalOrders
  FROM   orderStage osg CROSS JOIN orderStatus ost
             LEFT JOIN [order] o ON o.OrderStageID = osg.OrderStageID
                    AND o.OrderStatusID = ost.OrderStatusID
  GROUP BY osg.OrderStageID, ost.OrderStatusID

注意:请务必查看 CROSS JOIN 的运作方式。

【讨论】:

  • 像老板一样做,非常感谢。现在我必须弄清楚如何在页面上显示此信息,以便正确的 CaseStatus/CaseStage 文本旁边有正确的计数,例如开放 (60)、已派遣 (10)、已关闭 (20) 等
【解决方案2】:

如果你想在不存在的地方显示0,你可以创建一个包含所有组合的驱动程序表,并加入到该表中:

SELECT driver.*,SUM(CASE WHEN o.OrderStatusID IS NOT NULL THEN 1 ELSE 0 END)AS OrderCount
FROM (SELECT *
      FROM  (SELECT DISTINCT OrderStatusID FROM Orders) a
           ,(SELECT DISTINCT OrderStageID FROM Orders) b
      ) driver
LEFT JOIN Orders o
 ON driver.OrderStatusID  = o.OrderStatusID
 AND driver.OrderStageID = o.OrderStageID
GROUP BY driver.OrderStatusID
       , driver.OrderStageID

演示:SQL Fiddle

【讨论】:

  • 如果所有组合都不在订单表中,则不起作用。因此,驱动程序表的想法。我使用了一个计数表,因为这可能很快就会变大。但是,最好使用固定表。
  • 确实,两个独立表之间的交叉连接更有意义,第一次阅读这个问题我只看到了订单表引用。
【解决方案3】:

在回答这些类型的问题时,我喜欢在 tempdb 中创建一个示例表。

--
--  Create test table
--

-- Just toss away code
USE tempdb;
GO

-- Create a simple table
CREATE TABLE my_orders
(
  my_orderstatus_id int,
  my_orderstage_id int
);
GO

如果您还没有查看 Jeff Moden 的计数表,那么您应该查看。它们是一种以关系代数方式进行循环的快速方法。我正在加载组合 (1..5)(1..5) 次和 (6..10)(6..10) 两次。 go 后面的数字表示运行 TSQL 的次数。

--
--  Load test table
--

-- Create some data (1-5) x 4 counts
;
WITH cteTally1 (my_number1) AS 
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY a.name) AS my_number
    FROM 
        sys.objects a 
    CROSS JOIN 
        sys.objects b
),
cteTally2 (my_number2) AS 
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY a.name) AS my_number
    FROM 
        sys.objects a 
    CROSS JOIN 
        sys.objects b
)
INSERT INTO my_orders 
SELECT my_number1, my_number2 
FROM cteTally1 T1 CROSS JOIN cteTally2 T2
WHERE 
    (T1.my_number1 > 0 AND T1.my_number1 <= 5 ) AND 
    (T2.my_number2 > 0 AND T2.my_number2 <= 5 );
GO 4


-- Create some data (6-10) x 2 counts
;
WITH cteTally1 (my_number1) AS 
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY a.name) AS my_number
    FROM 
        sys.objects a 
    CROSS JOIN 
        sys.objects b
),
cteTally2 (my_number2) AS 
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY a.name) AS my_number
    FROM 
        sys.objects a 
    CROSS JOIN 
        sys.objects b
)
INSERT INTO my_orders 
SELECT my_number1, my_number2 
FROM cteTally1 T1 CROSS JOIN cteTally2 T2
WHERE 
    (T1.my_number1 >= 6 AND T1.my_number1 <= 10 ) AND 
    (T2.my_number2 >= 6 AND T2.my_number2 <= 10 );
GO 2

-- Show the data
SELECT * FROM my_orders;

最后但并非最不重要的一点是,一个简单的分组依据将为您计算每个组合的计数。

--
-- Simple group by will suffice
--

SELECT 
    my_orderstatus_id, 
    my_orderstage_id,
    COUNT(*) as number  
FROM my_orders
GROUP BY 
    my_orderstatus_id, 
    my_orderstage_id;
GO

输出的快速屏幕截图:

这应该可以解决您的特定问题。但如果我确实弄错了要求,请回帖。

您现在更改了初始要求。这就是我们在管理中所说的范围更改

如果您知道组合始终为 10 x 10,请创建一个矩阵表或公用表表达式,这样无论匹配如何,您都将始终拥有该组合。

将简单的 group by 扩展到数据的左连接并相应地分组/order by。

--
-- Report Query
--

-- For missing records
;
WITH cteTally1 (my_number1) AS 
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY a.name) AS my_number
    FROM 
        sys.objects a 
    CROSS JOIN 
        sys.objects b
),
cteTally2 (my_number2) AS 
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY a.name) AS my_number
    FROM 
        sys.objects a 
    CROSS JOIN 
        sys.objects b
),
cteMissingCombos (my_orderstatus_id, my_orderstage_id) AS 
(
    SELECT T1.my_number1, T2.my_number2
    FROM cteTally1 T1 CROSS JOIN cteTally2 T2
    WHERE T1.my_number1 < 11 AND T2.my_number2 < 11
)

-- Return null if no records, use coalesce if you want 0
SELECT 
    C.my_orderstatus_id, 
    C.my_orderstage_id,
    COUNT(O.my_orderstatus_id) as number  
FROM 
    cteMissingCombos as C LEFT JOIN my_orders as O
ON 
    C.my_orderstatus_id = O.my_orderstatus_id AND 
    C.my_orderstage_id = O.my_orderstage_id
GROUP BY 
    C.my_orderstatus_id,
    C.my_orderstage_id
ORDER BY 
    C.my_orderstatus_id,
    C.my_orderstage_id
GO

下面的输出表明我们没有 1 x 6 到 10 的组合。因此

【讨论】:

  • 如果 Order 表中不存在该组合,它会返回零计数吗?
  • 这比它需要的复杂得多。
  • 我错过了阅读要求 - 我虽然你没有表 [OrderStatus] 和 [OrderStage],但只有表 [Order]。如果在 Order 上允许在这些基表上进行删除而没有级联,则以下查询将不起作用。除此之外,驱动程序表的交叉连接是要走的路!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-03
  • 2012-05-16
  • 1970-01-01
  • 2014-07-14
  • 1970-01-01
  • 2018-11-01
相关资源
最近更新 更多