在单个结果行

时间:2017-12-20 11:45:42

标签: mysql

我有两张桌子; memberstelephone

会员可以拥有多个电话行。每个电话行可以设置标志以指示它是什么类型的电话号码。

我需要一个每个成员单行的结果集,包含所有或部分不同的电话行。

这是来自同一个表的不同部分的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...| ...

注意:

  • 我知道标志列(mainplayer等)可能是ENUM

  • 我也知道这些表可能是InnoDB并且设置了外键。

(我根本不反对这些事情,而只是一个案例,而且只是一个案例)

  • 所有电话号码都不需要,所以我知道INNER JOIN效率更高;它不能用于这种情况。

  • GROUP_CONCAT不会按列进行操作,因为每个列都是由不同的条件(标志)选择的。也许它可以在更广泛的设置上工作,但每个tel_num值都需要重命名(我想我可以在这种情况下使用GROUP_CONCAT的一些指导)。

  • 标志互斥:电话行可以是| Y | Y | Y。但每个成员只有一个的每个标志。

  • 所有电话号码都处于活动状态,有些电话号码没有标志,因此无需退回。

问题:

我可以将SQL查询改进为不需要三个单独的LEFT JOIN吗?

我在处理类似问题时遇到的其他问题:

1 个答案:

答案 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数字列表外,结果集中的每条记录还将包含相应的电话号码类型。