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

Posted

技术标签:

【中文标题】MySQL从一个表中选择行,第二个表中有多行,并在所选行中获取多行数组【英文标题】:MySQL select row from one table with multiple rows in a second table and get array of multi row in selected row 【发布时间】:2022-01-16 13:52:20 【问题描述】:

我有一个包含“客户”信息的表格,另一个包含每个客户的“票证”信息。

int-------| varchar -------| varchar
client_id | client_name    | client_tickets
----------+----------------+--------------
1         | Title one      | 1,2
2         | Title two      | 2,3

简化票务表

int--------| varchar -------| varchar
ticket_id  | ticket_name | ticket_price
-----------+-------------+--------------
1          | ticketone   | 30  
2          | tickettwo   | 40   
3          | ticketthree | 50  
4          | ticketfour  | 60   
5          | ticketfive  | 70 

使用上述两个表,我想生成一个带有单个查询的单个表,其中包含所有相关信息以生成搜索网格 从而给出以下输出:

client_id | client_name    | client_tickets | ticket_names          | ticket_prices
----------+----------------+----------------+-----------------------+--
1         | Title one      | 1,2            | ticketone,tickettwo   | 30,40
2         | Title two      | 2,3            | tickettwo,ticketthree | 40,50

ticket_names,ticket_ids,client_name 是 varchar

我想通过一个请求接收最后 5 列 例如:

SELECT s.*,
(SELECT GROUP_CONCAT(ticket_name SEPARATOR ',') FROM tickets_table WHERE ticket_id IN(s.client_tickets)) AS ticket_names, 
(SELECT GROUP_CONCAT(ticket_price SEPARATOR ',') FROM tickets_table WHERE ticket_id IN(s.client_tickets)) AS ticket_prices 
FROM client_table s where s.client_id=1

这似乎有问题 你有更好的建议吗?

请提出您的建议

更新: 清理我想要的结果 以下代码有两个查询, 我希望这段代码通过查询来完成

$client_result = $conn->query("SELECT * FROM client_table where client_id=1");
while($client_row = $client_result->fetch_assoc()) 
  $ticket_result = $conn->query("SELECT * FROM tickets_table where ticket_id IN ($client_row['client_tickets'])");
  while($ticket_row = ticket_result->fetch_assoc()) 
    echo $ticket_row['ticket_name']."<br>";
  

更新 2

我使用建议 @raxi ,但我的 mariadb 是 10.4.17-MariaDB 并且不支持 JSON_ARRAYAGG ,根据参考创建 aggregate function 解决它 , 使用 SQL

DELIMITER //

DROP FUNCTION IF EXISTS JSON_ARRAYAGG//

CREATE AGGREGATE FUNCTION IF NOT EXISTS JSON_ARRAYAGG(next_value TEXT) RETURNS TEXT
BEGIN  

 DECLARE json TEXT DEFAULT '[""]';
 DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN json_remove(json, '$[0]');
      LOOP  
          FETCH GROUP NEXT ROW;
          SET json = json_array_append(json, '$', next_value);
      END LOOP;  

END //
DELIMITER ;

【问题讨论】:

你想保持表格原样吗?或者您是否希望看到一个能让事情变得更容易的架构(两种情况下的连接查询完全不同) 不,如果有更好的办法,那我就去做 【参考方案1】:

您想要一个相当简单的 SELECT 查询和一些 LEFT/INNER JOIN(s)。

这个网站有一些很好的例子/解释似乎非常接近您的需要:https://www.mysqltutorial.org/mysql-inner-join.aspx


我会给你一个简单的例子,但我并不清楚相关列是什么数据类型。两个表的_id-columns 可能是 INTEGER 的一些变体,它们是否也是主键(或至少索引?),client_name/ticket_name 可能是 VARCHAR/TEXT/STRING 类型,但究竟是剩余的列存储?作为 json 或数组或? (+细节)

您还用php 标记了您的帖子,您是在SQL 查询之后吗?或寻找其中包含 SQL 的 PHP 代码。


更新

架构的改进版本

CREATE TABLE clients (
    client_id      SERIAL,
    client_name    VARCHAR(255)    NOT NULL,
    PRIMARY KEY (client_id)
);

CREATE TABLE tickets (
    ticket_id      SERIAL,
    ticket_name    VARCHAR(255)    NOT NULL,
    ticket_price   DECIMAL(10,2)   NOT NULL,
    PRIMARY KEY (ticket_id)
);

-- A junction table to glue those 2 tables together (N to N relationship)
CREATE TABLE client_tickets (
    client_id      BIGINT UNSIGNED NOT NULL,
    ticket_id      BIGINT UNSIGNED NOT NULL,
    PRIMARY KEY (client_id, ticket_id)
);

我已更改数据类型。 client_nameticket_name 仍然是 VARCHARS。我已将它们标记为NOT NULL(例如:必填字段),但如果您不喜欢,可以删除该部分。 client_id/ticket_id/ticket_price 也是 NOT NULL,但更改会产生负面影响。

ticket_price 现在是一个 DECIMAL 字段,可以存储诸如 1299.5050.00 之类的数字2 位小数(美分)。所以你可以存储从 $ -99.999.999,99 到 $ 99.999.999,99 的任何东西。 在 SQL 中,总是用这种表示法写数字(比如 70k):70000.00(例如:点,而不是逗号;并且没有千位运算符)。

client_idticket_id 现在都是SERIALs,这是BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的简写,并且它们都是PRIMARY KEYs。这听起来可能很复杂,但它们仍然只是普通的INTEGERs,具有412 等值。

UNIQUE 位防止您拥有 2 个具有相同 ID 号的客户端,AUTO_INCREMENT 意味着当您添加新客户端时,您不必指定 ID(尽管您可以这样做);你可以这样做:

INSERT INTO clients (client_name) values ('Fantastic Mr Fox');

client_id 将自动设置(随时间递增)。另一个表中的ticket_id 也是如此。

.

我已将您原来的 client_tickets 列替换为单独的连接表。 那里的记录存储客户的client_id 和属于他们的ticket_id。 一个客户端可以在联结表中有多个记录(他们拥有的每张票一个记录)。 同样,可以在任意数量的行上提及一张票。 某个client_id 可能在联结表中没有任何记录。 同样,某个ticket_id 可能在联结表中没有任何记录。 此表中不能存在相同的记录(由PRIMARY KEY 强制执行)。

测试数据

接下来,我们可以放一些数据来测试它:

    -- Create some tickets
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (1, 'ticketone',    '30' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (2, 'tickettwo',    '40' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (3, 'ticketthree',  '50' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (4, 'ticketfour',   '60' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (5, 'ticketfive',   '70' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (6, 'ticketsix',     '4' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (7, 'ticketseven',   '9' );
    INSERT INTO  tickets (ticket_id, ticket_name, ticket_price) values (8, 'ticketeight', '500' );
    
    -- Create some users, and link them to some of these tickets
    INSERT INTO  clients (client_id, client_name) values (1, 'John');
    INSERT INTO  client_tickets (client_id, ticket_id) values (1, 3);
    INSERT INTO  client_tickets (client_id, ticket_id) values (1, 7);
    INSERT INTO  client_tickets (client_id, ticket_id) values (1, 1);
    
    INSERT INTO  clients (client_id, client_name) values (2, 'Peter');
    INSERT INTO  client_tickets (client_id, ticket_id) values (2, 5);
    INSERT INTO  client_tickets (client_id, ticket_id) values (2, 2);
    INSERT INTO  client_tickets (client_id, ticket_id) values (2, 3);
    
    INSERT INTO  clients (client_id, client_name) values (3, 'Eddie');
    INSERT INTO  client_tickets (client_id, ticket_id) values (3, 8);
    
    INSERT INTO  clients (client_id, client_name) values (9, 'Fred');
    
    -- Note: ticket #3 is owned by both client #1/#2; 
    -- Note: ticket #4 and #6 are unused; 
    -- Note: client #9 (Fred) has no tickets;
    

查询

获取所有现有关系(无票客户被排除在外,无所有者票被排除在外)

            SELECT clients.*
                 , tickets.*
              FROM client_tickets AS ct
        INNER JOIN clients ON ct.client_id = clients.client_id
        INNER JOIN tickets ON ct.ticket_id = tickets.ticket_id
          ORDER BY clients.client_id ASC
                 , tickets.ticket_id ASC ;

获取所有仍然免费的门票(无所有者)

            SELECT tickets.*
              FROM tickets
             WHERE tickets.ticket_id NOT IN (
                        SELECT ct.ticket_id
                          FROM client_tickets AS ct
                   )
          ORDER BY tickets.ticket_id ASC ;

获取所有客户(甚至是无票客户)的列表,并包括每个客户有多少票以及他们的票的总价格。

            SELECT clients.*
                 , COALESCE(COUNT(tickets.ticket_id),     0)  AS  amount_of_tickets
                 , COALESCE(SUM(tickets.ticket_price), 0.00)  AS  total_price
              FROM clients
         LEFT JOIN client_tickets AS ct  ON ct.client_id = clients.client_id
         LEFT JOIN tickets               ON ct.ticket_id = tickets.ticket_id
          GROUP BY clients.client_id
          ORDER BY clients.client_id ASC ;

将所有有趣的信息放在一起(不包括无所有者门票)

            SELECT clients.*
                 , COALESCE(COUNT(sub.ticket_id),       0)  AS  amount_of_tickets
                 , COALESCE(SUM(sub.ticket_price),   0.00)  AS  total_price
                 , JSON_ARRAYAGG(sub.js_tickets_row)        AS  js_tickets_rows
              FROM clients
         LEFT JOIN client_tickets AS ct  ON  ct.client_id = clients.client_id
         LEFT JOIN (
                        SELECT tickets.*
                             , JSON_OBJECT( 'ticket_id',    tickets.ticket_id
                                          , 'ticket_name',  tickets.ticket_name
                                          , 'ticket_price', tickets.ticket_price
                                          )  AS js_tickets_row
                          FROM tickets
                   ) AS sub ON ct.ticket_id = sub.ticket_id
          GROUP BY clients.client_id
          ORDER BY clients.client_id ASC ;
          
          -- sidenote: output column `js_tickets_rows` (a json array) may contain NULL values

包含一些汇总数据的所有票证列表

            SELECT tickets.*
                 , IF(COALESCE(COUNT(clients.client_id), 0) > 0
                     , TRUE, FALSE)                                      AS  active
                 , COALESCE(    COUNT(clients.client_id), 0)             AS  amount_of_clients
                 , IF(COALESCE( COUNT(clients.client_id), 0) > 0
                     , GROUP_CONCAT(clients.client_name SEPARATOR ', ')
                     , NULL)                                             AS  client_names
              FROM tickets
         LEFT JOIN client_tickets AS ct  ON ct.ticket_id = tickets.ticket_id
         LEFT JOIN clients               ON ct.client_id = clients.client_id
          GROUP BY tickets.ticket_id
          ORDER BY tickets.ticket_id ASC 
                 , clients.client_id ASC ;

【讨论】:

问题已更新..请检查一下, 谢谢,但值 client_tickets = (1,2) 表示 (ticket_id IN (1,2)) OR 表示 (ticket_id=1 OR ticket_id=2 ) 逗号是门票 ID 的分隔符 根据你的回答,必须为每个client_id创建一个ticket,但是我们想使用一些在client_tickets列中用逗号分隔的预制ticket 我更新了问题,所需的输出是用两个查询编写的,但我需要用一个查询来获得这个输出 嗯,我明白了,是的,我当时并没有完全理解门票的想法,但在这种情况下,是否有可能说门票 6 适用于超过 1 个客户? (我假设没有?)它真的总是2张票吗? (从不 1 或 3 或 4 等)——这将是一个简单的改变;如果您能稍微澄清一下这些票证,我会更新示例;

以上是关于MySQL从一个表中选择行,第二个表中有多行,并在所选行中获取多行数组的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 将多行作为列连接

MYSQL:我想用另一个表中的值更新表中的所有行,其中第一个表中的值等于第二个表

使用 SSIS 将单个字段值拆分为第二个表中的多行

大查询:加入第二个表中的单个最新行

如果一个表的列等于第二个表中的列,则在第三个表中插入值,python - mysql

将第二个表中的第二个(条件)结果添加到 SQL 查询