【问题标题】:Postgres: left join with order by and limit 1Postgres:左连接,order by和limit 1
【发布时间】:2014-03-17 06:01:52
【问题描述】:

我有这种情况:

Table1 has a list of companies.
Table2 has a list of addresses.
Table3 is a N relationship of Table1 and Table2, with fields 'begin' and 'end'.

由于公司可能会随着时间的推移而移动,因此它们之间的 LEFT JOIN 会导致每个公司有多个记录。

begin 和 end 字段永远不会为 NULL。找到最新地址的解决方案是使用ORDER BY being DESC,删除旧地址是LIMIT 1。

如果查询只能带来 1 家公司,那就可以了。但我需要一个将所有 Table1 记录与它们当前的 Table2 地址连接起来的查询。因此,必须在 LEFT JOIN 的 ON 子句中删除过时数据(AFAIK)。

知道如何建立条款以不创建重复的 Table1 公司并带来最新地址吗?

【问题讨论】:

    标签: postgresql sql-order-by left-join foreign-key-relationship duplicate-removal


    【解决方案1】:

    在连接条件中使用带有 max() 函数的依赖子查询。
    类似这个例子:

    SELECT *
    FROM companies c
    LEFT JOIN relationship r
    ON c.company_id = r.company_id
       AND r."begin" = (
            SELECT max("begin")
            FROM relationship r1
            WHERE c.company_id = r1.company_id
         )
    INNER JOIN addresses a
    ON a.address_id = r.address_id 
    

    演示:http://sqlfiddle.com/#!15/f80c6/2

    【讨论】:

    • 在 sqlfiddle 上很好 - 我直到现在才看到 jsfiddle,但 sqlfiddle 似乎超级有用!
    • 如果 r.begin 在某些记录中可以相等,它似乎不起作用
    【解决方案2】:

    我设法使用 Windows 功能解决了它:

    WITH ranked_relationship AS(
        SELECT
            *
            ,row_number() OVER (PARTITION BY fk_company ORDER BY dt_start DESC) as dt_last_addr
        FROM relationship
    )
    
    SELECT
        company.*
        address.*,
        dt_last_addr as dt_relationship
    FROM
        company
        LEFT JOIN ranked_relationship as relationship
                ON relationship.fk_company = company.pk_company AND dt_last_addr = 1
        LEFT JOIN address ON address.pk_address = relationship.fk_address
    

    row_number() 在基于 fk_company 的每个窗口内为每个记录创建一个 int 计数器。对于每个窗口,日期最新的记录排在第一位,排名为 1,然后dt_last_addr = 1 确保每个fk_company 只发生一次 JOIN,地址最新的记录。

    窗口函数非常强大,很少有人使用它们,它们避免了许多复杂的连接和子查询!

    【讨论】:

      【解决方案3】:

      从 PostgreSQL 9.3 开始,JOIN LATERAL (https://www.postgresql.org/docs/9.4/queries-table-expressions.html) 允许进行子查询加入,因此它以优雅的方式解决了您的问题:

      SELECT * FROM companies c
      JOIN LATERAL (
          SELECT * FROM relationship r
          WHERE c.company_id = r.company_id
          ORDER BY r."begin" DESC LIMIT 1
      ) r ON TRUE
      JOIN addresses a ON a.address_id = r.address_id
      

      这种方法的缺点是LATERAL里面的表的索引在外面不起作用。

      【讨论】:

      • 您能否详细说明您的评论“这种方法的缺点是 LATERAL 内部表的索引在外部不起作用。”在您的示例中,如果r.company_id 上有索引,这是否意味着索引 将 用于子查询,但如果在外部查询中的 WHERE 子句?
      • @EricDauenhauer 是的,确实如此。
      猜你喜欢
      • 1970-01-01
      • 2023-03-16
      • 2012-07-10
      • 1970-01-01
      • 2013-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-09
      相关资源
      最近更新 更多