Distinct left table rows on a left join - mysql

I need to distinct the left table rows on a left join.
Instead of this
| name | last name | phone |
|--------|-------------|---------|
| name1 | lastname1 | 1234567 |
|--------|-------------|---------|
| name1 | lastname1 | 2345678 |
|--------|-------------|---------|
| name2 | lastname2 | 3456789 |
I need this
| name | last name | phone |
|--------|-------------|---------|
| name1 | lastname1 | 1234567 |
| | | 2345678 |
|--------|-------------|---------|
| name2 | lastname2 | 3456789 |
I tried with a SELECT DISTINCT but without success...
I tried also a GROUP BY but it hides the second row

Mariano, followed you here from your WP post that got closed.
<?php
$results = your_sql_query_here();
$data = array();
foreach( $results as $result ) {
// Make a new array node for each name
if ( ! isset( $data[$result['name']] )
$data[$result['name']] = array();
$data[$result['name']][] = $result['phone'];
}
This will give you something like this
Array(
['name1'] => Array(
[0] => 5123451,
[1] => 5123452
),
['name2'] => Array(
[0] => 5123453,
[1] => 5123454
) )
You can then just do a for loop of your $data array, using the key as your name1 value.
Or store entire data sets
<?php
$results = your_sql_query_here();
$data = array();
foreach( $results as $result ) {
// Make a new array node for each name
if ( ! isset( $data[$result['name']] )
$data[$result['name']] = array();
$data[$result['name']][] = $result;
}
Now you will have access to all nodes, but grouped by name.
Array(
['name1'] => Array(
[0] => Array( 'name' => 'name1', 'phone' => 4165123, 'another_field' => 1 ),
[1] => Array( 'name' => 'name1', 'phone' => 4165157, 'another_field' => 0 ),
[1] => Array( 'name' => 'name1', 'phone' => 4225157, 'another_field' => 0 )
),
['name2'] => Array(
[0] => Array( 'name' => 'name2', 'phone' => 4165123, 'another_field' => 1 ),
[1] => Array( 'name' => 'name2', 'phone' => 4572321, 'another_field' => 1 ),
[1] => Array( 'name' => 'name2', 'phone' => 5235157, 'another_field' => 0 )
) )

You need GROUP_CONCAT:
This function returns a string result with the concatenated non-NULL values from a group.
So, your code will be:
SELECT name,
last_name,
GROUP_CONCAT(phone) AS phones
FROM table1
GROUP BY name, last_name;

Use GROUP_CONCAT() but beware of that fact it has the limit on character length of 1024 default,but can can be increased
SELECT name,
last_name,
GROUP_CONCAT(DISTINCT phone SEPARATOR ' ')
FROM table1
GROUP BY name, last_name
See fiddle Demo

Related

Join 2 tables with different number of rows

I would like to join 2 tables (contact and contact_meta) in ONE sql query knowing that contact_meta has several rows
Contact
id fname lname email
1 Nick John njohn#gmail.com
2 Laura Pitt lpitt#gmail.com
Contact_meta
id_contact contact_meta_key contact_meta_value
1 Newsletter yes
1 Level weak
2 Newsletter yes
I tried
SELECT * FROM contact as c
JOIN contact_meta as cm ON c.id = cm.id_contact
but where there are more than one row in contact_meta, I only get the last one.
Array (
[0] => stdClass Object ( [id] => 1 [lname] => Nick [fname] => John [email] => njohn#gmail.com [contact_meta_key] => level [contact_meta_value] => weak )
[1] => stdClass Object ( [id] => 2 [lname] => Laura [fname] => pitt [email] => lpitt#gmail.com [contact_meta_key] => newsletter [contact_meta_value] => yes
);
Moreover, I would like [newsletter] => yes instead of [contact_meta_key] => newsletter [contact_meta_value] => yes
What I wish :
Array (
[0] => stdClass Object ( [id] => 1 [lname] => Nick [fname] => John [email] => njohn#gmail.com [level] => weak [newsletter] => yes)
[1] => stdClass Object ( [id] => 2 [lname] => Laura [fname] => pitt [email] => lpitt#gmail.com [newsletter] => yes
);
You need to pivot the meta table.
SELECT c.*,
MAX(IF(contact_meta_key = 'Newsletter', contact_meta_value, NULL)) AS newsletter,
MAX(IF(contact_meta_key = 'Level', contact_meta_value, NULL)) AS level
FROM contact AS c
LEFT JOIN contact_meta AS cm ON c.id = cm.id_contact
GROUP BY c.id
DEMO
The thing is today John has 2 entries in contact_meta but tomorrow it can be 5 and Laura 9, I store many datas in it. If it possible to get all meta datas contact from users in 1 SQL query.
I found a solution but I think not good, because 2 SQL queries (I use Wordpress) :
$user = $wpdb->get_results( "SELECT * FROM contact as c JOIN relation_browser_contact as rbc ON c.contact_id = rbc.id_contact JOIN browser as b ON rbc.id_browser = b.browser_id");
$fellows = array();
$i=0;
foreach ($user as $key => $value) {
$data=array();
foreach ($user[$i] as $key => $value) {
if ( Helper::encrypt_decrypt('decrypt', $value) ){
$data[$key] = Helper::encrypt_decrypt('decrypt', $value);
}else{
$data[$key] = $value;
}
$metas = $wpdb->get_results( "SELECT contact_meta_key, contact_meta_value FROM contact_meta WHERE id_contact = ".$user[$i]->contact_id );
foreach ($metas as $key => $value) {
$data[$value->contact_meta_key] = $value->contact_meta_value;
}
}
$fellows[] = $data;
$i++;
}
print_r($fellows);
In this case, I have all I want, but just wondering if SQL query can do it...

Get Unique conversation between users in messages table mysql using cakephp

I have a messages table with following columns
id | message | toId | fromId | {few more columns}
I am trying to list recent conversations list. Consider a case where i with id 1 has a chat with id 2
some rows will be like
1 | First Message | 1 | 2 | {few more columns}
2 | Sec Message | 2 | 1 | {few more columns}
3 | Third Message | 1 | 3 | {few more columns}
Here is my code to get unique conversations based on messages
$result = $this->Message->find('all', array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'users',
'alias' => 'from',
'type' => 'LEFT',
'conditions' => array(
'from.id = Message.from',
)
),
array(
'table' => 'users',
'alias' => 'to',
'type' => 'LEFT',
'conditions' => array(
'to.id = Message.to',
)
),
),
'fields' => array('from.id','from.firstname','from.username', 'from.lastname','from.imagePath',
'to.id','to.firstname','to.username', 'to.lastname','to.imagePath',
'Message.*'),
'order' => array('Message.created DESC'),
'limit' => 20,
'offset' => $offset * 20,
));
This above code is just simply fetching all the messages and attaching their users. However i am not sure how can i put condition based on unique conversations.
In above example there are two conversations, between 1 and 2 and between 1 and 3
How can i list only 2 messages in response that have unique combination
"SELECT From.id, From.firstname, From.username,
From.lastname, From.imagePath, To.id, To.firstname,
To.username, To.lastname, To.imagePath, Message.* FROM
xxxx.messages AS Message LEFT JOIN xxxx.users AS To
ON (Message.to = To.id) LEFT JOIN xxxx.users AS From
ON (Message.from = From.id) LEFT JOIN xxxx.messages AS
message ON (((Message.from = message.from AND Message.to
= message.to) OR (Message.from = message.to AND Message.to = message.from)) AND Message.created <
message.created) WHERE message.id IS NULL ORDER BY
Message.created DESC LIMIT 20",
To get the latest conversation from messages per unique toId and fromId you can use self join with some conditional clauses in on() part
SELECT m.*
FROM
message m
LEFT JOIN message m1 ON (
(
(m.fromId = m1.fromId AND m.toId = m1.toId )
OR
(m.fromId = m1.toId AND m.toId = m1.fromId )
)
AND m.created < m1.created
)
WHERE m1.id IS NULL
ORDER BY m.id
DEMO
Later on you could join you users table with above query to get user related information

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)

sql select multiple rows in subselect

First things first, I know this is incorrect currently. I know this because it doesn't work. So with that said, any assistance in getting me closer to my desired result is appreciated.
I have a query:
$query = "SELECT cat.*,
prod.title AS product,
cat1.title AS category,
(SELECT cat2.*
FROM ic_store_catalog AS cat2
WHERE cat2.parentid = cat.parentid) AS children
FROM ic_store_catalog AS cat
LEFT JOIN ic_products AS prod
ON cat.productid = prod.productid
LEFT JOIN ic_product_categories AS cat1
ON cat.categoryid = cat1.categoryid
WHERE `storeid` = $storeid
AND `column` = $column
AND `parentid` = 0
ORDER BY `the_order` ASC";
This errors out giving me: Operand should contain 1 column(s) as the problem. This exists in the sub-select guaranteed.
Now what I need to achieve when I use print_r in PHP is an Array similar in structure to this:
[0] => Array
(
[catalogID] => 165
[storeID] => 0
[categoryID] => 7
[parentID] => 0
[productID] => 4
[title] =>
[column] => 1
[the_order] => 1
[cat_order] => 1
[category] => Cookies & Brownies
[children] => Array
(
[0] => Array (
[catalogID] => 166
[storeID] => 0
[categoryID] => 8
[parentID] => 7
[productID] => 5
[the_order] => 1
[cat_order] => 1
[category] => Brownies
)
)
)
Any help in getting me closer to this result is appreciated.
(SELECT cat2.*
FROM ic_store_catalog AS cat2
WHERE cat2.parentid = cat.parentid)
You should replace cat2.* by only one column that will represent children.

SQL: GROUP BY after JOIN without overriding rows?

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.