在单个结果行中改进连接的左连接查询

问题描述 投票:0回答:1

我有两张桌子; memberstelephone

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

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

这是来自同一表的不同部分的LEFT JOINS的串联。

数据库表在所有数字列上都有完整索引。

Table structures

(可能不那么重要,但为了完整起见,这里)

成员:

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

My Query so far:

查询的目的是为每个成员详细说明(在未示出的其他数据中)返回所有相关电话号码并且能够在每个号码之间进行区分的单行:

WHEREconditionals仅适用于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 ...   

Example data:

会员表:

 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...| ...

Notes:

  • 我知道标志列(mainplayer等)可能是ENUM
  • 我也知道这些表可能是InnoDB并且设置了外键。

(我完全不反对这些事情中的任何一个,这只是一个绕过它们的情况)

  • 没有电话号码是必需的,所以我知道INNER JOIN效率更高;它不能用于这种情况。
  • GROUP_CONCAT不能按列进行操作,因为每个列都是由不同的标准(标志)选择的。也许它可以在更广泛的设置上工作,但每个tel_num值都需要重命名(我想我可以在这种情况下使用GROUP_CONCAT的一些指导)。
  • 标志并不相互排斥:电话排可能是| Y | Y | Y。但每个成员只有一个标志。
  • 所有电话号码都是活动的,有些没有标志,因此不需要返回。

题:

Can I improve my SQL query to not need three seperate LEFT JOINs?


我在类似问题上看过的其他问题:

mysql
1个回答
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数字列表之外,结果集中的每条记录还将具有相应的电话号码类型。

© www.soinside.com 2019 - 2024. All rights reserved.