Return multiple values in one column - sql

I have two tables
Employee:
Empid Ename Eage Eadd Ephone
1 x 23 b 677
2 y 24 h 809
3 z 34 u 799
Department:
Did fkEmpid dname ddescription
123 1 test test
234 1 test1 test1
667 2 hello hello
Finally I want something like this
Ename Eage Eadd Ephone dname
x 23 b 677 test,test1
y 24 h 809 hello
z 34 u 799 null
Please help me with the SQL

It certainly would be nice to know the target RDBMS. But this question is asked so often so let's try and list'em all (at least popular ones) side by side.
For SQL Server:
SELECT e.Ename, e.Eage, e.Eadd, e.Ephone, d.dname
FROM Employee e LEFT JOIN
(
SELECT fkEmpid,
STUFF((SELECT ',' + dname
FROM Department
WHERE fkEmpid = t.fkEmpid
FOR XML PATH('')) , 1 , 1 , '' ) dname
FROM Department t
GROUP BY fkEmpid
) d
ON e.Empid = d.fkEmpid
Here is SQLFiddle demo
For Mysql, SQLite, HSQLDB 2.X:
SELECT e.Ename, e.Eage, e.Eadd, e.Ephone, d.dname
FROM Employee e LEFT JOIN
(
SELECT fkEmpid,
GROUP_CONCAT(dname) dname
FROM Department t
GROUP BY fkEmpid
) d
ON e.Empid = d.fkEmpid
Here is SQLFiddle demo (MySql)
Here is SQLFiddle demo (SQLite)
For Oracle 11g:
SELECT e.Ename, e.Eage, e.Eadd, e.Ephone, d.dname
FROM Employee e LEFT JOIN
(
SELECT fkEmpid,
LISTAGG (dname, ',') WITHIN GROUP (ORDER BY dname) dname
FROM Department t
GROUP BY fkEmpid
) d
ON e.Empid = d.fkEmpid
Here is SQLFiddle demo
For PostgreSQL 9.X:
SELECT e.Ename, e.Eage, e.Eadd, e.Ephone, d.dname
FROM Employee e LEFT JOIN
(
SELECT fkEmpid,
string_agg(dname, ',') dname
FROM Department t
GROUP BY fkEmpid
) d
ON e.Empid = d.fkEmpid
Here is SQLFiddle demo
Output in all cases:
| ENAME | EAGE | EADD | EPHONE | DNAME |
---------------------------------------------
| x | 23 | b | 677 | test,test1 |
| y | 24 | h | 809 | hello |
| z | 34 | u | 799 | (null) |

Considering RDBMS as SQL SERVER 2008
select E.Ename,E.Eage,E.Eadd,E.Ephone,D.dname
into Table1
from Employee E
left join Deparment D on E.Empid=D.fkEmpid
select t1.[Ename], t1.[Eage], t1.[Eadd], t1.[Ephone],
STUFF((
SELECT ', ' + t2.dname
FROM Table1 t2
WHERE t2.Ename = t1.Ename
AND t2.Eage=t1.Eage
AND t2.Eadd=t1.Eadd
AND t2.Ephone=t1.Ephone
FOR XML PATH (''))
,1,2,'') AS Names
FROM Table1 t1
GROUP BY t1.Ename,t1.[Eage], t1.[Eadd], t1.[Ephone];
SQL FIDDLE

Related

How to use GROUP BY when fetching values from More than one Table [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 4 months ago.
We have 2 Tables Employees and Department.
We want to show the maximum salary from each department and their corresponding employee name from the employee table and the department name from the department table.
Employee Table
EmpId | EmpName |salary |DeptId
101 shubh1 1000 1
101 shubh2 4000 1
102 shubh3 3000 2
102 shubh4 5000 2
103 shubh5 12000 3
103 shubh6 1000 3
104 shubh7 1400 4
104 shubh8 1000 4
Department Table
DeptId | DeptName
1 ComputerScience
2 Mechanical
3 Aeronautics
4 Civil
I tried doing it but was getting error
SELECT DeptName FROM Department where deptid IN(select MAX(salary),empname,deptid
FROM Employee
GROUP By Employee.deptid)
Error
Token error: 'Column 'Employee.EmpName' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.' on server 4e0652f832fd executing on line 1 (code: 8120, state: 1, class: 16)
Can someone please help me.
select salary
,EmpName
,DeptName
from (
select e.salary
,e.EmpName
,d.DeptName
,rank() over(partition by e.DeptId order by e.salary desc) as rnk
from Employee e join Department d on d.DeptId = e.DeptId
) t
where rnk = 1
salary
EmpName
DeptName
4000
shubh2
ComputerScience
5000
shubh4
Mechanical
12000
shubh5
Aeronautics
1400
shubh7
Civil
Fiddle
Now that I know it's MS SQL Server, technically; we could use cross or outer Apply; it's a table value function not a join per say... but this will depend on the version of SQL Server; and if you want data if it doesn't exist in another
I find this the "Best" Design pattern to use for this type of query.
What the engine does is for each record in department, it runs a query for the employees Finding those in that department returning the 1 record having the max salary. With top we could specify with ties to return more than one. but we would need to know how to handle Ties of salary. Use top 1 with ties or order the results so you get the "Top" result you want.
Demo: dbfddle.uk
SELECT Sub.empName, Sub.Salary, D.DeptName
FROM Department D
CROSS Apply (SELECT Top 1 *
--(SELECT TOP 1 with Ties * -- could use this if we ties
FROM Employee E
WHERE E.DeptID = D.DeptID
ORDER BY Salary Desc) Sub --add additional order by if we don't want ties.
The cross apply gives us:
+---------+--------+-----------------+
| empName | Salary | DeptName |
+---------+--------+-----------------+
| shubh2 | 4000 | ComputerScience |
| shubh4 | 5000 | Mechanical |
| shubh5 | 12000 | Aeronautics |
| shubh7 | 1400 | Civil |
+---------+--------+-----------------+
Before window functions, before cross Apply or lateral... We'd write an inline view
It would get us the max salary for each dept, we then join that back to our base tables to find the employee within each dept with max salary...
Demo: DbFiddle.uk
SELECT E.*, D.*
FROM Employee E
INNER JOIN Department D
on E.DeptID = D.DeptID
INNER JOIN (SELECT MAX(SALARY) maxSal , DeptID
FROM Employee
GROUP BY DeptID) Sub
on Sub.DeptID = E.DeptID
and Sub.MaxSal = E.Salary
One has to do a join to get the department info an the employee info. However, we can eliminate the join for salarymax by using exists and correlation instead.
Demo DbFiddle.uk
SELECT E.*, D.*
FROM Employee E
INNER JOIN Department D
on E.DeptID = D.DeptID
WHERE EXISTS (SELECT MAX(Sub.SALARY) maxSal , Sub.DeptID
FROM Employee Sub
WHERE sub.DeptID=E.DeptID --correlation 1
GROUP BY Sub.DeptID
HAVING E.Salary = max(Sub.Salary)) --correlation 2
We could eliminate the last join too I suppose:
Demo: Dbfiddle.uk
SELECT E.*, (SELECT DeptName from Department where E.DeptID = DeptID)
FROM Employee E
WHERE EXISTS (SELECT MAX(Sub.SALARY) maxSal , Sub.DeptID
FROM Employee Sub
WHERE sub.DeptID=E.DeptID --correlation 1
GROUP BY Sub.DeptID
HAVING E.Salary = max(Sub.Salary)) --correlation 2
The top 3 give us this result:
+-----+---------+--------+--------+--------+-----------------+
| id | empName | salary | deptID | DeptID | DeptName |
+-----+---------+--------+--------+--------+-----------------+
| 101 | shubh2 | 4000 | 1 | 1 | ComputerScience |
| 102 | shubh4 | 5000 | 2 | 2 | Mechanical |
| 103 | shubh5 | 12000 | 3 | 3 | Aeronautics |
| 104 | shubh7 | 1400 | 4 | 4 | Civil |
+-----+---------+--------+--------+--------+-----------------+

Update using joins by finding the previous value in sql

I have two tables and I have to update the address field in the emp table by looking up in the emphistory table with previous value i.e USA for employee John
Table emp
EId ename sal Address AccountId
-------------------------------
101 John 100 U X12
102 Peter 500 Null X13
Table emphistory
emphisid EId AccountId Address Date (use row_number to find the second record for that eid and accountid)
-----------------------------------------------------
1 101 X12 U 11-01-2020 09:45:00
2 102 X13 Null 11-01-2020 09:46:00
3. 101 X12 USA 11-01-2020 09:30:00
I have to join the tables with account id and eid.
This works in Postgresql & Sql Server
UPDATE emp
SET Address = hist.Address
FROM (
SELECT h.EId, h.AccountId, h.Address
, ROW_NUMBER() OVER (PARTITION BY h.EId, h.AccountId
ORDER BY h.Date DESC) AS Rn
FROM emphistory h
JOIN emp e
ON e.EId = h.EId
AND e.AccountId = h.AccountId
WHERE h.Address IS NOT NULL
) hist
WHERE emp.EId = hist.EId
AND hist.Rn = 2

Select rows where every child row meets a condition

In my Oracle DB, I have two tables in a one-to-many relationship: Managers and Employees.
+------------+-------+------------+
| Manager_ID | Name | Department |
+------------+-------+------------+
| 1 | Steve | Sales |
| 2 | Ben | Sales |
| 3 | Molly | Accounts |
+------------+-------+------------+
+-------------+------------+--------+-----+
| Employee_ID | Manager_ID | Name | Age |
+-------------+------------+--------+-----+
| 1 | 1 | Kyle | 25 |
| 2 | 1 | Gary | 31 |
| 3 | 2 | Renee | 31 |
| 4 | 2 | Oliver | 32 |
+-------------+------------+--------+-----+
How do I select only those Managers where every one of his Employees is over the age of 30?
In my example data, the only Manager who meets this condition is Ben, because both of his employees are over 30.
I thought something like this would do it, but it's wrong:
SELECT m.manager_id
FROM managers m
WHERE m.manager_id IN (SELECT e.manager_id
FROM employees e
GROUP BY e.manager_id
HAVING e.age > 30)
Use not exists :
select m.*
from manager m
where not exists (select 1
from Employees e
where e.Manager_ID = m.Manager_ID and e.Age < 30
) and
exists (select 1 from Employees e where e.Manager_ID = m.Manager_ID)
The only thing I don't like about Yogesh's answer (which I upvoted, since it's probably the way I'd write it) is that you have to go to the employees table a second time, to make sure the manager actually has at least one employee.
On the plus side, the NOT EXISTS that Yogesh used will allow Oracle to stop looking at a manager's employees once it finds one that is too young. So, maybe it's a toss-up.
I'll offer this alternative. It is shorter than the NOT EXISTS and does not have to go to the employees table a second time.
SELECT m.*
FROM manager m
CROSS APPLY (
SELECT min(age) min_age
FROM employee e
WHERE e.manager_id = m.manager_id ) ma
where ma.min_age >= 30;
Using sub-query for counts
SQL> WITH manager(Manager_ID, Name, Department) AS (
2 SELECT 1, 'Steve', 'Sales' FROM dual UNION ALL
3 SELECT 2, 'Ben', 'Sales' FROM dual UNION ALL
4 SELECT 3, 'Molly', 'Accounts' FROM dual),
5 employee(Employee_ID, Manager_ID, Name, Age) AS (
6 SELECT 1 , 1, 'Kyle', 25 FROM dual UNION ALL
7 SELECT 2 ,1, 'Gary', 31 FROM dual UNION ALL
8 SELECT 3, 2, 'Renee', 31 FROM dual UNION ALL
9 SELECT 4, 2 , 'Oliver', 32 FROM dual)
10 ---------------------------
11 --- End of data preparation
12 ---------------------------
13 SELECT m.name
14 FROM manager m
15 JOIN (SELECT manager_id,
16 COUNT(1) total,
17 COUNT(CASE WHEN age > 30 THEN 1 ELSE NULL END) age_30_above
18 FROM employee
19 GROUP BY manager_id) ee
20 ON m.manager_id = ee.manager_id
21 WHERE total = age_30_above;
Output
NAME
-----
Ben
Your query will be:
SELECT m.name
FROM manager m
JOIN (SELECT manager_id,
COUNT(1) total,
COUNT(CASE WHEN age > 30 THEN 1 ELSE NULL END) age_30_above
FROM employee
GROUP BY manager_id) ee
ON m.manager_id = ee.manager_id
WHERE total = age_30_above;
SELECT manager_id
FROM employees -- managers
minus
select manager_id
from employees
where age <= 30
You can use ALL function like this:
SELECT m.manager_id
FROM managers m
WHERE (30 <= ALL (SELECT e.age FROM employees e WHERE e.manager_id = m.manager_id));
You might want to reverse the conditions, select all managers, who dont have any employee below 30
select * from managers
where manager_id not in (select manager_id
from employees
where age < 30)

T-SQL - Concatenate Multiple Rows

I am struggling with a query to return a list of managers with their respective employees
I have three tables as follows:
Managers
ManagerID ManagerName
1 Bob
2 Sally
3 Peter
4 George
EmployeeManager
EmployeeID ManagerID
1 1
1 1
2 2
2 2
3 3
3 3
4 4
4 4
Employees
EmployeeID EmployeeName
1 David
1 Joseph
2 Adam
2 Pete
3 Mark
3 Mavis
4 Susan
4 Jennifer
Desired Result Set
ManagerName CountEmployee Employees
Bob 2 David, Joseph
Sally 2 Anish, Pete
Peter 2 Mark, Mavis
George 2 Susan, Jennifer
The query I am currently using is as follows:
Select m.ManagerName
,Count(e.EmployeeName) Over(Partition By m.ManagerID) as CountEmployee
,Rank() Over(Partition By m.ManagerID Order By em.EmployeeID) [RankEmployee]
,e.EmployeeName
From dbo.Employees e
Left Join dbo.EmployeeManager em on em.ManagerID=e.ManagerID
Left Join dbo.Managers m on m.ManagerID=em.ManagerID;
This returns a list of managers and employees on individual rows but I'm struggling to concatenate the employee names as per the above table.
Any ideas or solutions?
Manpaal Singh
You can stuff the result to comma seperated result.
Select m.ManagerName
,Count(e.EmployeeName) Over(Partition By m.ManagerID) as CountEmployee
,Rank() Over(Partition By m.ManagerID Order By em.EmployeeID) [RankEmployee]
,STUFF((SELECT ',' + e.EmployeeName
FOR XML PATH('')), 1, 1, '') AS EmployeeName
From dbo.Employees e
Left Join dbo.EmployeeManager em on em.ManagerID=e.ManagerID
Left Join dbo.Managers m on m.ManagerID=em.ManagerID
SELECT M.ManagerName, E.EmployeeName
FROM Managers AS M
INNER JOIN EmployeeManager AS EM ON M.ManagerID = EM.ManagerID
INNER JOIN Employees AS E ON EM.EmployeeID = E.EmployeeID
ORDER BY M.ManagerName
This will give a list of managers and their employees.
when you fix the ID's of the employees table.
1 David
1 Joseph
2 Adam
2 Pete
3 Mark
3 Mavis
4 Susan
4 Jennifer
should be:
1 David
2 Joseph
3 Adam
4 Pete
5 Mark
6 Mavis
7 Susan
8 Jennifer
you can use recursive sql to convert rows in a string :
with t1 (mngId, empName) as (
select a.mngId,
b.empname
from manager as a, employee as b
where b.mngId = a.mngId),
t2 (mngID, nbr, empName, all_name) as (
select mngId,
cast(1 as Int),
min(empName),
cast(min(empName) as varchar(1000)
from t1
group by mngId
union all
select b.mngId,
b.nbr+1,
a.empName,
trim(b.all_name) concat ', ' concat a.empName
from t0 as a, t1 as b
where b.mngId = a.mngId
and a.empName > b.empName
and a.empName = (
select min( c.empName)
from t0 as c
where c.mngId = b.mngId
and c.empName > b.empName )
)
select *
from t1 as e
where nbr = (
select max(nbr)
from t1 as d
where d.mngId = e.mngId )

SQL Server basics - where clause

This is the code I have:
use TSQL2012
select
emp.empid, emp.firstname, emp.mgrid,
manager.firstname as manager_name,
sum(unique(emp.mgrid)) as Total
from
HR.Employees AS emp, HR.Employees as manager
where
emp.mgrid = manager.empid
go
And the result I am supposed to get is:
empid firstame mgrid manager_name Total
1 2 Don 1 Sara 1
2 3 Judy 2 Don 2
3 4 Yael 3 Judy 2
4 5 Sven 2 Don 2
5 6 Paul 5 Sven
6 7 Russell 5 Sven
7 8 Maria 3 Judy
8 9 Zoya 5 Sven
I can't get the last column (Total). Any help would be appreciated
One way of doing it is by the use of CROSS APPLY:
select emp.empid, emp.firstname, emp.mgrid, manager.firstname as manager_name,
x.cnt as Total
from Employees AS emp
inner join Employees as manager on emp.mgrid = manager.empid
cross apply (
select count(*)
from Employees AS e
where e.mgrid = emp.mgrid) AS x(cnt)
Demo here
Edit:
You can also use a CTE in this way:
;With C as (
select b.mgrid, count(*) as Total
from Employees as b
group by mgrid
)
select emp.empid, emp.firstname, emp.mgrid, manager.firstname as manager_name,
Total
from Employees AS emp
inner join Employees as manager on emp.mgrid = manager.empid
inner join C on emp.mgrid = C.mgrid
Demo here