Home > Blockchain >  MySQL select row from one table with multiple rows in a second table and get array of multi row in s
MySQL select row from one table with multiple rows in a second table and get array of multi row in s

Time:12-14

i have one table containing "Client" information, and another including "Tickets" information for each client.

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

Simplified tickets table

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

With the above two tables, I want to produce a single table with a single query with all the pertinent information to generate a search grid So as to give the following output :

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 are varchar

I want to receive the final 5 columns with one request for example :

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

Which seems to have a problem Do you have a better suggestion?

Please make your suggestions

Update : To clean the result I want The following code has two querys, I want this code to be done with a query

$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>";
  }
}

update 2

i use suggest @raxi , but my mariadb is 10.4.17-MariaDB and don't support JSON_ARRAYAGG , for resolve it according to the reference Creating an aggregate function , Using 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 ;

CodePudding user response:

What you want a fairly straightforward SELECT query with some LEFT/INNER JOIN(s).

This website has some good examples/explanations which seem very close to your need: https://www.mysqltutorial.org/mysql-inner-join.aspx


I would give you a quick working example, but it is not really clear to me what datatype the relevant columns are. Both tables' _id-columns are likely some variant of INTEGER, are they also both primary keys (or otherwise atleast indexed ?), the client_name/ticket_name are likely VARCHAR/TEXT/STRING types, but how exactly is the remaining column stored? as json or array or ? ( details)

Also you tagged your post with PHP, are you just after the SQL query ? or looking for PHP code with the SQL inside it.


updated

Improved version of the schema

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)
);

I have changed the datatypes. client_name and ticket_name are still VARCHARS. I've flagged them as NOT NULL (eg: required fields), but you can remove that part if you don't like that. client_id/ticket_id/ticket_price are also NOT NULL but changing that has negative side-effects.

ticket_price is now a DECIMAL field, which can store numbers such as 1299.50 or 50.00 The (10,2) bit means it covers every possible number up to 8 whole digits (dollars/euros/whatever), and 2 decimals (cents). so you can store anything from $ -99.999.999,99 to $ 99.999.999,99 . in SQL always write numbers (like lets say 70k) in this notation: 70000.00 (eg: a dot, not a comma; and no thousandseperators).

client_id and ticket_id are both SERIALs now, which is shorthand for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE and theyre both PRIMARY KEYs on top of that. That probably sounds complicated but they're still just ordinary INTEGERs with values like 4 or 12 etc.

The UNIQUE bit prevents you from having 2 clients with the same ID number, and the AUTO_INCREMENT means that when you add a new client, you dont have to specify an ID (though you are allowed to); you can just do:

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

and the client_id will automatically be set (incrementing over time). And the same goes for ticket_id in the other table.

.

I've replaced your original client_tickets column, into a separate junction table. Records in there store the client_id of a client and the ticket_id that belongs to them. A client can have multiple records in the junction table (one record for each ticket they own). Likewise, a ticket can be mentioned on any number of rows. It's possible for a certain client_id to not have any records in the junction table. Likewise, it's possible for a certain ticket_id to not have any records in the junction table. Identical records cannot exist in this table (enforced by PRIMARY KEY).

Testdata

Next, we can put some data in there to be able to test it:

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

Queries

Get all the existing relationships (ticket-less clients are left out & owner-less tickets are left out)

            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 ;

Get all the tickets that are still free (owner-less)

            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 ;

Get a list of ALL clients (even ticketless ones), and include how many tickets each has and the total price of their tickets.

            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 ;

Put all the juicy info together (owner-less tickets are left out)

            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

An list of all tickets with some aggregate data

            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 ;

  • Related