【问题标题】:mysql multiple left join and group by main tablemysql多个左连接并按主表分组
【发布时间】:2020-01-22 10:44:33
【问题描述】:

我有以下情况。一个地区有多个地区,一个地区有多个地址,一个地址在一个月内被访问多次。现在我想生成一份关于某个地区的月度报告。 (一个区域被访问了多少次)。我已经编写了查询,但结果集产生的区域较少,因为有些地址没有被访问。我有以下结构

表格

areas: id|name (180 rows) //名称是唯一的

区域:id|name|area_id(1k 行)

地址:id|name|territory_id(80k 行)

visiting_addresses:id|address_id|date|status(1M+ 行)//状态 => 1 = 已访问,2 = 待处理

我的查询如下。

select ar.id as area_id, ar.name as area,
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total

from      areas ar
left join territories t on t.area_id=ar.id
left join addresses a on a.territory_id=t.id
left join visiting_addresses va on va.address_id=a.id
where     month(va.date) = '01'
and       year(va.date)='2020'
group by  ar.id

区域表包含 180 个区域,但结果集仅显示 144 个区域。我的错误在哪里,对此有何解释?那些区域丢失了,因为他们没有来访。

【问题讨论】:

    标签: mysql sql left-join


    【解决方案1】:

    您的 WHERE 子句正在将带有 visiting_addresses 的 LEFT JOIN 转换为 INNER JOIN。由于它是 LEFT-JOIN 链中最右边的表,所有连接都将转换为 INNER JOINS。为了防止这种情况,您应该将相应的条件从 WHERE 子句移到 ON 子句:

    select ar.id as area_id, ar.name as area,
    sum(case when va.status = 1 then 1 else 0 end) as visited,
    sum(case when va.status = 2 then 1 else 0 end) as pending,
    count(va.id) as total
    
    from      areas ar
    left join territories t on t.area_id=ar.id
    left join addresses a on a.territory_id=t.id
    left join visiting_addresses va
      on  va.address_id=a.id
      and month(va.date) = '01'
      and year(va.date)='2020'
    
    group by  ar.id
    

    但由于您有很多行,我宁愿运行两个查询。首先使用内部连接仅获取上个月具有地址的区域。您应该更改va.date 上的条件以使用索引:

    select ar.id as area_id, ar.name as area,
    sum(case when va.status = 1 then 1 else 0 end) as visited,
    sum(case when va.status = 2 then 1 else 0 end) as pending,
    count(va.id) as total
    
    from areas ar
    join territories t on t.area_id=ar.id
    join addresses a on a.territory_id=t.id
    join visiting_addresses va on  va.address_id=a.id
    where va.date >= '2020-01-01'
      and va.date <  '2020-02-01'
    
    group by  ar.id
    

    确保您在visiting_addresses(date) 上有索引,或者在visiting_addresses(date, address_id, status) 上有更好的索引。

    然后用一个简单的方法得到所有区域

    select ar.id as area_id, ar.name as area 
    from areas ar
    

    并在将visitedpendingtotal 设置为零(在应用程序代码中)时将缺失区域添加到第一个结果中。

    INNER JOIN 应该快得多,因为现在引擎可以使用 WHERE 条件的索引从 visiting_addresses 开始仅读取必要的行。

    您还可以使用更复杂但单一的查询。想法是使用带有预聚合子查询的 LEFT JOIN:

    select ar.id as area_id, ar.name as area,
        coalesce(visited, 0) as visited,
        coalesce(pending, 0) as pending,
        coalesce(total, 0) as total
    from areas ar
    left join (
        select t.area_id
        sum(case when va.status = 1 then 1 else 0 end) as visited,
        sum(case when va.status = 2 then 1 else 0 end) as pending,
        count(va.id) as total
        from territories t
        join addresses a on a.territory_id=t.id
        join visiting_addresses va on  va.address_id=a.id
        where va.date >= '2020-01-01'
          and va.date <  '2020-02-01'
        group by t.area_id
    ) x on x.area_id = ar.id
    

    【讨论】:

    • 感谢您的努力。最后一个查询部分是我真正在寻找的答案。我试图从 mysql 端而不是 PHP 使用一个查询来做到这一点。非常感谢..
    【解决方案2】:

    尝试将WHERE 子句中的逻辑移动到相应连接的ON 子句中:

    SELECT
        ar.id AS area_id,
        ar.name AS area,
        COUNT(CASE WHEN va.status = 1 THEN 1 END) AS visited,
        COUNT(CASE WHEN va.status = 2 THEN 1 END) AS pending,
        COUNT(va.id) AS total
    FROM areas ar
    LEFT JOIN territories t ON t.area_id = ar.id
    LEFT JOIN addresses a ON a.territory_id = t.id
    LEFT JOIN visiting_addresses va ON va.address_id = a.id AND
        va.date >= '2020-01-01' AND va.date <  '2020-02-01'
    GROUP BY
        ar.id;
    

    请注意,选择 name 字段时仅按 id 进行聚合在 MySQL 中是有效的,假设 idareas 表中的唯一字段。

    您也可以尝试将以下索引添加到visiting_addresses 表中:

    CREATE INDEX date_idx ON visiting_addresses (address_id, date, status);
    

    这可能有助于加快加入此表的速度。

    【讨论】:

    • 我已复制粘贴您的查询。现在它非常慢:( ..所有表中的日期字段和foreign_keys都被索引了。出现超时错误。有没有更好的方法来编写这个查询?
    • @Noob 考虑使用文字重写对日期的限制,并添加一个索引。
    猜你喜欢
    • 1970-01-01
    • 2015-03-13
    • 1970-01-01
    • 2010-12-31
    • 2018-09-15
    • 1970-01-01
    • 2018-10-14
    • 2018-07-26
    • 1970-01-01
    相关资源
    最近更新 更多