【问题标题】:MySQL complex semi-join without group byMySQL 复杂的半连接无分组依据
【发布时间】:2016-01-13 12:55:06
【问题描述】:

总结

我正在寻找一个半联接(ish)查询,它可以选择一些客户并从其他表中联接他们的最新数据。

稍后,我希望直接将条件附加到查询的末尾:WHERE c.id IN (1,2,3)

问题

据我所知,我的要求排除了GROUP BY

SELECT * FROM customer c
LEFT JOIN customer_address ca ON ca.customer_id = c.id
GROUP BY c.id
# PROBLEM: Cannot append conditions *after* GROUP BY!

对于大多数基于子查询的尝试,我的问题是一样的。

作为一个额外的挑战,我不能严格使用半联接,因为我允许至少两种类型的电话号码(移动电话和固定电话)来自同一张桌子。因此,从电话表中,我可能会为每个客户加入 多个 记录,即这不再是半加入。下面我当前的解决方案说明了这一点。

问题

  • 底部的EXPLAIN 结果对我来说看起来很高效。我对么?每个子查询是否只执行一次更新:似乎DEPENDENT SUBQUERY 对外部查询中的每一行执行一次。如果我们能避免这种情况,那就太好了。
  • 对于我正在做的事情有更好的解决方案吗?

DDL

DROP TABLE IF EXISTS customer;

CREATE TABLE `customer` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
);

DROP TABLE IF EXISTS customer_address;

CREATE TABLE `customer_address` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `customer_id` bigint(20) unsigned NOT NULL,
  `street` varchar(85) DEFAULT NULL,
  `house_number` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
  );

DROP TABLE IF EXISTS customer_phone; 
CREATE TABLE `customer_phone` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `customer_id` bigint(20) unsigned NOT NULL,
  `phone` varchar(32) DEFAULT NULL,
  `type` tinyint(3) unsigned NOT NULL COMMENT '1=mobile,2=landline',
  PRIMARY KEY (`id`)
  );

insert ignore customer values (1);
insert ignore customer_address values (1, 1, "OldStreet", 1),(2, 1, "NewStreet", 1);
insert ignore customer_phone values (1, 1, "12345-M", 1),(2, 1, "12345-L-Old", 2),(3, 1, "12345-L-New", 2);

SELECT * FROM customer;
+----+
| id |
+----+
|  1 |
+----+

SELECT * FROM customer_address;
+----+-------------+-----------+--------------+
| id | customer_id | street    | house_number |
+----+-------------+-----------+--------------+
|  1 |           1 | OldStreet |            1 |
|  2 |           1 | NewStreet |            1 |
+----+-------------+-----------+--------------+

SELECT * FROM customer_phone;
+----+-------------+-------------+------+
| id | customer_id | phone       | type |
+----+-------------+-------------+------+
|  1 |           1 | 12345-M     |    1 |
|  2 |           1 | 12345-L-Old |    2 |
|  3 |           1 | 12345-L-New |    2 |
+----+-------------+-------------+------+

目前的解决方案

SELECT *
FROM customer c

# Join the most recent address
LEFT JOIN customer_address ca ON ca.id = (SELECT MAX(ca.id) FROM customer_address ca WHERE ca.customer_id = c.id)

# Join the most recent mobile phone number
LEFT JOIN customer_phone cphm ON cphm.id = (SELECT MAX(cphm.id) FROM customer_phone cphm WHERE cphm.customer_id = c.id AND cphm.`type` = 1)

# Join the most recent landline phone number
LEFT JOIN customer_phone cphl ON cphl.id = (SELECT MAX(cphl.id) FROM customer_phone cphl WHERE cphl.customer_id = c.id AND cphl.`type` = 2)

# Yay conditions appended at the end
WHERE c.id IN (1,2,3)

小提琴

这个小提琴使用给定的解决方案给出了适当的结果集。请参阅我上面的问题。

http://sqlfiddle.com/#!9/98c57/3

【问题讨论】:

  • wheregroup by 之前。使用正确的语法有什么困难?目前还不清楚您要做什么。解释问题的一个好方法是使用样本数据和期望的结果。最后,您的问题是什么?
  • 难点在于查询要放在字符串常量中,没有条件。然后将条件附加到各种不同的用例中。我不希望将查询切成麻线以将条件放在正确的位置。到目前为止,我们对所有查询都使用了一个简单的追加,我不想偏离这个。
  • 尝试附加 HAVING 而不是 WHERE。希望优化师能解决。不过,看起来你已经让自己陷入了一个明智的框架。
  • 是的,这是一个限制。好电话,但出于各种原因,我真的在寻找可以使用WHERE 的解决方案。
  • 为什么必须使用 WHERE?如果您的限制能够附加,那么您不能使用 WHERE 来执行此操作。 WHERE 必须在 GROUP 之前。您可以查看并询问您想要的所有内容,但您找不到解决方法:)。使用 HAVING,您就有机会参与其中。

标签: mysql join subquery semi-join


【解决方案1】:

我会避免那些依赖子查询,而是试试这个:

SELECT
      *
FROM customer c
      LEFT JOIN (
            SELECT
                  customer_id
                , MAX(id) AS currid
            FROM customer_phone
            WHERE type = 1
            GROUP BY
                  customer_id
      ) gm ON c.id = gm.customer_id
      LEFT JOIN customer_phone mobis ON gm.currid = mobis.id
      LEFT JOIN (
            SELECT
                  customer_id
                , MAX(id) AS currid
            FROM customer_phone
            WHERE type = 2
            GROUP BY
                  customer_id
      ) gl ON c.id = gl.customer_id
      LEFT JOIN customer_phone lands ON gl.currid = lands.id
WHERE c.id IN (1, 2, 3)
;

或者,也许:

SELECT
      *
FROM customer c
      LEFT JOIN (
            SELECT
                  customer_id
                , MAX(case when type = 1 then id end) AS mobid
                , MAX(case when type = 2 then id end) AS lndid
            FROM customer_phone
            GROUP BY
                  customer_id
      ) gp ON c.id = gp.customer_id
      LEFT JOIN customer_phone mobis ON gp.mobid = mobis.id
      LEFT JOIN customer_phone lands ON gp.lndid = lands.id
WHERE c.id IN (1, 2, 3)
;

见:http://sqlfiddle.com/#!9/ef983/1/

【讨论】:

  • 感谢您的建议。不幸的是,子查询首先在没有任何条件的情况下完成,因此它对整个 customer_phone 表执行GROUP BY,而不是对一小部分记录。
  • 你有证据吗?如果你能证明这一点,那么也可以在子查询中添加一个 where 子句。但在下结论之前,一定要找到你需要的证据。
  • 呵呵,这就是问题所在:在查询中间避免WHERE 是我试图避免这个问题的原因。这些表将很快拥有数百万条记录,就像它们的前辈一样,因此完整的表GROUP BY 是不可能的。我已经在这种大小的桌子上尝试过,以确认确实会发生这种情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-14
  • 2016-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多