【问题标题】:How to JOIN a table on an aggregate如何在聚合上加入表
【发布时间】:2021-05-08 01:29:39
【问题描述】:

问题摘要:

我需要根据聚合函数将单行连接到表输出,在本例中是最近的对应记录。关于这个主题的各种其他问题似乎都基于两个表值都是必需的(INNER JOIN 等),但在我的情况下,聚合需要在 LEFT JOIN 表上工作,该表很多时候将是 @987654327 @。

MySQL 5.7; 这里有几个说明性表格:

表格

Core_data:
-----------

create table `core`
(
core_id int(8) unsigned auto_increment primary key,
some_name varchar(80) null,
some_data varchar(80) null,
some_values .... etc. 
)


Linked_data:
------------

create table `linked_data`
(
link_id smallint(6) unsigned auto_increment primary key,
core_id int(8) unsigned
data_date date,
some_linked_data_values varchar(80) null
)

我有一个处理几十个表的查询。从 Core 表中选择 1 行,并从其他数十个表中选择各种 LEFT JOIN 数据。

图示的链接数据表有数据,日期很重要,只返回最近的。

示例数据:

linked_data
------------

link_id | core_id |  data_date  | data_value | ...
-------------------------------------------------
   1    |    2    |  2020-09-03 |  something | ...
   2    |    4    |  2019-07-29 |  whatever  | ...
   3    |    1    |  2017-11-09 |    yews    | ...
   4    |    4    |  2018-04-10 |    socks   | ...

我只想加入 core_id = 4 和最大日期值的行。如何在JOIN 场景中创建它;我无法将MAX 聚合放入JOIN ... ON 条件中。

我当前的 SQL:

我的 SQL 是这样的:

   SELECT ... many columns ..., 
   ld.data_value,
   ld.data_date,
   more.columns ... 
   FROM core
   LEFT JOIN table1 ON core.core_id = table1.core_id
   LEFT JOIN table2 ON core.core_id = table2.core_id
   LEFT JOIN table3 ON core.core_id = table3.core_id
   ... etc ...
   LEFT JOIN linked_data ld ON core.core_id = ld.core_id AND MAX(ld.data_date)
   WHERE ... core_id = value

一个表我只需要一个具有最高列值的结果行(基于数据),linked_data 没有理由保存任何数据,因此 LEFT JOIN 可能返回 NULL

预期结果:

对于core_id = 4,我希望能够输出包含linked_data.data_value = whatever 的单个SQL 行结果。对于core_id = 5,我希望能够输出其余数据,但linked_data. 表中没有。

我已经尝试了什么?

  • This answer 被标记为正确,但也注意到随着数据量的增加它会很快变得非常慢。

  • This answer 将限定符放在WHERE 子句中,但没有保证linked_data 将包含任何结果,所以我当然可以添加更多条件(检查是否进入WHERE 子句,但我希望避免这种情况。

  • This MySQL post 有另一种可能的解决方案,但 cmets 对此也表示它非常慢(这可能是他们的用户错误,我还没有测试过)。

  • 我也尝试在 LEFT JOIN 中使用 SELECT,如下所示:

     SELECT ... many columns ..., 
     ld.data_value,
     ld.data_date,
     more.columns ... 
     FROM core
     LEFT JOIN table1 ON core.core_id = table1.core_id
     LEFT JOIN table2 ON core.core_id = table2.core_id
     LEFT JOIN table3 ON core.core_id = table3.core_id
     ... etc ...
     LEFT JOIN (
         SELECT linked_data FROM linked_data ldi WHERE core.core_id = ldi.core_id AND MAX(ldi.data_date) 
     ) as ld ON core.core_id = ldi.core_id
     WHERE ... core_id = value
    

引用自this Q&A

但这仍然告诉我这里不允许聚合调用

编辑:我找到了不允许聚合的原因;我的一个简单的语法错误;但我已经提出了一个完整的答案来澄清这个问答,因为我在搜索时找不到任何相关的答案,所以这可能对某人有用。

如果有人有更正确的解决原始问题的方法,请分享!

【问题讨论】:

    标签: mysql


    【解决方案1】:

    根据所提供的内容为您提供最短的示例答案...您可以进行预查询,从而产生连接的别名。此预查询可以对每个核心进行分组/最大。

    也就是说,由于链接数据表是自动递增的,我会假设(是的,我知道假设,但您可以确认)随着每条记录的添加,日期将始终是添加的日期。因此,ID 为 100 的日期可能为 2020 年 1 月 14 日,您永远不会有更高 ID 的较早日期记录,例如 ID 101 = 2019 年 11 月 3 日。添加每个 ID 时,无论核心 ID。

    然后,您可以根据需要继续对其他表进行额外的“左连接”。

    评论澄清修订

    Martin,根据您提供的有关来自多个来源的数据的说明,并且日期可能代表较旧的数据,只需将预查询内部 sql 修改为以下内容。该查询被大量注释以阐明在这种情况下过度/分区查询是如何工作的

    现在,与您的所有其他东西集成。我只会作为左连接加入你的主核心表

    select
          cd.core_id,
          cd.some_name,
          cd.some_data,
          cd.some_values,
          ld2.link_id,
          ld2.data_date,
          ld2.some_linked_data_values
       from
          core_data cd
             left join
             ( select pq1.* 
                 from
                   -- first, all columns I want to have returned from 
                   -- the linked_data table
                   ( select core_id, 
                            data_date, 
                            link_id, 
                            -- dense_rank() returns sequential counter value
                            -- starting at 1 based on every change of the 
                            -- PARTITION BY Core_ID in next part below
                            dense_rank() 
                        over ( partition by 
                                  -- primary sorting by the core_id
                                  core_id 
                               order by 
                                  -- then within each core_id, descending by date
                                  data_date desc, 
                                  -- and then by the link_id descending, just in case
                                  -- there are multiple records for the same core_id
                                  -- AND the same date... So you get the most
                                  -- recently added linked_data record for given core
                                  link_id desc ) as sqlrow
                        from linked_data ) pq1
                where
                   -- now, from inner partition/over query, only get the record
                   -- where the sqlrow = 1, as result of dense_rank()
                   -- that resets to 1 every time core_id changes
                   pq1.sqlrow=1 ) PQ
                 on cd.core_id = PQ.core_id
                    LEFT JOIN linked_data ld2
                       on PQ.Link_id = ld2.link_id
    

    使用 OVER / PARTITION BY 的内部查询基本上是对数据进行第一次传递,并首先按分区(核心 id)对其进行排序,然后按 data_date DESCENDING 进行子排序(所以最近的日期首先不管之前或之后从任何外部来源的导入中添加),然后根据为任何给定日期添加的最新记录按 link_id 降序排序。

    最后的外部 WHERE 子句基本上是声明只给我每个 core_id 的第一行。因此,现在,您拥有适当的关键元素来重新执行 LEFT 连接回原始核心 ID,同时拥有适当的 link_id 以在最终查询结果中获得正确的记录。

    【讨论】:

    • 谢谢。 data_date 列的日期不是添加到此系统的日期,不能保证最近的记录具有最近的日期。记录来自各种来源,有时已有多年历史
    • @Martin,查看修订和编辑查询并实现了过度/分区
    【解决方案2】:

    写完整个问题后,我发现对我的最新示例 SQL 进行的简单语法修复解决了执行此操作的正确方法(我过去曾以正确的方式完成此操作,但没有学会它)。

    我注意到在 Internet 上对我的查询缺乏(或者更确切地说是我无法轻松找到)明确明确的答案,所以这是我的方法...

    关于从连接表中获取 0 或 1 个结果,执行此类聚合连接的正确方法如下:

    • LEFT JOIN,然后将子查询包裹在括号中,并像往常一样设置为as ...
    • 将聚合函数放入子查询中,忽略连接条件;连接条件将在外部查询 ON ... 部分中。
    • 在子查询中使用ORDER BY,然后在该点设置聚合。

    所以;

       SELECT ... many columns ..., 
       ld.data_value,
       ld.data_date,
       more.columns ... 
       FROM core
       LEFT JOIN table1 ON core.core_id = table1.core_id
       LEFT JOIN table2 ON core.core_id = table2.core_id
       LEFT JOIN table3 ON core.core_id = table3.core_id
       ... etc ...
       LEFT JOIN (
           SELECT linked_data FROM linked_data ldi WHERE optional = 1 ORDER BY MAX(ldi.data_date) 
       ) as ld ON core.core_id = ldi.core_id
       WHERE ... core_id = value
    

    在子查询中不需要WHERE,但显然如果你这样做了,那么它应该是静态的并且不需要引用外部表,就像在ON 子句中所做的那样。为了熟悉,我输入了WHERE optional = 1

    LEFT JOIN 的本质是它会返回 0 或 1 个结果,所以我发现不需要太多其他内容。

    如果有人有更好的方法来解决我原来的问题,请告诉我!

    P.s> 我根本没有对这个答案进行效率测试。

    【讨论】:

      猜你喜欢
      • 2021-02-12
      • 1970-01-01
      • 2019-10-20
      • 1970-01-01
      • 1970-01-01
      • 2016-04-23
      • 1970-01-01
      • 2021-06-19
      • 2021-01-27
      相关资源
      最近更新 更多