SQL: GROUP BY after JOIN without overriding rows? - mysql

I have a table of basketball leagues, a table af teams and a table of players like this:
LEAGUES
ID | NAME |
------------------
1 | NBA |
2 | ABA |
TEAMS:
ID | NAME | LEAGUE_ID
------------------------------
20 | BULLS | 1
21 | KNICKS | 2
PLAYERS:
ID | TEAM_ID | FIRST_NAME | LAST_NAME |
---------------------------------------------
1 | 21 | John | Starks |
2 | 21 | Patrick | Ewing |
Given a League ID, I would like to retrieve all the players' names and their team ID from all the teams in that league, so I do this:
SELECT t.id AS team_id, p.id AS player_id, p.first_name, p.last_name
FROM teams AS t
JOIN players AS p ON p.team_id = t.id
WHERE t.league_id = 1
which returns:
[0] => stdClass Object
(
[team_id] => 21
[player_id] => 1
[first_name] => John
[last_name] => Starks
)
[1] => stdClass Object
(
[team_id] => 21
[player_id] => 2
[first_name] => Patrick
[last_name] => Ewing
)
+ around 500 more objects...
Since I will use this result to populate a dropdown menu for each team containing each team's list of players, I would like to group my result by team ID, so the loop to create these dropdowns will only have to cycle through each team ID instead of all 500+ players each time.
But when I use the GROUP BY like this:
SELECT t.id AS team_id, p.id AS player_id, p.first_name, p.last_name
FROM teams AS t
JOIN players AS p ON p.team_id = t.id
WHERE t.league_id = 1
GROUP BY t.id
it only returns one player from each team like this, overriding all the other players on the same team because of the use of the same column names.
[0] => stdClass Object
(
[team_id] => 21
[player_id] => 2
[first_name] => Patrick
[last_name] => Ewing
)
[1] => stdClass Object
(
[team_id] => 22
[player_id] => 31
[first_name] => Shawn
[last_name] => Kemp
)
etc...
I would like to return something like this:
[0] => stdClass Object
(
[team_id] => 2
[player_id1] => 1
[first_name1] => John
[last_name1] => Starks
[player_id2] => 2
[first_name2] => Patrick
[last_name2] => Ewing
+10 more players from this team...
)
+25 more teams...
Is it possible somehow?

You cannot do this in SQL, since you cannot represent that result in a form of data set. You want to return complex object. What you can do, is to handle this in the code, and help yourself by returning a data set which is sorted by team_id. Whenever your team_id changes, then it is time to create new object in your code and fill it with new list of players.
It would be something like this (syntax might not be correct):
Returned result set:
team_id|player_id|first|last
1|1|f1|l1
1|2|f2|l2
1|3|f3|l3
2|5|f5|l5
2|6|f6|l6
And when this is returned in your code
$lastTeamId=0;
$output=array();
foreach($results as $row){
if($lastTeamId != $row["team_id"]){
$lastTeamId = $row["team_id"];
$output[$lastTeamId] = array();
}
$newPlayer = null;
$newPlayer->id = $row["player_id"];
$newPlayer->first = $row["first"];
$newPlayer->last = $row["last"];
$output[$lastTeamId][] = $newPlayer;
}

In MySQL, you could GROUP BY team_id and then SELECT GROUP_CONCAT(player detail ...). But that runs into restrictions and is not the typical relational approach.

Related

$wpdb->get_results query returns different (wrong) results compared to phpmyadmin

I have 4 custom tables in my database - wp_api_teams, wp_api_matches, wp_api_competitions and wp_api_federations. They are part of my Wordpress database, this is how they look (I'm using foreign keys and all of that):
I am trying to output all the teams that are part of a federation. When I run this in phpmyadmin it works:
SELECT
wp_api_teams.team_id,
wp_api_teams.title,
wp_api_competitions.comp_id,
wp_api_competitions.title,
wp_api_federations.federation_id,
wp_api_federations.title
FROM
wp_api_teams
JOIN wp_api_matches
ON wp_api_teams.team_id = wp_api_matches.hometeam_id OR wp_api_teams.team_id = wp_api_matches.awayteam_id
JOIN wp_api_competitions
ON wp_api_matches.comp_id = wp_api_competitions.comp_id
JOIN wp_api_federations
ON wp_api_competitions.federation_id = wp_api_federations.federation_id
WHERE wp_api_federations.federation_id = 1
GROUP BY (wp_api_teams.team_id);
I get the exact results that I am looking for.
-----------------------------------------------------------------------------------
| team_id | title | comp_id | title | federation_id | title |
1 Arsenal 1 Premier League 1 England
2 Chelsea 1 Premier League 1 England
3 Liverpool 1 Premier League 1 England
4 Manchester United 1 Premier League 1 England
5 Manchester City 1 Premier League 1 England
This is the code I'm using in Wordpress:
global $wpdb;
$sql = "
SELECT
wp_api_teams.team_id,
wp_api_teams.title,
wp_api_competitions.comp_id,
wp_api_competitions.title,
wp_api_federations.federation_id,
wp_api_federations.title
FROM
wp_api_teams
JOIN wp_api_matches
ON wp_api_teams.team_id = wp_api_matches.hometeam_id OR wp_api_teams.team_id = wp_api_matches.awayteam_id
JOIN wp_api_competitions
ON wp_api_matches.comp_id = wp_api_competitions.comp_id
JOIN wp_api_federations
ON wp_api_competitions.federation_id = wp_api_federations.federation_id
WHERE wp_api_federations.federation_id = 1
GROUP BY wp_api_teams.team_id
";
$test = $wpdb->get_results($sql);
echo('<pre>');
print_r($test);
echo('</pre>');
and the result that I'm getting is missing two table columns:
Array
(
[0] => stdClass Object
(
[team_id] => 1
[title] => England
[comp_id] => 1
[federation_id] => 1
)
[1] => stdClass Object
(
[team_id] => 2
[title] => England
[comp_id] => 1
[federation_id] => 1
)
.... and so on.
As you can see, the wp_api_teams.title and wp_api_competitions.title columns are missing from the result.
So my questions is, why is this happening, why are these columns being omitted and is there anything I can do to fix this, because I absolutely need them in my returned result. How can the same query produce a different result in WP vs PHPmyadmin.
Is this a known bug or is there something wrong with my code?
I'm running 10.2.14-MariaDB on localhost and all the table engines are InnoDB.
Your column names are the same in your result set. You need to provide an alias:
global $wpdb;
$sql = "
SELECT
wp_api_teams.team_id,
wp_api_teams.title as team_title,
wp_api_competitions.comp_id,
wp_api_competitions.title as competition_title,
wp_api_federations.federation_id,
wp_api_federations.title as federation_title
FROM
wp_api_teams
JOIN wp_api_matches
ON wp_api_teams.team_id = wp_api_matches.hometeam_id OR wp_api_teams.team_id = wp_api_matches.awayteam_id
JOIN wp_api_competitions
ON wp_api_matches.comp_id = wp_api_competitions.comp_id
JOIN wp_api_federations
ON wp_api_competitions.federation_id = wp_api_federations.federation_id
WHERE wp_api_federations.federation_id = 1
GROUP BY wp_api_teams.team_id
";
$test = $wpdb->get_results($sql);
echo('<pre>');
print_r($test);
echo('</pre>');

How to join 2 rows from the tables?

Hi I have a table here with the follow columns: ApplicantID, Name and Age. I would like to know how to join the 2 rows of applicant together to be displayed on the same table(php). I am trying to retrieve it from database. Thanks!
--------------------------------------
Applicant ID Name Age
--------------------------------------
001 John 20
002 Nick 22
Below is what I want it to return
--------------------------------------
Name1 Age1 Name2 Age2
--------------------------------------
John 20 Nick 22
EDIT: The code from Amit work, but the data returned is:
Name1 Age1 Name2 Age2
--------------------------------------
John 20 Nick 22
--------------------------------------
Nick 22 Judy 25
How do I only return Nick once?
if you're using php, it could be like this :
let's assume that we have an array which have same value as your table.
$arr = [
['Applicant ID' => 001, 'Name' => 'John', 'Age' => 20],
['Applicant ID' => 002, 'Name' => 'Nick', 'Age' => 22],
];
$res = [];
foreach($arr as $key => $values){
$names = 'Name'.($key+1);
$ages = 'Age'.($key+1);
$res[$names] = $values['Name'];
$res[$ages] = $values['Age'];
}
you could use a cross (self) join filtering the rows
select a.name as name1, a.age as age1, b.name as name2, b.age as age2
from table1 as a
cross join table2 as b
where a.applicant_ID = '001'
and b.applicant_ID ='002'
You can use self join by using following query:-
SELECT applicant1.name,
applicant1.age,
applicant2.name,
applicant2.age
FROM applicant as applicant1
JOIN applicant applicant2
on applicant1.applicant_id+1 = applicant2.applicant_id;

MySQL GROUP BY kills ORDER BY

I have a problem and there are already some similar questions but they didn't solve my problem.
I have several hotels and pictures in it. The pictures are ordered by a number. I want all hotels and the picture with the lowest number.
When I try following query I get the right order:
SELECT s25.entry_id AS id,
s25.value AS title,
s35.file AS picture,
s86.value AS picture_sort
FROM sym_entries_data_25 AS s25
LEFT JOIN sym_entries_data_34 AS s34 ON (s25.entry_id = s34.relation_id)
LEFT JOIN sym_entries_data_35 AS s35 ON (s34.entry_id = s35.entry_id)
LEFT JOIN sym_entries_data_86 AS s86 ON (s34.entry_id = s86.entry_id)
ORDER BY s86.value
I will get the Array
[0] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis2.jpg
[picture_sort] => 1
)
[1] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis1.jpg
[picture_sort] => 2
)
[2] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis3.jpg
[picture_sort] => 3
)
...And so on
But I only want one result per hotel, so I tried a GROUP BY:
SELECT s25.entry_id AS id,
s25.value AS title,
s35.file AS picture,
s86.value AS picture_sort
FROM sym_entries_data_25 AS s25
LEFT JOIN sym_entries_data_34 AS s34 ON (s25.entry_id = s34.relation_id)
LEFT JOIN sym_entries_data_35 AS s35 ON (s34.entry_id = s35.entry_id)
LEFT JOIN sym_entries_data_86 AS s86 ON (s34.entry_id = s86.entry_id)
GROUP BY s25.value
ORDER BY s86.value
Then I only get one Result but a random one, not the first one:
[0] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis3.jpg
[picture_sort] => 3
)
What could be the problem here?
when you are doing group by, you have no guarantee regarding the values of the columns you are not grouping by.
for example if you have a table with columns a and b, and data like:
a | b
-----
1 | 2
1 | 3
if you'll do select * from table group by a, you can get either 2 or 3 as the value of b.
if you only want the top result for the hotel, what you need to do is not group by, but take the result where picture_sort equals to the min(picture_sort)

mysql ColumnName AS 1, ColumnName AS 2, with WHERE, using UNION for all columns get AS 1

Have table like this
IdRows | UpperLevelIdRows | CategoriesName |
-------------------------------------------------
2 | 0 | Transport
4 | 2 | Cars
12 | 4 | Alfa Romeo
Query
SELECT IdRows AS IdRows1, CategoriesName AS CategoriesName1 FROM categories
WHERE UpperLevelIdRows = ?
UNION
SELECT IdRows AS IdRows2, CategoriesName AS CategoriesName2 FROM categories
WHERE UpperLevelIdRows = ?
Data for placeholders is
Array
(
[0] => 2
[1] => 4
)
So
SELECT IdRows AS IdRows1 .... WHERE UpperLevelIdRows = 2
and
SELECT IdRows AS IdRows2 .... WHERE UpperLevelIdRows = 4
As result expect get array like
[0] => Array
(
[IdRows1] => 4
[CategoriesName1] => Cars
)
[1] => Array
(
[IdRows2] => 12
[CategoriesName2] => Alfa Romeo
)
But get array like this
[0] => Array
(
[IdRows1] => 4
[CategoriesName1] => Cars
)
[1] => Array
(
[IdRows1] => 12
[CategoriesName1] => Alfa Romeo
)
Instead of IdRows2 see IdRows1
If i execute only the second SELECT IdRows AS IdRows2 ..., then see as expected [CategoriesName2] => Alfa Romeo
Where is my mistake? What need to correct?
From the data i want to create select/option boxes. Like
First select box
echo '<select name="upper_level_id0" id="upper_level_id0" >
<option value="'.$IdRows1.'">'.$CategoriesName1.'</option>
</select>';
Second select box
echo '<select name="upper_level_id1" id="upper_level_id1" >
<option value="'.$IdRows2.'">'.$CategoriesName2.'</option>
</select>';
At the moment found solution using transaction. Loop through all SELECT ...
$db->beginTransaction();
foreach ( $sql_get_id_name as $k_sql => $val_sql ) {
$stmt_get_id_name = $db->prepare( $val_sql );
$stmt_get_id_name->execute( array( $data_get_id_name[$k_sql] ) );
$id_name[] = $stmt_get_id_name->fetchAll(PDO::FETCH_ASSOC);
}
$roll_back = $db->rollBack();
Use this select
select cp.IdRows p_id, cp.UpperLevelIdRows p_parent_id, cp.CategoriesName p_name,
cc.IdRows p_id, cc.UpperLevelIdRows c_parent_id, cc.CategoriesName c_name
from categories cc left join categories cp on cp.IdRows = cc.UpperLevelIdRows
where cc.UpperLevelIdRows = 4
This way you will get all the sons of a parent. The first 3 columns from the result are the parent (in your row data it will be the Cars) and the second its childs (in your row data the Alfa Romeo)

MySQL select row and join to multiple records

I have 2 Tables and can't get my MySQL Query to work:
First Table users:
user_id name
------------
1 Bob
2 Alice
Second Table fields:
id unser_id field_id value
-----------------------------
1 1 4 foo
2 1 5 10
3 2 4 bar
Now I would like to join the Tables in a way that outputs:
[0] => (
[user_id] => 1
[name] => Bob
[fields] => (
[4] => foo
[5] => 10
)
[1] => (
[user_id] => 2
[name] => Alice
[fields] => (
[4] => bar
)
)
Thanks a lot!
I tried:
SELECT u.user_id,
u.name
FROM users as u
INNER JOIN fields as f on u.user_id = d.user_id
But I get for every user just the first fields entry.
SELECT user_id,name,GROUP_CONCAT(value SEPARATOR ',')as field FROM users u JOIN fields f
ON u.user_id=f.unser_id GROUP BY user_id
Maybe something like this?
select u.user_id,u.name,f.field_id,f.value from users u LEFT JOIN fields f ON u.user_id = f.unser_id