【问题标题】:How do I get the min/max of columns in table A and values from B where the rows of B are determined by another column in A?如何获取表 A 中列的最小/最大值和 B 中的值,其中 B 的行由 A 中的另一列确定?
【发布时间】:2020-10-08 14:12:57
【问题描述】:

我试图了解如何编写我的查询,以便对于某些列,它返回其他列具有最小值或最大值的值,同时返回这些列的最小值/最大值。

我有两个包含驱动器(开车旅行)和街道地址的 Postgresql 表,我想从中选择数据以将驱动器组合成旅行。例如,从我家到商店是一个驱动器,再回去是另一个驱动器,将两者组合在一起形成一个单程。对于每个这样的分组旅行,我想获取第一个驱动器的开始时间和地址,最终驱动器的结束时间和位置,以及所有驱动器时间和距离的总和。

这是获取我想组合成一次行程的三个驱动器的示例查询:

SELECT
    start_time,
    end_time,
    duration,
    distance,
    start_address.name AS start_address,
    end_address.name AS end_address
FROM drives
    LEFT JOIN addresses start_address ON start_address_id = start_address.id
    LEFT JOIN addresses end_address ON end_address_id = end_address.id
WHERE drives.id=ANY('{10, 11, 12}');

运行此查询可能会产生以下结果:

row# | start_time | end_time   | duration | distance | start_address | end_address
----------------------------------------------------------------------------------
1      14:40:00     14:43:00     3          0.75       Home            The store
2      14:48:00     14:58:00     10         2.25       The store       Post office
3      15:10:00     15:08:00     8          2.00       Post office     Work

现在,我想构建一个查询,该查询应该产生一行代表这三个驱动器的组合行程。我希望该行包含最小开始时间、最大结束时间、持续时间的总和、距离的总和、具有最小开始时间的行的起始地址和具有最长结束时间。

如果我省略地址部分,那很简单:

SELECT
    min(start_time) AS start_time,
    max(end_time) AS end_time,
    sum(duration) AS duration,
    sum(distance) AS distance
FROM drives
WHERE drives.id=ANY('{10, 11, 12}');
row# | start_time | end_time   | duration | distance
----------------------------------------------------
1      14:40:00     15:08:00     21          5.00

但是我如何编写查询以便我也得到地址?我想要这个结果:

row# | start_time | end_time   | duration | distance | start_address | end_address
----------------------------------------------------------------------------------
1      14:40:00     15:08:00     21          5.00      Home            Work

显然我需要将地址部分返回到查询中,但无论我尝试什么,我都无法弄清楚如何正确构造它。坦率地说,我什至不确定我得到了这个问题的标题:/

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    我们可以在主查询中使用窗口函数first_valuelast_value,然后与一级子查询聚合为,

    SELECT
        min(start_time) AS start_time,
        max(end_time) AS end_time,
        sum(duration) AS duration,
        sum(distance) AS distance,
        max(start_address) start_address,
        max(end_address) end_address  
    FROM
    (
        SELECT
            start_time,
            end_time,
            duration,
            distance,
            first_value(start_address.name) over (order by start_time,end_time) start_address,
            last_value(end_address.name) over (order by start_time,end_time rows between unbounded preceding and unbounded following) end_address
        FROM drives
            LEFT JOIN addresses start_address ON start_address_id = start_address.id
            LEFT JOIN addresses end_address ON end_address_id = end_address.id
        WHERE drives.id=ANY('{10, 11, 12}')
    ) c;
    

    【讨论】:

    • 谢谢!我必须在内部选择中添加一个名称(只是在分号前添加了“as foo”)才能使查询成功。它产生了除结束地址之外的所需数据;开始地址和结束地址都取自第一个驱动器(在我的示例中,报告的行程是从 Home -> The store)。
    • 我希望它应该可以工作。 order by 在这里很重要,你能检查一下fiddle 它似乎在哪里工作吗?或者您是否可以通过输入 order by start_time,end_time 来尝试使用主查询,并查看预期的 start_address.nameend_address.name 分别出现在第一行和最后一行的结果。
    • 我已经模拟了完整的一个。请查看db<>fiddle
    • @Emil Fors,多亏了你,我现在意识到 last_value 以不同的方式工作,即 The last_value() window function will pick the last value in the current frame. Without changing any of the frame defaults, this will be the current row. 所以我们要么切换到 first_value 更改 order by start_time,end_time desc 要么我们需要添加window 子句就像我现在所做的那样,通过更新答案来告诉last_value 将完整的结果集视为window frame。现在我坚信它会起作用。再次感谢您的学习。
    猜你喜欢
    • 2017-06-05
    • 2014-04-18
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 2021-06-27
    • 1970-01-01
    相关资源
    最近更新 更多