【问题标题】:UNION & ORDER two tables inside Common Table Expression公用表表达式中的 UNION & ORDER 两个表
【发布时间】:2019-01-15 09:40:05
【问题描述】:

我在 SQL 存储过程中有一个 CTE,它是来自两个数据库的 UNIONing 值 - 这些值是客户编号和该客户的最后订购日期。

这里是原始 SQL -

;WITH CTE_last_order_date AS
(
SELECT c1.customer ,MAX(s2.dt_created) AS last_order_date
FROM customers c1 WITH (NOLOCK)

LEFT JOIN archive_orders s2 WITH (NOLOCK)
ON c1.customer = s2.customer

GROUP BY c1.customer

UNION ALL

SELECT c1.customer ,MAX(s1.dt_created) AS last_order_date
FROM customers c1 WITH (NOLOCK)

LEFT JOIN orders s1 WITH (NOLOCK)
ON c1.customer = s1.customer

GROUP BY c1.customer
)

示例结果:

customer,    last_order_date
CF122595,    2011-11-15 15:30:22.000
CF122595,    2016-08-15 10:01:51.230

(2 row(s) affected)

这显然不适用于UNION distinct records 规则,因为日期值不匹配,这意味着 SQL 从 两个 表中返回了最大值(即最终记录集不是不同的)

为了解决这个问题,我尝试了从 this question 借来的另一种方法并实现了分组:

;WITH CTE_last_order_date AS
(
SELECT max(last_order_date) as 'last_order_date', customer
FROM (
SELECT distinct cust.customer, max(s2.dt_created) AS last_order_date, '2' AS 'group'
FROM customers c1 WITH (NOLOCK)

LEFT JOIN archive_orders s2 WITH (NOLOCK)
ON c1.customer = s2.customer

GROUP BY c1.customer

UNION 

SELECT distinct c1.customer, max(sord.dt_created) AS last_order_date, '1' AS 'group'
FROM customers c1 WITH (NOLOCK)

LEFT JOIN orders s1 WITH (NOLOCK)
ON cust.customer = sord.customer

GROUP BY
   c1.customer
   ) AS t
GROUP  BY customer
ORDER  BY MIN('group'), customer
)

示例结果:

customer,    last_order_date
CF122595,    2016-08-15 10:01:51.230

(1 row(s) affected)

这有一个工作正常的区别(哈哈),直到进入阻止ORDER BY 在公用表表达式中的规则,这是选择最低组所必需的(这意味着实时订单(第 1 组) ,其日期需要优先于存档(第 2 组))。

The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.

感谢所有帮助或想法。

【问题讨论】:

  • 您必须在 CTE 之外订购,除非您使用的是 SELECT TOP ,否则需要订购。您可以在 CTE 之外订购。那么你需要什么订单?你想在这里返回什么?你能给出示例数据和结果吗?你是说你想要两张桌子的最大值吗?如果是合并子查询中的两个表,则取子查询的最大值。
  • 添加了更多细节,包括结果。

标签: sql-server sql-server-2008-r2 common-table-expression


【解决方案1】:

与其先分组,然后合并,然后再分组,为什么不合并订单表并从那里开始工作:

SELECT c1.customer ,MAX(s2.dt_created) AS last_order_date
FROM customers c1
INNER JOIN (select customer, dt_created from archive_orders
union all select customer, dt_created from orders) s2
ON c1.customer = s2.customer
GROUP BY c1.customer

请记住,在 SQL 中,您的工作是告诉系统您想要什么,而不是要遵循哪些步骤/程序来获得这些结果。从逻辑上讲,上面描述了我们想要什么 - 我们想要每个客户订单的最后一个订单日期,并且我们不在乎这是存档订单还是未存档订单。

由于无论如何我们将在GROUP BY 行为期间将订单信息减少到单行(每个客户),我们不需要需要UNION 来删除重复项所以我切换到UNION ALL

(我承认,此时我真的看不出 ORDER BY 应该添加什么,所以我没有尝试将它包含在此处。如果这将进入 CTE,然后反映事实上,CTE,就像表和视图一样,没有固有顺序。唯一影响结果行顺序的ORDER BY子句是应用于最外层/最后一个SELECT


orders 优先于 archived_orders

;With CTE1 as (
    SELECT c1.customer,group,MAX(s2.dt_created) as MaxInGroup
    FROM customers c1
    INNER JOIN (select customer, dt_created,2 as group from archive_orders
    union all select customer, dt_created,1 from orders) s2
    ON c1.customer = s2.customer
    GROUP BY c1.customer,group
), CTE2 as (
    SELECT *,ROW_NUMBER() OVER (PARTITION BY customer ORDER BY group) as rn
    from CTE2
)
select * from CTE2 where rn = 1

【讨论】:

  • 非常好的观点,并证明了为什么对这些事情有一双新的眼睛总是好的。这是试图根据一些计划外的规范更改来修改第三方所做的工作,而我试图支持已经(看似)过于复杂的脚本。我也忘了KISS。 :D
  • @Optimaximal - 您可能希望暂时恢复接受。您的编辑清楚地表明,任何订单都应优先于已归档的订单,而此查询还没有。正在努力。
  • @Optimmaximal - 参见底部的编辑,也实现了优先级。
  • 虽然我感谢您添加的概念,但我认为没有必要。由于使用了分组方法,因此需要将实时订单优先于存档订单。存档订单只是所有超过 3 年的订单,它们不能同时存在于两个表中 - 如果所有数据都汇总在一起,那么简单的 MAX() 命令就可以了。保持你的滴答声! :)
【解决方案2】:

另一种方法是仅从我们没有当前客户的存档表中获取客户。比如:

WITH CurrentLastOrders(customer, last_order_date) AS    -- Get current last orders
(
    SELECT o.customer, max(o.dt_created) AS last_order_date
    FROM orders s WITH (NOLOCK) ON c.customer = o.customer
    GROUP BY o.customer
),
ArchiveLastOrders(customer, last_order_date) AS -- Get archived last orders where customer does not have a current order
(
    SELECT o.customer, max(o.dt_created) AS last_order_date
    FROM archive_orders o WITH (NOLOCK)
    WHERE NOT EXISTS ( SELECT *
                        FROM CurrentLastOrders lo
                        WHERE o.customer = lo.customer)
    GROUP BY o.customer
),
AllLastOrders(customer, last_order_date) AS -- All customers with orders
(
    SELECT customer, last_order_date
    FROM CurrentLastOrders
    UNION ALL
    SELECT customer, last_order_date
    FROM ArchiveLastOrders
)
AllLastOrdersPlusCustomersWithNoOrders(customer, last_order_date) AS    -- All customerswith latest order if they have one
(
    SELECT customer, last_order_date
    FROM AllLastOrders
    UNION ALL
    SELECT customer, null
    FROM customers c WITH (NOLOCK)
    WHERE NOT EXISTS ( SELECT *
                        FROM AllLastOrders lo
                        WHERE c.customer = lo.customer)
)

【讨论】:

    【解决方案3】:

    我不会尝试嵌套 SQL 来获得不同的结果集,这与在两个联合查询中按客户分组的逻辑相同。 如果你想要一个不同的有序集,你可以在 CTE 之外进行

    怎么样:

    ;WITH CTE_last_order_date AS
    (
       SELECT c1.customer ,s2.dt_created AS last_order_date, '2' AS 'group'
       FROM customers c1 WITH (NOLOCK)
       LEFT JOIN archive_orders s2 WITH (NOLOCK) ON c1.customer = s2.customer
    
       UNION ALL
    
       SELECT c1.customer ,s1.dt_created AS last_order_date, '1' AS 'group'
       FROM customers c1 WITH (NOLOCK)
       LEFT JOIN orders s1 WITH (NOLOCK) ON c1.customer = s1.customer
    
    )
    SELECT customer, MAX(last_order_date)
    FROM CTE_last_order_date
    GROUP BY customer 
    ORDER BY MIN('group'), customer
    

    【讨论】:

    • 正在创建 CTE 以保存最后订单日期,以便在存储过程中将它们作为稍后 INSERT 命令的一部分进行引用,因此需要对其进行预排序。跨度>
    【解决方案4】:

    如果您将所有可能的行合并在一起,然后计算 row_number,按客户分区并按“组”排序,然后按 last_order_date 降序排列,然后您可以选择所有 row=1 来为每个客户提供“前 1 个”

    ;WITH CTE_last_order_date AS
    (
    SELECT max(last_order_date) as 'last_order_date', customer
    FROM (
    SELECT distinct cust.customer, max(s2.dt_created) AS last_order_date, '2' AS 'group'
    FROM customers c1 WITH (NOLOCK)
    
    LEFT JOIN archive_orders s2 WITH (NOLOCK)
    ON c1.customer = s2.customer
    
    GROUP BY c1.customer
    
    UNION 
    
    SELECT distinct c1.customer, max(sord.dt_created) AS last_order_date, '1' AS 'group'
    FROM customers c1 WITH (NOLOCK)
    
    LEFT JOIN orders s1 WITH (NOLOCK)
    ON cust.customer = sord.customer
    
    GROUP BY
       c1.customer
       ) AS t
    GROUP  BY customer
    
    )
    ,   --row_number below is 'per customer' and can be used to make rn=1 the top 1 for each customerid
    ROWN AS (SELECT Customer,last_order_date,[group], row_number() OVER(partition by customer order by [group] ASC, sord.dt_created DESC) AS RN)
    SELECT * FROM Rown WHERE Rown.rn = 1
    

    【讨论】:

      猜你喜欢
      • 2013-07-29
      • 1970-01-01
      • 2021-02-22
      • 1970-01-01
      • 2012-11-12
      • 2020-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多