【问题标题】:How to use results of subquery in a parent query case statement如何在父查询案例语句中使用子查询的结果
【发布时间】:2021-04-03 02:25:21
【问题描述】:

我有以下查询返回预期结果:

SELECT
    locations.*,
    (
        SELECT
            id
        FROM
            hauls
        WHERE
            haul_type_id = 1
            AND location_id = locations.id
        ORDER BY
            created_at DESC
        LIMIT 1) AS last_delivery_id,
    (
        SELECT
            id
        FROM
            hauls
        WHERE
            haul_type_id = 2
            AND location_id = locations.id
        ORDER BY
            created_at DESC
        LIMIT 1) AS last_pickup_id
FROM
    locations

我想在父查询的 case 语句中使用子查询的结果(last_delivery_id、last_pickup_id),但出现错误:

错误:未知列“last_delivery_id”

SELECT
    locations.*, 
    case when last_delivery_id = 1 then 'pending' when last_delivery_id = 2 then 'active' end as status,
    (
        SELECT
            id
        FROM
            hauls
        WHERE
            haul_type_id = 1
            AND location_id = locations.id
        ORDER BY
            created_at DESC
        LIMIT 1) AS last_delivery_id,
    (
        SELECT
            id
        FROM
            hauls
        WHERE
            haul_type_id = 2
            AND location_id = locations.id
        ORDER BY
            created_at DESC
        LIMIT 1) AS last_pickup_id
FROM
    locations

【问题讨论】:

  • 将您的第一个查询用作子查询,然后您可以选择并使用 2 个派生列。

标签: mysql sql datetime subquery lateral-join


【解决方案1】:

您可以使用子查询或 CTE:

SELECT lh.*, . . .   -- any expressions you want
       (case when last_delivery_id = 1 then 'pending'
             when last_delivery_id = 2 then 'active'
        end) as status
FROM (SELECT l.*,
             (SELECT id
              FROM hauls h
              WHERE h.haul_type_id = 1 AND
                    h.location_id = l.id
              ORDER BY h.h.created_at DESC
              LIMIT 1
             ) AS last_delivery_id,
             (SELECT id
              FROM hauls h
              WHERE h.haul_type_id = 2 AND
                    h.location_id = l.id
              ORDER BY h.created_at DESC
              LIMIT 1
             ) AS last_pickup_id
      FROM locations l
     ) lh

请注意表别名的使用,这使查询更易于编写和阅读。也就是说,如果您只想要字符串版本,我不确定您是否真的需要 delivery_id:

      SELECT l.*,
             (SELECT (case when h.id = 1 then 'pending'
                           when h.id = 2 then 'active'
                      end)
              FROM hauls h
              WHERE h.haul_type_id = 1 AND
                    h.location_id = l.id
              ORDER BY h.created_at DESC
              LIMIT 1
             ) AS status
      FROM locations l

【讨论】:

  • 谢谢。这是我最终使用的解决方案。
【解决方案2】:

考虑使用 CTE 和窗口函数重写内联子查询。这避免了使用LIMIT 子句,如果没有以最佳方式运行,这些子句可以有performance issues。这需要支持 CTE 和窗口函数的 MySQL 8.0+ 版本:

WITH sub AS (
  SELECT id AS last_delivery_id
       , location_id
       , haul_type_id
       , ROW_NUMBER() OVER (PARTITION BY location_id, haul_type_id
                            ORDER BY created_at DESC) AS rn
  FROM hauls
)

SELECT l.*       -- CONSIDER EXPLICITLY SELECTING COLUMNS FOR PERFORMANCE
     , CASE
          WHEN h1.last_delivery_id = 1 THEN 'pending' 
          WHEN h2.last_delivery_id = 2 THEN 'active' 
       END AS `status`
FROM locations l
LEFT JOIN sub h1
  ON h1.location_id = l.id
 AND h1.haul_type_id = 1
 AND h1.rn = 1
LEFT JOIN sub h2
  ON h2.location_id = l.id
 AND h2.haul_type_id = 2
 AND h2.rn = 1

对于早期版本 (

SELECT l.*       -- CONSIDER EXPLICITLY SELECTING COLUMNS FOR PERFORMANCE 
     , CASE
          WHEN h1.id = 1 THEN 'pending' 
          WHEN h2.id = 2 THEN 'active' 
       END AS `status`
FROM locations l
LEFT JOIN
    (SELECT location_id
          , MAX(CASE WHEN haul_type_id = 1 THEN created_at END) AS h1_max_date
          , MAX(CASE WHEN haul_type_id = 2 THEN created_at END) AS h2_max_date
     FROM hauls
     GROUP BY location_id
    ) h_agg
   ON l.location_id = h_agg.location_id
LEFT JOIN hauls h1
   ON  h1.location_id = h_agg.location_id
   AND h1.haul_type_id = 1
   AND h1.created_at = h_agg.h1_max_date
LEFT JOIN hauls h2
   ON  h2.location_id = h_agg.location_id
   AND h2.haul_type_id = 2
   AND h2.created_at = h_agg.h2_max_date

【讨论】:

  • 谢谢。我最终使用了另一种解决方案,但感谢您指出可能的性能问题。如果遇到性能问题,我会保留此解决方案。
【解决方案3】:

如果您运行的是 MySQL 8.0.14 或更高版本,则可以为此使用横向连接:

select l.*, h1.*, h2.*
    case 
        when last_delivery_id = 1 then 'pending' 
        when last_delivery_id = 2 then 'active' 
    end as status
from locations l
left join lateral (
    select h.id as last_delivery_id
    from hauls h
    where h.haul_type_id = 1 and h.location_id = l.id
    order by h.created_at desc limit 1
) h1 on true
left join lateral (
    select h.id as last_pickup_id
    from hauls h
    where h.haul_type_id = 2 and h.location_id = l.id
    order by h.created_at desc limit 1
) h2 on true

横向连接是一个强大的功能,MySQL 长期以来一直缺少它。例如,上面的语句可以很容易地调整为从每个子查询返回更多列。

【讨论】:

  • 谢谢。我喜欢这个,但不幸的是不能使用 MySQL 8。感谢您的建议
【解决方案4】:

因为 select 部分中的所有列一次全部执行,并且 last_delivery_id 列尚未创建并且您尝试使用它,所以 sql server 返回错误,您可以使用 CTE、视图和 Drive table 来解决它。

【讨论】:

    猜你喜欢
    • 2017-07-17
    • 2013-11-21
    • 1970-01-01
    • 2021-08-19
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-26
    相关资源
    最近更新 更多