SELECT query with a variable number of columns for each row - mysql

I created a table of employees. Some of those employees report to other employees defined in the same table. Let's call them managers.
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
managerID INT NULL,
FOREIGN KEY (managerID) REFERENCES employees (id) ON DELETE RESTRICT
) ENGINE=INNODB;
Then, some values are entered in the database:
INSERT INTO employees(id, name, managerID)
VALUES (1, "Ivan", NULL),
(2, "Peter", 1),
(3, "Alexandra", 1),
(4, "Maria", 2),
(5, "Marian", 2),
(6, "Margarita", 3),
(7, "Mihail", 3),
(8, "Eva", 6);
I want a SELECT query which returns all employees and all the managers they report to. For now the next best thing I have is this:
SELECT employee.name AS Name, manager.name AS `Reports to:`
FROM employees,
LEFT JOIN employees AS manager ON employee.managerID = manager.id;
Which has predictable output:
Employee Reports to:
Ivan NULL
Peter Ivan
Alexandra Ivan
Maria Peter
Marian Peter
Margarita Alexandra
Mihail Alexandra
Eva Margarita
What I want is a such SELECT query that will return a variable number of columns based on the fact how many managers a single employee has. It's apparent to me that a recursive SELF JOIN will be required, but I don't know how to tell the DBMS I want a variable number of columns for each row and I don't know how to phrase the condition.
Desired output:

I understand that you want to generate a resultset where each record corresponds to an employee, with columns that show all managers of that employee (manager of the employee, then manager of the manager, etc...).
The thing to keep in mind is that a SQL query must return a fixed set of columns. The RDBMS needs to know in advance how many columns will be returned by the query to be able to parse it and execute it.
Here is a solution that can handle up to 5 levels of management. It workds by extending your logic with more LEFT JOINs. It's not pretty, but it works. You can add mor joins to handle more management levels.
SELECT
e0.name AS Name,
e1.name AS Manager1,
e2.name AS Manager2,
e3.name AS Manager3,
e4.name AS Manager4,
e5.name AS Manager5
FROM employees e0
LEFT JOIN employees e1 ON e1.id = e0.managerID
LEFT JOIN employees e2 ON e2.id = e1.managerID
LEFT JOIN employees e3 ON e3.id = e2.managerID
LEFT JOIN employees e4 ON e4.id = e3.managerID
LEFT JOIN employees e5 ON e5.id = e5.managerID
In this DB Fiddle with your sample data, the query returns:
| Name | Manager1 | Manager2 | Manager3 | Manager4 | Manager5 |
| --------- | --------- | --------- | -------- | -------- | -------- |
| Ivan | | | | | |
| Peter | Ivan | | | | |
| Alexandra | Ivan | | | | |
| Maria | Peter | Ivan | | | |
| Marian | Peter | Ivan | | | |
| Margarita | Alexandra | Ivan | | | |
| Mihail | Alexandra | Ivan | | | |
| Eva | Margarita | Alexandra | Ivan | | |

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

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.

Joining two tables with comma separated string id in one table

This is branch detail table which have the branch name and the employee id work on that branch.
CREATE TABLE Branch_detail
(`nid` int,`branch` varchar(15), `emp_with_department` varchar(15));
INSERT INTO Branch_detail
(`nid`,`branch`, `emp_with_department`)
VALUES
(1,'Mumbai', '1,2,4'),
(2,'Banglore', '4,5');
| nid | branch | emp_with_department |
|-----|----------|---------------------|
| 1 | Mumbai | 1,2,4 |
| 2 | Banglore | 4,5 |
This is employee detail table with their respective designations.
CREATE TABLE Emp
(`id` int, `name` varchar(10), `Designations ` varchar(10));
INSERT INTO Emp
(`id`, `name`,`Designations `)
VALUES
(1,'Rantesh', 'Executive'),
(2,'Keith', 'Art'),
(3,'Nikhil', 'Executive'),
(4,'Gauresh', 'Art'),
(5,'Sumit', 'Executive');
| id | name | Designations |
|----|---------|--------------|
| 1 | Rantesh | Executive |
| 2 | Keith | Art |
| 3 | Nikhil | Executive |
| 4 | Gauresh | Art |
| 5 | Sumit | Executive |
This query i tried to get atleast which id is Executive or Art employee
SELECT a.nid,
GROUP_CONCAT(b.Designations ORDER BY b.id) Designations_Name
FROM Branch_detail a INNER JOIN Emp b
ON FIND_IN_SET(b.id, a.emp_with_department) > 0
GROUP BY a.nid
| nid | Designations_Name|
|-----|-------------------|
| 1 | Executive,Art,Art |
| 2 | Art,Executive |
Output should be:
count of Executives for nid=1,
count of Art for nid=1,
count of executives for nid=2,
count of Art for nid=1
OR
| nid | count(Designations_Name)|
|-----|--------------------------|
| 1 | 1,2,2 |
| 2 | 1,1 |
I want the Output like there on n location, how many executive and art employee is present in count ?
this is sqlfiddle where the demo is present
http://sqlfiddle.com/#!9/27aa51/1
Although you should implement Normalization.
But still, here is a query which will work for you, utilizing Nested Queries:
select inner_nest.nid, GROUP_CONCAT(designations_count)
FROM (SELECT a.nid, b.Designations, COUNT(DISTINCT b.id) as designations_count
FROM Branch_detail a
INNER JOIN Emp b ON FIND_IN_SET(b.id, a.emp_with_department) > 0
WHERE 1 = 1
GROUP BY a.nid, b.Designations ) AS inner_nest
GROUP BY inner_nest.nid
It gets Count for Art first, then Executives (as alphabetically Art comes first)

One to many relation in MYSQL produces wrong results

I have two tables- students and subjects. Student table stores list of all student and subject table stores all the subjects that these students have been enrolled into.
CREATE TABLE students
(`id` int, `name` varchar(7));
INSERT INTO students
(`id`, `name`)
VALUES
(1, 'Jason'),
(2, 'Matt'),
(3, 'Abram')
;
CREATE TABLE subjects
(`id` int,`student_id` int, `subject` varchar(15));
INSERT INTO subjects
(`id`,`student_id`, `subject`)
VALUES
(1,1, 'Math'),
(2,1, 'Physics'),
(3,2, 'Chemistry'),
(4,2, 'Math'),
(5,2, 'English'),
(6,3, 'Chemistry')
;
And upon executing the following query
SELECT STUD.id,STUD.name,SUB.subject
FROM students AS STUD
LEFT JOIN subjects AS SUB
ON STUD.id=SUB.student_id
;
gives a result set like this (which is not what I am trying to get)
++++++++++++++++++++++++++++++++++++
id | name | subject |
++++++++++++++++++++++++++++++++++++
| 1 | Jason | Math |
------------------------------------
| 1 | Jason | Physics |
------------------------------------
| 2 | Matt | Chemistry |
------------------------------------
| 2 | Matt | Math |
------------------------------------
| 2 | Matt | English |
------------------------------------
| 3 | Abram | Chemistry |
------------------------------------
The students gets repeated in the list.
And when I try
SELECT STUD.id,STUD.name,SUB.subject
FROM students AS STUD
LEFT JOIN subjects AS SUB
ON STUD.id=SUB.student_id
GROUP BY STUD.id
;
I get (which is again wrong)
++++++++++++++++++++++++++++++++++++
id | name | subject |
++++++++++++++++++++++++++++++++++++
| 1 | Jason | Math |
------------------------------------
| 2 | Matt | Chemistry |
------------------------------------
| 3 | Abram | Chemistry |
------------------------------------
My Desired result looks like this:
++++++++++++++++++++++++++++++++++++++++++++++++
id | name | subject |
++++++++++++++++++++++++++++++++++++++++++++++++
| 1 | Jason | Math, Physics |
------------------------------------------------
| 2 | Matt | Chemistry,Math ,English |
------------------------------------------------
| 3 | Abram | Chemistry |
------------------------------------------------
I am a novice in Relational databases.
Why is group by not giving correct result?
Thanks in advance.
SQL FIDDLE
You can achieve this by
SELECT STUD.id,STUD.name,GROUP_CONCAT(SUB.subject) as subject
FROM students AS STUD
LEFT JOIN subjects AS SUB
ON STUD.id=SUB.student_id
GROUP BY STUD.id;
Actually it gives correct result when you have group by some column in sql it will returns only first record in group so for your desired result you needs to used GROUP_CONCAT function which return all comma separated values in given column.

Can a MySQL query turn rows into columns?

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