我有两张桌子; members
和telephone
。
会员可以拥有多个电话行。每个电话行可以设置标志以指示它是什么类型的电话号码。
我需要一个每个成员单行的结果集,包含所有或部分不同的电话行。
这是来自同一个表的不同部分的LEFT JOINS
的串联。
数据库表在所有数字列上都有完整索引。
(可能不那么重要,但为了完整起见)
成员:
CREATE TABLE `members` (
`mem_id` smallint(6) NOT NULL AUTO_INCREMENT,
...
`reg` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'No Reg',
PRIMARY KEY (`mem_id`)
) ENGINE=MyISAM AUTO_INCREMENT=968 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
电话:
CREATE TABLE `telephone` (
`tel_id` int(8) NOT NULL AUTO_INCREMENT,
`mem_id` int(8) NOT NULL,
`tel_name` varchar(64) CHARACTER SET utf8 NOT NULL,
`tel_num` varchar(24) COLLATE utf8mb4_unicode_ci NOT NULL,
`main` char(1) CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`player` char(1) CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`home` char(1) CHARACTER SET utf8 NOT NULL DEFAULT 'N' COMMENT 'Y= Home Phone',
PRIMARY KEY (`tel_id`),
KEY `memid` (`mem_id`),
KEY `main` (`main`),
KEY `player` (`player`),
KEY `home` (`home`),
KEY `telnum` (`tel_num`)
) ENGINE=MyISAM AUTO_INCREMENT=2237 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
到目前为止查询的目的是为每个成员详细说明(在未示出的其他数据中)返回一行所有相关联的电话号码,并能够区分每个号码:
WHERE
条件仅适用于members
表格。
SELECT members.mem_id, ... ,
thome.tel_num as thome, tmob.tel_num as tmob,
twork.tel_num as twork
FROM members
LEFT JOIN telephone thome FROM telephone ON thome.mem_id = members.mem_id AND thome.home = 'Y'
LEFT JOIN telephone tmob FROM telephone ON tmob.mem_id = members.mem_id AND tmob.main = 'Y' AND tmob.tel_num LIKE '07%'
LEFT JOIN telephone twork FROM telephone ON twork.mem_id = members.mem_id AND twork.player = 'Y'
WHERE ...
会员表:
memid | mname | reg
---------------------------------
22 | george | 1456436456
电话桌
tel_id | mem_id | tel_name | tel_num | main | player | home
-----------------------------------------------------------------------
1 | 22 | Home phone | 01234 456463 | N | N | Y
12 | 22 | Work phone | 01334 457893 | Y | N | N
19 | 22 | Mobile num | 07564 456333 | N | Y | N
23 | 22 | Home phone | 01987 657353 | N | N | N
期望的结果:
memid | mname | reg | twork | tmob | thome | etc...
-------------------------------------------------------------------
22 | george | 1456436456 | 01334...| 07564...| 01234...| ...
我知道标志列(main
,player
等)可能是ENUM
我也知道这些表可能是InnoDB并且设置了外键。
(我根本不反对这些事情,而只是一个案例,而且只是一个案例)
所有电话号码都不需要,所以我知道INNER JOIN
效率更高;它不能用于这种情况。
GROUP_CONCAT
不会按列进行操作,因为每个列都是由不同的条件(标志)选择的。也许它可以在更广泛的设置上工作,但每个tel_num
值都需要重命名(我想我可以在这种情况下使用GROUP_CONCAT
的一些指导)。
标志不互斥:电话行可以是| Y | Y | Y
。但每个成员只有一个的每个标志。
所有电话号码都处于活动状态,有些电话号码没有标志,因此无需退回。
问题:
LEFT JOIN
吗?我在处理类似问题时遇到的其他问题:
答案 0 :(得分:1)
在不改变当前架构的情况下,我没有看到改进当前查询的方法,除了可能尝试使用索引等进行调整。但是,我可以对您的表设计进行建议更改,这可以适用于对您想要的输出进行更简单的查询。考虑将telephone
表重构为以下内容:
tel_id | mem_id | tel_name | tel_num | type | active
-----------------------------------------------------------------------
1 | 22 | Home phone | 01234 456463 | 1 | 1
12 | 22 | Work phone | 01334 457893 | 2 | 1
19 | 22 | Mobile num | 07564 456333 | 3 | 1
23 | 22 | Home phone | 01987 657353 | 1 | 0
现在,如果您想要一个可以检索每个成员的活动家庭,工作和手机号码的查询,以下内容就足够了:
SELECT
m.memid,
m.mname,
m.reg,
GROUP_CONCAT(t.tel_name ORDER BY t.type) names,
GROUP_CONCAT(t.tel_num ORDER BY t.type) numbers
FROM members m
LEFT JOIN telephone t
ON m.mem_id = t.mem_id AND
t.active = 1
GROUP BY
m.memid;
我假设每个成员总是有一些家庭,工作和移动号码的条目,即使该条目应为null或空字符串。我还假设您只想报告活动数字。如果您需要报告所有数字,那么群组连接解决方案就会变得不那么有吸引力了,因为您可能会找回一个很长的CSV字符串,而且如何阅读它可能并不直观。
修改强>
我们还可以GROUP_CONCAT
电话名称标签。这意味着除了CSV数字列表外,结果集中的每条记录还将包含相应的电话号码类型。