Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Select multiple rows from MySQL tables for 1 user

Tags:

html

sql

php

mysql

For easier explaination I will try to simplify everything.

I have 3 SQL tables: Users, Certs, Serv

In Users table are stored details about unique users.

In Certs table are stored details about certificates and UserId of user who have this certificate (1 user can have multiple certificates)

In Serv table are stored details about sea services and UserId of user (like Certs table, 1 user can have multiple entries in Serv table)

SAMPLE DATA

Users

UserId  Name
1       John
2       Lisa

Certs

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

Serv

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

I need to retrieve output via PHP something like (where UserId = 1) also for reality there will be more columns from each table (for example more details of certificates like date of issue, date of expire, place of issue and so on):

Personal details:
Name
John

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

Sea Services:
Sea Service Name
SA
SB
SC
SD

But I got wrong output, duplicated entries, that because joining 2 tables with UserId in whose are multiple records with this UserId.

PHP CODE

$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 } ?>

You can check SQL FIDDLE to see what results selecting, duplicating rows.

Have you ideas how can I achieve desired output without duplicates?

UPDATE

After using GROUP_CONCAT with DISTINCT It's still wrong. Imagine that In Serv table I have columns like: UserId, Name, Rank and Country

If the same user worked in different companies (this Name in example - Company Name) with the same rank in different countries, It selecting wrong data. For example:

Serv table (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 

If I use query like this:

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 = ?

So GROUP_CONCAT(DISTINCT..) returning me in following:

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

Only first row have rank and first 3 rows have countries returned, but in database are stored ranks and countries for every row.

Full desired output with this data should be like this:

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

You can check It at SQL FIDDLE

UPDATE 2

If I remove DISTINCT I got following output:

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

If I'm using DISTINCT I got like this:

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

But It should be:

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

UPDATE 3

Imagine that I have fixed width of columns and I have long Sea Service Name which will be wrapped to new row:

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

As you see now each column are separate, rows didn't match. But It should be like 1 row. So I think I can't achieve It with GROUP_CONCAT, looks like I need another way.

How It looks in real:

rows not match

like image 628
Infinity Avatar asked Apr 05 '16 07:04

Infinity


People also ask

How do I select multiple rows in MySQL?

To select last two rows, use ORDER BY DESC LIMIT 2.

How do I select multiple rows in SQL?

Syntax - SELECT column1,column2, …, columnN FROM table_name; column1,column2 – Specifies the name of the columns used to fetch. table_name - Specifies the name of the table.

How do I select multiple rows in a table?

To select more than one row in the data view, click one row, then hold the Control (Windows) or Command (Mac) key and select each of the other rows you wish to edit or remove. To select a continuous list, click one row, then hold the Shift key and click the last row. Was this helpful?

How to select from two tables in MySQL?

Use UNION to select from two tables. Let us first create a table − mysql> create table DemoTable1 (Id int NOT NULL AUTO_INCREMENT PRIMARY KEY, FirstName varchar (20)); Query OK, 0 rows affected (0.90 sec) Insert some records in the table using insert command −

How do I join two tables in SQL?

When combining (joining) information from multiple tables, you need to specify how records in one table can be matched to records in the other. This is easy because they both have a name column. The query uses an ON clause to match up records in the two tables based on the name values. The query uses an INNER JOIN to combine the tables.

How many rows are affected by CREATE TABLE demotable2?

mysql> create table DemoTable2 ( Id int NOT NULL AUTO_INCREMENT PRIMARY KEY, FirstName varchar (20) ); Query OK, 0 rows affected (1.75 sec)

What does it mean when a query requires both tables?

This means the query requires both tables: The FROM clause joins two tables because the query needs to pull information from both of them. When combining (joining) information from multiple tables, you need to specify how records in one table can be matched to records in the other.


3 Answers

You are missing a group by clause :

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 = ?

Also, I've added distinct to your GROUP_CONCAT since you are left joining to multiple tables with multiples rows for each user, you will have multiple duplicates.

like image 124
sagi Avatar answered Oct 04 '22 09:10

sagi


This looks like a mad amount of effort to avoid multiple queries..

Keep it simple

You could run the query:

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 = ?

And separate the certificates and services in the application logic.

Or just run two or three separate queries:

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

Although there is the overhead of 3 queries, with the right indexing these will be crazy fast and you've reduced the amount of redundant data changing hands.

These simple queries are easily debugged and understood. The query you have is very complex for this operation and even minor changes are already causing you issues.

Also, please please please separate out your formatting from the database. If I was having trouble with spacing in a layout, one of the LAST places I'd look is the database query.

Keeping the separate layers of your application separate allows you to deal with one problem at a time and change the display of your data without worrying about the data itself.

like image 44
Arth Avatar answered Oct 04 '22 08:10

Arth


This can be simply done using application logic after getting entire data with duplicated entries and separating them in PHP. Always remember you can have multiple application servers to distribute its load but you would always prefer to have single Database server on which you would want minimum load of queries. Also Customer load can be distributed among application servers, where as same number of clients would be accessing same database.

like image 20
skilledpeas Avatar answered Oct 04 '22 07:10

skilledpeas