从 MySQL 表中为 1 个用户选择多行

Posted

技术标签:

【中文标题】从 MySQL 表中为 1 个用户选择多行【英文标题】:Select multiple rows from MySQL tables for 1 user 【发布时间】:2016-07-24 23:55:11 【问题描述】:

为了更容易解释,我将尝试简化所有内容。

我有 3 个 SQL 表:UsersCertsServ

Users 表中存储了有关唯一用户的详细信息。

Certs 表中存储有关证书的详细信息以及拥有此证书的用户的UserId(1 个用户可以拥有多个证书)

Serv 表中存储有关海上服务的详细信息和用户的UserId(如Certs 表,1 个用户可以在Serv 表中拥有多个条目)

样本数据

用户

UserId  Name
1       John
2       Lisa

证书

Id  UserId  CertName
1   1       A
2   1       B
3   1       C
4   2       A
5   2       C

服务

UserId  Name
1       SA
1       SB
1       SC
1       SD
2       S2A

我需要通过 php 检索输出,例如(其中 UserId = 1),实际上每个表中都会有更多列(例如证书的更多详细信息,如签发日期、到期日期、签发地点等开):

Personal details:
Name
John

Certificates:
Certificate Id    Certificate Name
1                 A 
2                 B
3                 C

Sea Services:
Sea Service Name
SA
SB
SC
SD

但是我得到了错误的输出,重复的条目,这是因为加入 2 个带有 UserId 的表,其中有多个带有这个 UserId 的记录。

PHP 代码

$users = $con->prepare("
    select u.Name 
           ,GROUP_CONCAT(c.Id SEPARATOR '<br>') AS Id
           ,GROUP_CONCAT(c.certsName SEPARATOR '<br>') AS certsName 
           ,GROUP_CONCAT(s.Name SEPARATOR '<br>') AS Name         
    from users u
    left join certs c on u.UserId = c.UserId
    left join serv s on u.UserId = s.UserId 
    where u.UserId = ?
");
$users->bind_param('i', $GetUserId);
$users->execute();

$users->bind_result(
    $userName,
    $certId,            
    $certName,          
    $servName
);

<?php 
while ($users->fetch()) 
?>

<span>Personal Details</span>
<div class="grid-group">
    <div class="grid-column">
        <div class="grid-item header">User Name </div> 
    </div>
    <div class="grid-column">
        <div class="grid-item"><?php echo $userName; ?></div>       
    </div>
</div>

<span>Certificates</span>
<div class="grid-group">
    <div class="grid-column">
        <div class="grid-item header">Certificate Id</div> 
        <div class="grid-item header">Certificate Name</div> 
    </div>
    <div class="grid-column">
        <div class="grid-item"><?php echo $certId; ?></div> 
        <div class="grid-item"><?php echo $certName; ?></div>       
    </div>
</div>

<span>Sea Services</span>
<div class="grid-group">
    <div class="grid-column">
        <div class="grid-item header">Sea Service Name</div> 
    </div>
    <div class="grid-column">
        <div class="grid-item"><?php echo $servName; ?></div>       
    </div>
</div>

<?php  ?>

您可以查看SQL FIDDLE 以查看选择、复制行的结果。

您知道如何在不重复的情况下实现所需的输出吗?

更新

在使用GROUP_CONCATDISTINCT 之后仍然是错误的。想象一下,在Serv 表中,我有如下列:UserIdNameRankCountry

如果同一用户在不同的公司工作(例如这个名字 - 公司名称)在不同的国家具有相同的排名,它选择了错误的数据。例如:

服务表 (SQL)

UserId  Name     Rank        Country
1       SA       Captain     USA
1       SB       Captain     USA
1       SC       Captain     RUS
1       SD       Captain     ENG
2       S2A      Engineer    USA 

如果我使用这样的查询:

select u.Name 
       ,GROUP_CONCAT(distinct c.Id SEPARATOR '<br>') AS Id
       ,GROUP_CONCAT(distinct c.certsName SEPARATOR '<br>') AS certsName 
       ,GROUP_CONCAT(distinct s.Name SEPARATOR '<br>') AS Name  
       ,GROUP_CONCAT(distinct s.Rank SEPARATOR '<br>') AS Rank
       ,GROUP_CONCAT(distinct s.Country SEPARATOR '<br>') AS Country        
from users u
left join certs c on u.UserId = c.UserId
left join serv s on u.UserId = s.UserId 
where u.UserId = ?

所以GROUP_CONCAT(DISTINCT..) 回复我如下:

......
Sea Services:
Sea Service Name    Rank      Country
SA                  Captain   USA
SB                            RUS
SC                            ENG
SD                        

只有第一行有排名,前 3 行有国家/地区返回,但在数据库中存储了每一行的排名和国家/地区。

包含此数据的完整期望输出应如下所示:

Personal details:
Name
John

Certificates:
Certificate Id    Certificate Name
1                 A 
2                 B
3                 C

Sea Services:
Sea Service Name    Rank      Country
SA                  Captain   USA
SB                  Captain   USA             
SC                  Captain   RUS
SD                  Captain   ENG

您可以在SQL FIDDLE查看它

更新 2

如果我删除 DISTINCT 我得到以下输出:

Sea Service Name    Rank        Country
SA                  Captain     USA
SA                  Captain     USA
SA                  Captain     USA
SB                  Captain     USA
SB                  Captain     USA
SB                  Captain     USA
SC                  Captain     RUS
SC                  Captain     RUS
SC                  Captain     RUS
SD                  Captain     ENG
SD                  Captain     ENG
SD                  Captain     ENG

如果我使用DISTINCT,我会得到这样的结果:

Sea Services:
Sea Service Name    Rank      Country
SA                  Captain   USA
SB                            RUS
SC                            ENG
SD                        

但它应该是:

Sea Services:
Sea Service Name    Rank      Country
SA                  Captain   USA
SB                  Captain   USA             
SC                  Captain   RUS
SD                  Captain   ENG

更新 3

假设我有固定宽度的列,并且我有很长的海上服务名称,它将被包装到新行:

Sea Service Name |  Rank  | Country
-----------------|--------|---------
This is long Sea | Captain| USA
Service Name     |--------|---------
-----------------| Captain| RUS
 Other Name      |--------|---------
-----------------| Captain| ENG
Another long Sea |--------|---------
Service Name     | Master | USA                        
-----------------|--------|---------
Other Sea Serv   |
-----------------|

正如您现在看到的,每一列都是独立的,行不匹配。但它应该像 1 行。所以我想我无法用GROUP_CONCAT 实现它,看来我需要另一种方式。

真实的样子:

【问题讨论】:

一条建议:不要在 SQL 或任何数据访问层中对数据进行格式化。使用它来提取数据,然后使用应用程序逻辑来转换该数据以进行演示或用于其他用例。 【参考方案1】:

您缺少group by 子句:

select u.Name 
       ,GROUP_CONCAT(distinct c.Id SEPARATOR '<br>') AS Id
       ,GROUP_CONCAT(distinct c.certsName SEPARATOR '<br>') AS certsName 
       ,GROUP_CONCAT(distinct s.Name SEPARATOR '<br>') AS Name  
       ,(SELECT GROUP_CONCAT(ss.Rank SEPARATOR '<br>') FROM users uu
         LEFT OUTER JOIN serv ss ON (uu.UserId = ss.UserId)
         WHERE uu.user_id = u.user_id) as Rank
       ,GROUP_CONCAT(distinct s.Country SEPARATOR '<br>') AS Country        
from users u
left join certs c on u.UserId = c.UserId
left join serv s on u.UserId = s.UserId 
where u.UserId = ?

另外,我已经向您的 GROUP_CONCAT 添加了 distinct,因为您要加入多个表,每个用户都有多行,所以您将有多个重复项。

【讨论】:

非常感谢! distinct 正是我所需要的! 我不明白你是如何从这个查询中得到这个输出的,它们甚至在列上都不匹配..告诉我确切的输出以便我理解。 感谢您的帮助,我还在等待您的建议。 感谢您的帮助,似乎可以正常工作,但我不认为这是使用GROUP_CONCAT 的最佳解决方案。在我的问题中检查 UPDATE 3 @Infinity:我的建议:不要使用一个查询,使用三个。证书与海上服务无关,所以不要试图以某种方式加入它们,然后在 PHP 中混淆这些数据。选择用户数据,构建 html;选择证书,构建 HTML;选择海上服务,构建 HTML。再简单不过了。【参考方案2】:

这看起来像是为了避免多次查询而付出的巨大努力..

保持简单

您可以运行查询:

SELECT ...        
FROM users u
LEFT JOIN certs c on u.UserId = c.UserId
LEFT JOIN serv s on u.UserId = s.UserId 
WHERE u.UserId = ?

并在应用逻辑中分离证书和服务。

或者只运行两个或三个单独的查询:

    SELECT * FROM users u WHERE u.UserId = ?
    SELECT * FROM certs c WHERE c.UserId = ?
    SELECT * FROM serv s  WHERE s.UserId = ?

虽然有 3 次查询的开销,但如果使用正确的索引,这些查询会非常快,并且您减少了易手的冗余数据量。

这些简单的查询很容易调试和理解。对于此操作,您的查询非常复杂,即使是很小的更改也已经给您带来了问题。

另外,请把你的格式从数据库中分离出来。如果我在布局中遇到间距问题,我会查看的最后一个位置是数据库查询。

将应用程序的各个层分开,这样您就可以一次处理一个问题并更改数据的显示方式,而不必担心数据本身。

【讨论】:

感谢您的回答,但我在运行 2 个查询时遇到问题,也许我遇到了 php 问题或者我不知道,我在这里问过如何运行 2 个单独的查询,但仍然没有运气解决它。 ***.com/questions/36427041/… 嗯,先整理一下你的错误报告,这样你就可以自己调试了,然后弄清楚如何在一个请求中运行两个查询(看起来你是通过同一个连接执行两个查询,然后再获取第一个结果)。不要因为简单的错误而限制您的选择。您是否考虑过使用框架?很多这些东西已经为你完成了。 这是正确答案。三个不同的选择 = 三个单独的查询。从读取的数据中创建 HTML 表格等应该不是问题。 @Infinity:在我看来,您应该接受 Arth 的回答。如果您在 PHP 中一个接一个地运行多个查询时遇到问题,那么这是另一个与此请求无关的问题。 我也相信这是解决这个问题的正确答案和正确方法【参考方案3】:

这可以在获取具有重复条目的整个数据并在 PHP 中将它们分开之后使用应用程序逻辑简单地完成。永远记住,您可以拥有多个应用程序服务器来分配其负载,但您总是希望拥有单个数据库服务器,您希望在其上的查询负载最小。此外,客户负载可以分布在应用服务器之间,因为相同数量的客户端将访问相同的数据库。

【讨论】:

感谢您的回答,但我无法通过 PHP 成功实现它,这就是为什么我要问如何。我不知道如何解决重复问题。您能提供一些代码示例吗?【参考方案4】:

您需要将DISTINCT 添加到GROUP_CONCAT,因为您将certs 中的n 行连接到serv 中的m 行,从而导致 n*m 行有很多重复。

但是,当您使用派生表为每个表执行 GROUP_CONCAT 时,每个表只能得到 一个 行:

select u.Name 
       ,c.Id
       ,c.certsName 
       ,s.Name  
       ,s.Rank
       ,s.Country        
from users u
left join
 ( select UserId
       ,GROUP_CONCAT(Id SEPARATOR '<br>') AS Id
       ,GROUP_CONCAT(certsName SEPARATOR '<br>') AS certsName 
   from certs
   where UserId = 1
   group by UserId
 ) as c on u.UserId = c.UserId
left join 
 ( select UserId
       ,GROUP_CONCAT(Name SEPARATOR '<br>') AS Name  
       ,GROUP_CONCAT(Rank SEPARATOR '<br>') AS Rank
       ,GROUP_CONCAT(Country SEPARATOR '<br>') AS Country        
   from serv
   where UserId = 1
   group by UserId
 ) as s on u.UserId = s.UserId 
where u.UserId = 1

见Fiddle

当然,提交单独查询的建议仍然是最佳答案,其他一切都只是一种变通解决方案。

【讨论】:

我已经给了他一个答案,问题是当等级/名称/证书之一..超过 1 行长度时行对齐.. @sagi:由于在连接之前进行了聚合,因此每个表只有一行,如果他不能以适当的方式显示它,他也可能会因三个 Selects 而失败:- 我同意,我认为这个问题不需要他关心,他需要另辟蹊径。听起来像 xy 问题..【参考方案5】:
SELECT 
u.name,
(SELECT GROUP_CONCAT(c.id SEPARATOR '<br>') FROM certs c WHERE c.userId = u.userId) AS Id,
(SELECT GROUP_CONCAT(c.CertsName SEPARATOR '<br>') FROM certs c WHERE c.userId = u.userId) AS certsName,
(SELECT GROUP_CONCAT(s.Name SEPARATOR '<br>') FROM serv s WHERE s.userId = u.userId) AS Name
FROM users u WHERE u.userId = ?

【讨论】:

【参考方案6】:

从 TABLE1(TB1) 和 TABLE2(TB2) 中选择多行: 来自表 1 的 CM1_1、CM1_2 和来自表 2 的 CM2_1、CM2_2、CM2_3

SELECT CM1_1 , CM1_2 , CM2_1 , CM2_2 , CM2_3 FROM TB1, TB2 WHERE  CM1_2 = '1' AND CM1_1 = CM2_1  AND CM2_3 = 'John'

【讨论】:

以上是关于从 MySQL 表中为 1 个用户选择多行的主要内容,如果未能解决你的问题,请参考以下文章

MySQL从一个表中选择行,第二个表中有多行,并在所选行中获取多行数组

MySQL - 在一个查询中从不同表中选择多行

如何从 SQL Server 中包含多行数据的 2 个表中选择 4 个不同的值?

Oracle中的多行插入查询(从一张表中选择多行并插入到另一张表中[重复]

从mysql 5.7中的变量将多行插入表中

MYSQL:在多行选择中使用 COUNT 和 SUM 函数的问题