Can a MySQL query turn rows into columns? - mysql

I have a number of tables I am trying to combine with joins but as such, the results are returned in a number of rows whereas I would like to have them generated as new columns.
member_information Table
MemberID | FirstName | LastName
---------------------------------
1 | John | Harris
2 | Sarah | Thompson
3 | Zack | Lewis
member_dependent_information Table
MemberID | FirstName | LastName | Type
---------------------------------------
1 | Amy | Harris | 1
2 | Bryan | Thompson | 1
2 | Dewey | Thompson | 2
2 | Tom | Thompson | 2
3 | Harry | Lewis | 2
3 | Minka | Lewis | 1
MySQL Query:
SELECT
t1.FirstName,
t1.LastName,
t1.MemberID,
IF(t2.Type = '1',CONCAT(t2.FirstName,' ',t2.LastName),'') AS Spouse_Name,
IF(t2.Type = '2',CONCAT(t2.FirstName,' ',t2.LastName),'') AS Child_Name,
FROM
member_dependent_information t2
INNER JOIN
member_information t1
USING
(MemberID)
ORDER BY
t1.LastName ASC,
t1.MemberID ASC;
Ideal Results
MemberID | FirstName | LastName | Spouse_Name | Child_Name1 | Child_Name2
--------------------------------------------------------------------------------
1 | John | Harris | Amy Harris | NULL | NULL
2 | Sarah | Thompson | Bryan Thompson | Dewey Thompson | Tom Thompson
3 | Zack | Lewis | Mika Lewis | Harry Lewis | NULL
ACTUAL RESULTS
MemberID | FirstName | LastName | Spouse_Name | Child_Name
-------------------------------------------------------------------
1 | John | Harris | Amy Harris | NULL
2 | Sarah | Thompson | Bryan Thompson | NULL
2 | Sarah | Thompson | NULL | Dewey Thompson
2 | Sarah | Thompson | NULL | Tom Thompson
3 | Zack | Lewis | Mika Lewis | NULL
3 | Zack | Lewis | NULL | Harry Lewis
While my query returns the "correct" data in multiple rows, it does not combine the result into one single row as needed.
The suggestion for Pivot Tables / Crosstabs has been mentioned below but every reference I am able to find suggests using mathematic calculations or that the number of fields to be returned is known. I will not know this information as a single member COULD have up to 100 dependents (although more like 4-8)
UPDATE #1
I feel I am getting closer to the final solution. I added the function GROUP_CONCAT to my query which returns ALL firstnames in a single column and ALL last names in a single column but still need to break them out into their own individual columns.
New function is:
SELECT
t1.MemberID,
t1.FirstName,
t1.LastName,
GROUP_CONCAT(t2.FirstName) AS Dep_Firstnames,
GROUP_CONCAT(t2.LastName) AS Dep_LastNames
FROM
member_information t1
LEFT OUTER JOIN member_dependent_information t2
ON t1.MemberID = t2.MemberID
WHERE
t1.Status = 1
GROUP BY
t1.MemberID

Sometimes the first step to solving your problem is knowing what it's called. After that, it's simply a matter of googling. What you are trying to create is called a pivot table or crosstab report. Here is a link explaining how to create pivot tables in MySQL. And here is a more in depth tutorial.
UPDATE:
Now that you've updated the question, I have a clearer idea of what you are trying to accomplish. I'll give you an alternative solution which is similar but not exactly what you want based on MySQL's GROUP_CONCAT function.
select t1.FirstName, t1.LastName, group_concat(concat(t2.FirstName, ' ', t2.LastName))
from member_information as t1
left outer join member_dependent_information as t2 on t2.MemberID=t1.MemberID
group by t1.MemberID;
I've verified this query as follows. First the setup:
create table member_information (
MemberID int unsigned auto_increment primary key,
FirstName varchar(32) not null,
LastName varchar(32) not null
) engine=innodb;
create table member_dependent_information (
MemberID int unsigned not null,
FirstName varchar(32) not null,
LastName varchar(32) not null,
Type int unsigned not null,
foreign key (MemberID) references member_information(MemberID)
) engine=innodb;
insert into member_information (MemberID, FirstName, LastName) values
(1, 'John', 'Harris'),
(2, 'Sarah', 'Thompson'),
(3, 'Zack', 'Lewis');
insert into member_dependent_information (MemberID, FirstName, LastName, `Type`) values
(1, 'Amy', 'Harris', 1),
(2, 'Bryan', 'Thompson', 1),
(2, 'Dewey', 'Thompson', 2),
(2, 'Tom', 'Thompson', 2),
(3, 'Harry', 'Lewis', 2),
(3, 'Minka', 'Lewis', 1);
And now the query and results:
mysql> select t1.FirstName, t1.LastName, group_concat(concat(t2.FirstName, ' ', t2.LastName))from member_information as t1
-> left outer join member_dependent_information as t2 on t2.MemberID=t1.MemberID
-> group by t1.MemberID;
+-----------+----------+------------------------------------------------------+
| FirstName | LastName | group_concat(concat(t2.FirstName, ' ', t2.LastName)) |
+-----------+----------+------------------------------------------------------+
| John | Harris | Amy Harris |
| Sarah | Thompson | Bryan Thompson,Dewey Thompson,Tom Thompson |
| Zack | Lewis | Harry Lewis,Minka Lewis |
+-----------+----------+------------------------------------------------------+
3 rows in set (0.00 sec)

I'm not sure how the OP transformed from having programID and Status to how it is now, but the closest thing I would be able to get is (which does not require pivot tables):
SELECT t1.MemberID,
t1.FirstName,
t1.LastName,
concat(t2.FirstName, ' ', t2.LastName) as Spouse_Name,
group_concat(concat(t3.FirstName, ' ', t3.LastName) ORDER BY t3.FirstName) as Children_names
FROM member_information t1
LEFT JOIN member_dependent_information t2 ON (t1.MemberID=t2.MemberID AND t2.Type=1)
LEFT JOIN member_dependent_information t3 ON (t1.MemberID=t3.MemberID and t3.Type=2)
GROUP BY MemberID;
Which produces:
+----------+-----------+----------+----------------+-----------------------------+
| MemberID | FirstName | LastName | Spouse_Name | Children_names |
+----------+-----------+----------+----------------+-----------------------------+
| 1 | John | Harris | Amy Harris | NULL |
| 2 | Sarah | Thompson | Bryan Thompson | Dewey Thompson,Tom Thompson |
| 3 | Zack | Lewis | Minka Lewis | Harry Lewis |
+----------+-----------+----------+----------------+-----------------------------+
and it would be "easy" with any programming language to extract that Children_names into separate columns.

This is not exactly what you are looking for, but it might be a step in the right direction.
SELECT
Table1.memberIDs,
Table1.firstname,
Table1.lastnames,
Table2.programIDs,
Table3.description
FROM
Table1,
Table2,
Table3
WHERE
Table1.memberIDs = Table2.memberIDs AND
Table2.programIDs = Table3.programID

Related

What does the equal sign mean in this SQL Join statement?

I'm new to sql and do not understand what this join statement is doing. Does this statement ON people.state_code=states.state_abbrev mean that people.state_code and states.state_abbrev are now one?
SELECT people.first_name,
people.state_code, states.division
FROM people
JOIN states ON people.state_code=states.state_abbrev;
It will take the columns first_name and state_code from the table people and the column division from the table states and put them together in a join table where the entries in the state_code and state_abbrev columns match. The join table is produced only for display in response to this query; the underlying tables with the data entries are not amended.
In this case the '=' means equal (like values are equal) and is part of the join condition based on which data is retrieved by the select statement. You are 'linking' the two tables based on a condition so you can retrieve related data...
Relational data base - there are relations between tables and between data.
For example:
table_1
PERSON_ID FIRST_NAME LAST_NAME ADDRESS_ID
1 |John |Doe |2
table_2
ADRESS_ID STREET
1 | 5th Avenue
2 | 1st Street
SELECT FIRST_NAME, STREET
FROM table_1 t1
JOIN table_2 t2 ON t1.ADDRESS_ID = t2.ADDRESS_ID;
will return
John, 1st Street
Does this statement ON people.state_code=states.state_abbrev mean that people.state_code and states.state_abbrev are now one?
Answer: NO. people.state_code and states.state_abbrev should be the same value on the respective tables.
Let me give you an example taken from https://www.mysqltutorial.org/mysql-join/
Suppose you have below tables:
CREATE TABLE members (
member_id INT AUTO_INCREMENT,
members_name VARCHAR(100),
PRIMARY KEY (member_id)
);
CREATE TABLE committees (
committee_id INT AUTO_INCREMENT,
committees_name VARCHAR(100),
PRIMARY KEY (committee_id)
);
Some data examples:
+-----------+--------+
| member_id | members_name |
+-----------+--------+
| 1 | John |
| 2 | Jane |
| 3 | Mary |
| 4 | David |
| 5 | Amelia |
+-----------+--------+
+--------------+--------+
| committee_id | committees_name |
+--------------+--------+
| 1 | John |
| 2 | Mary |
| 3 | Amelia |
| 4 | Joe |
+--------------+--------+
To do the INNER JOIN we can use members_name and committees_name not the id because they are auto_increment and the data are not related.
So the query would be:
SELECT
m.member_id,
m.members_name AS member,
c.committee_id,
c.committees_name AS committee
FROM members m
INNER JOIN committees c ON c.name = m.name;
Giving below result:
+-----------+--------+--------------+-----------+
| member_id | member | committee_id | committee |
+-----------+--------+--------------+-----------+
| 1 | John | 1 | John |
| 3 | Mary | 2 | Mary |
| 5 | Amelia | 3 | Amelia |
+-----------+--------+--------------+-----------+
Conclusion: The values of the columns are equaly the same

How to loop an Update statement so that it updates all possible unique combinations?

I have an update statement that updates a table with a concatenated name generated from a random combination of three other tables (First, Middle, and Last) to create a random full name. I use alter table Full add unique index(full); to ensure that no name is used twice, and any time it would use a name that is already in use it instead makes it null.
The tables I am using give me a possible 27 unique names. I want to figure out how to loop my update statement so that it fills in every possible unique combination.
db-fiddle table
create table First (id int(10), first varchar(255));
Insert into First (id, first) values (1, 'John'), (2, 'Bill'), (3, 'Chad');
create table Middle (id int(10), middle varchar(255));
Insert into Middle (id, middle) values (1, 'Ethan'), (2, 'Dave'), (3, 'Ron');
create table Last (id int(10), last varchar(255));
Insert into Last (id, last) values (1, 'Smith'), (2, 'Miller'), (3, 'Darnold');
create table Full (id int(10) auto_increment primary key, full varchar(255));
insert into Full (id) values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14), (15), (16), (17), (18), (19), (20), (21), (22), (23), (24), (25), (26), (27), (28), (29), (30);
This is my current Update statement
alter table Full add unique index(full);
Update ignore Full u1
join (select id,
#i:=Floor(1+ RAND() * 3),
#j:=Floor(1+ RAND() * 3),
#k:=Floor(1+ RAND() * 3),
(select concat(l.last, ', ', f.first, ' ', m.middle)
from First as f
join Middle as m on m.id = #j
join Last as l on l.id = #k
where f.id = #i) full
from Full) u2
on u1.id = u2.id
set u1.full = u2.full;
I can't loop it as is because it generates a full list of names every time. I need to change it so that after the first run it only updates null names if any possible combinations are left. I have experimented with inserting it into a case statement, but I can't get it to work correctly. As it is, it just changes one of the names in the table to 1, so its obviously not working as intended.
Update ignore Full
join ( select id,
#i:=Floor(1+ RAND() * 3),
#j:=Floor(1+ RAND() * 3),
#k:=Floor(1+ RAND() * 3),
( select concat(l.last, ', ', f.first, ' ', m.middle)
from First as f
join Middle as m on m.id = #j
join Last as l on l.id = #k
where f.id = #i) full
From Full) u2
Set u1.full = Case
when u1.full is null
then u1.full = u2.full
else u1.full = u1.full
end;
If it was working as intended it would fill the all the null rows except 3, since that is all the unique combinations that are possible given my small table size. I want to make sure it's working as intended before I scale up the tables.
Edit:
I don't actually NEED every possible combination. In my actual table I do need every null row filled with a unique combination, and as it is now when a name is duplicated it is null. So I need to fill those while also not duplicating a name already in the table.
Edit2:
I have changed it to this
Update ignore Full f1
join (select id,
(SELECT concat(last, ', ', first, ' ', middle) fullname FROM First,Middle,Last ORDER BY RAND() limit 1) fullname
from Full ) f2
on f1.id = f2.id
set f1.full = f2.fullname;
Which works. Now I just need it to loop until there are no null rows left.
Works for me:
SELECT first, middle, last FROM first,middle,last ORDER BY RAND();
+-------+--------+---------+
| first | middle | last |
+-------+--------+---------+
| Bill | Ethan | Smith |
| John | Dave | Miller |
| Bill | Ron | Darnold |
| Bill | Ethan | Darnold |
| John | Ethan | Miller |
| Chad | Ethan | Darnold |
| Chad | Ethan | Smith |
| Bill | Ron | Miller |
| John | Dave | Darnold |
| Chad | Dave | Miller |
| Chad | Ron | Miller |
| John | Ethan | Darnold |
| Chad | Dave | Smith |
| Bill | Ron | Smith |
| John | Dave | Smith |
| Bill | Ethan | Miller |
| John | Ron | Smith |
| John | Ethan | Smith |
| Bill | Dave | Smith |
| Chad | Dave | Darnold |
| Chad | Ethan | Miller |
| John | Ron | Miller |
| Chad | Ron | Darnold |
| John | Ron | Darnold |
| Bill | Dave | Darnold |
| Bill | Dave | Miller |
| Chad | Ron | Smith |
+-------+--------+---------+
27 rows in set (0.00 sec)

Same Join Constraints on Multiple Columns

We have a situation that I have been able to recreate with the following simple example. I have the following two sample tables:
CREATE TABLE contact_info
(
id INT UNSIGNED AUTO_INCREMENT,
priContactId INT,
secContactId INT,
blahBlah VARCHAR(32),
PRIMARY KEY(id)
);
and
CREATE TABLE name_lookup
(
id INT UNSIGNED AUTO_INCREMENT,
contactID INT,
contactName VARCHAR(32),
PRIMARY KEY(id)
);
I populate them as follows:
INSERT INTO contact_info(priContactId, secContactId, blahBlah) VALUES(1, 3, "Team A"), (4, 2, "Team B");
INSERT INTO name_lookup(contactID, contactName) VALUES(1, "John Doe"), (2, "Mary Smith"), (3, "Jose Garcia"), (4, "Larry Brown");
Obviously, the contents of the tables are as follows:
+----+--------------+--------------+----------+
| id | priContactId | secContactId | blahBlah |
+----+--------------+--------------+----------+
| 1 | 1 | 3 | Team A |
| 2 | 4 | 2 | Team B |
+----+--------------+--------------+----------+
+----+-----------+-------------+
| id | contactID | contactName |
+----+-----------+-------------+
| 1 | 1 | John Doe |
| 2 | 2 | Mary Smith |
| 3 | 3 | Jose Garcia |
| 4 | 4 | Larry Brown |
+----+-----------+-------------+
We would like to perform a JOIN operation so that we get output like this:
+-------------+-------------+--------+
| John Doe | Jose Garcia | Team A |
+-------------+-------------+--------+
| Larry Brown | Mary Smith | Team B |
+-------------+-------------+--------+
The join constraints for both the priContactId and secContactId columns are the same and I am having tough time figuring out what the JOIN query should look like.
FYI, we are using MySQL version 5.6.49.
Two separate columns needs 2 separate table copies join.
SELECT t1.contactName name1, t2.contactName name1, t3.blahBlah team
FROM name_lookup t1
JOIN name_lookup t2
JOIN contact_info t3 ON t1.contactID = t3.priContactId
AND t2.contactID = t3.secContactId
This would normally be handled with two joins. You don't have not null constraints on the two contacts, so I would suggest outer joins:
select nl_pri.contactName as PrimaryName, nl_sec.contactName as SecondaryName,
ci.blahblah
from contact_info ci left join
name_lookup nl_pri
on ci.priContactId = nl_pri.contactId left join
name_lookup nl_sec
on c.secContactId = nl_sec.contactId;
Notes:
The left join keeps all contacts, even those that don't have both primary and secondary.
Table aliases make the query easier to write and read. The table aliases should be meaningful.
You should define the foreign key relationships, so it is clear that the contact ids refer to the name_lookup table.

MySQL junction table concatenation

I have an requirement to return all educators associated with a session as a concatenated list, but to do it within a join (don't ask - I know there are multiple ways of doing it but I'm working with a library that won't accept those methods ;-))
I have a sessions table with session_id and session_name.
| session_id | session_name |
+------------+--------------+
| 1 | Swimming |
| 2 | Chess |
I have a session_educators table which is basically a junction table session_id and contact_id.
| session_id | contact_id |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 4 |
I have a contacts table with contact_id and full_name
| contact_id | full_name |
+------------+--------------+
| 1 | Fred Bloggs |
| 2 | Mary Bloggs |
| 3 | Mark Smith |
| 4 | Shelly Smith |
So far this seems to be the closest I've come:
SELECT
sessions.session_id,
sessions.name,
educators.names
FROM
`sessions`
LEFT JOIN
(
SELECT
GROUP_CONCAT(contacts.full_name SEPARATOR ', ') as names
FROM
`contacts`
WHERE
contact_id
IN
(
SELECT
contact_id
FROM
session_educator
WHERE
session_educator.session_id = sessions.session_id
)
) `educators`
USING
(`session_id`)
But I'm really fumbling in the dark trying to figure it out, can anyone help?
What I'm wanting, as you can probably tell from the query, is a result like this:
| session_id | session_name | educators |
+------------+--------------+--------------------------+
| 1 | Swimming | Fred Bloggs, Mary Bloggs |
| 2 | Chess | Mark Smith, Shelly Smith |
Any help greatly appreciated - even if it's just to say it can't be done!
You shouldn't need subqueries for this. It is a few joins and an aggregation:
select s.session_id, s.session_name,
group_concat(e.name separator ', ') as educators
from sessions s left join
session_educators se
on se.session_id = s.session_id left join
educators e
on e.educator_id = se.educator_id
group by s.session_id;
I do believe you're making it all a little more complex than needed (unless I've missed something in your requirements? I know how much of a pain libraries can be...)
This worked for me:
SELECT
session_id,
session_name,
(
SELECT
GROUP_CONCAT(full_name SEPARATOR ', ') as names
FROM
contacts c,
session_educators se
WHERE
c.contact_id = se.contact_id
AND
se.session_id = s.session_id
)
FROM
sessions s
;

Populate empty database columns with !empty older row columns

Lets say I have a database containing the following rows:
id (auto_incremented) | id_person | first_name | last_name | phone | email
-----------------------------------------------------------------------------
1 | 12 | kevin | smith | | kevin#hotmail.com
2 | 12 | kevin | smith | 1-800-123-4567 |
3 | 33 | joe | jones | 1-800-765-4321 |
4 | 33 | joe | thompson | | joe#hotmail.com
5 | 33 | joe | thompson | | newjoe#hotmail.com
Based on id_person, I want to output the following in a single query:
id | id_person | first_name | last_name | phone | email
-----------------------------------------------------------------------------
2 | 12 | kevin | smith | 1-800-123-4567 | kevin#hotmail.com
5 | 33 | joe | thompson | 1-800-765-4321 | newjoe#hotmail.com
So basically, I just want to take the newest row values, and if they are empty, grab the values from the first row which isn't empty.
How do I do this? Hope this makes sense.
SELECT id_person, subQ.lastID
, GROUP_CONCAT(IF(subQ.lastFieldXId = tblFields.ID, tblFields.fieldX, NULL)) AS fieldX
, GROUP_CONCAT(IF(subQ.lastFieldYId = tblFields.ID, tblFields.fieldY, NULL)) AS fieldY
[, ...]
FROM
(SELECT id_person
, MAX(id) AS lastID
, MAX(IF(IFNULL(fieldX, '') = '', 0, id) AS lastFieldXId
, MAX(IF(IFNULL(fieldY, '') = '', 0, id) AS lastFieldYId
[, ....]
FROM theTable
GROUP BY id_person) AS subQ
LEFT JOIN theTable AS tblFields USING (id_person)
;
The LEFT JOIN may or may not be faster as
LEFT JOIN theTable AS tblFields
ON tblFields.ID IN (subQ.lastFieldXId, subQ.lastFieldYId [, ...])
There is probably also a way using session variables, but I don't have the energy...
select * from(
select p1.id,p1.id_person,p1.first_name,p1.last_name,
ifnull(p1.phone,p2ph.phone) phone ,ifnull(p1.email,p3em.email) email
from person p1
left join person p2ph on(p1.id_person=p2ph.id_person and p2ph.phone is not null)
left join person p3em on(p1.id_person=p3em.id_person and p3em.email is not null)
order by id_person,p1.id desc,p2ph.id desc,p3em.id desc
)t group by id_person;
verify at http://sqlfiddle.com/#!9/d2f44/6