How do get name of max? SQL - sql

I have query:
select max(sumValues)
FROM (select dep.name AS sumName, sum(em.salary) AS sumValues
from EMPLOYEE em
INNER JOIN DEPARTMENT dep ON em.department_id=dep.id
group by dep.id) a
In result I have:
|max numeric|
-----------
200
But I want get in result name (dep.name) of max (max(sumValues)), How can I to do it?

The simplest way is to sort and take the first tuple:
select sumName, sumValues
FROM (
select dep.name AS sumName, sum(em.salary) AS sumValues
from EMPLOYEE em
INNER JOIN DEPARTMENT dep ON em.department_id=dep.id
group by dep.id
) a
ORDER BY sumValues DESC
LIMIT 1;
Otherwise you can use the HAVING clause and a subquery over a CTE term.
(LIMIT is a PostgreSQL extension)

Related

SQL - JOIN of two tables

I have two tables:
I am trying to answer this question:
Write a query that returns the cities and the number of employees associated with them. Show only cities where the average age of employees is greater than 25. Sort the table by the names of the cities in ascending order.
My answer is:
SELECT emp.Name, AVG(emp.Age), emp.id, city_emp.city
FROM Emp_det emp JOIN CityWorkers city_emp
ON emp.id = city_emp.id
GROUP BY city_emp.city
HAVING AVG(EMP.Age) > 25
ORDER BY city_emp.city
But this is inncorrect.
Can you help me please?
SELECT COUNT(emp.id) AS num_of_emp,
AVG(emp.Age) AS AVG_age,
city_emp.city
FROM Emp_det emp JOIN CityWorkers city_emp
ON emp.id = city_emp.id
GROUP BY city_emp.city
HAVING AVG(EMP.Age) > 25
ORDER BY city_emp.city

For each employee with dependent(s), list their last name and the name of their oldest dependent. Display the results in sequence by dependent’s name

SELECT E.Lname,D.Dependent_name
FROM EMPLOYEE as E , DEPENDENT as D
WHERE
E.ssn=D.Essn
AND
D.Bdate = (SELECT MIN(Bdate) FROM DEPENDENT
WHERE Bdate IS NOT NULL)
This does work but it just show for 1 employee
Link to DB:
https://www.db-fiddle.com/f/xhEj2sAgdTMABBkCtJvmoC/0#&togetherjs=z3CKywAccH
As it is, your subquery returns the minimum bdate over the whole table, resulting in only the empolyee that has the oldest dependant being filtered in. As commented by #PM77-1, you would need to correlate your subquery with the employee that you are currently processing.
SELECT E.Lname,D.Dependent_name
FROM
EMPLOYEE as E
INNER JOIN DEPENDENT as D ON E.ssn=D.Essn
WHERE D.Bdate = (
SELECT MIN(Bdate)
FROM DEPENDENT
WHERE Essn = E.ssn
)
ORDER BY 2, 1;
Another, probably more efficient option, is to use Postgres window function ROW_NUMBER() to assign a row number to each record in the group made of all dependents of a given employee, ordered by date of birth. An outer query can then filter in the first record in each group :
SELECT x.lname, x.dependent_name
FROM (
SELECT
e.lname,
d.dependent_name,
ROW_NUMBER() OVER(PARTITION BY e.ssn ORDER BY d.bdate) rn
FROM employee e
INNER JOIN dependent d ON e.ssn = d.essn
) x WHERE x.rn = 1
ORDER BY 2, 1;
Demo on DB Fiddle
select a.lname, a.dependent_name
from
(
select e.lname, d.dependent_name, d.bdate, e.ssn
from employee e, dependent d
where e.ssn=d.essn
order by d.dependent_name, e.lname
)a
where a.bdate= (select min(bdate) from dependent
where dependent.essn= a.ssn)
order by a.lname;
This displays the names of three employees > Smith, Wallace, Wong with their corresponding eldest dependents > Elizabeth, Abner, Joy.
Hope this solves the problem.

How the following SQL query is working?

How is the given query correct as it is using T1 inside the with clause and T1 is declared after the clause within WITH is completed.
WITH T1(Emp,Manager,Salary) AS
(
SELECT tt2.[Emp],tt2.[Manager],tt2.[Salary]
FROM [YourTable] AS tt1
RIGHT OUTER JOIN [YourTable] AS tt2 ON tt1.[Emp]=tt2.[Manager]
WHERE tt1.[Emp] is NULL
UNION ALL
SELECT r.[Emp],T1.[Manager],r.[Salary]
FROM [YourTable] AS r
INNER JOIN T1 ON r.[Manager]=T1.[Emp]
)
SELECT [Manager],SUM([Salary]) AS Salary
FROM T1
GROUP BY [Manager]
ORDER BY SUM([Salary]) DESC
Above query is answer to following question -
I have table with columns (Employee, Manager, Salary). Need to calculate aggregate salary for all employees corresponding to top-level managers in one SQL. For example
Input table is :
Emp Manager Salary
A T 10
B A 11
C F 13
D B 5
Result should be :
Top-Lvl Manager Salary(agg)
T 26
F 13
Manager-Employee layering can go multiple levels.
This is a recursive query. The part before UNION ALL gets the base records. The part after it recursively gets more rows attatched to the former.
The first part is confusingly written. It is an anti-join pattern inplemented even with a right outer join which many consider hard to read. It merely means this:
select emp, manager, salary
from yourtable
where manager not in (select emp from yourtable);
So you get all employees that have no manager (i.e. the super managers).
With the part after UNION ALL you get their subordinates and the subordinates of these etc. A hierarchical query.
At last in
SELECT [Manager],SUM([Salary]) AS Salary
FROM T1
GROUP BY [Manager]
ORDER BY SUM([Salary]) DESC
you use those rows to get a cumulated salary per manager.
You can read up on recursive queries in SQL Server here: https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx.
EDIT - Less over-kill
Declare #YourTable table (Emp varchar(25),Manager varchar(25),Salary int)
Insert into #YourTable values
('A','T',10),
('B','A',11),
('C','F',13),
('D','B',5)
;with cteP as (
Select Seq = cast(1000+Row_Number() over (Order by Emp) as varchar(500))
,Emp=Manager
,Manager=cast(null as varchar(25))
,Lvl=1
,Salary = 0
From #YourTable
Where Manager Not In (Select Distinct Emp From #YourTable)
Union All
Select Seq = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.Emp)) as varchar(500))
,r.Emp
,r.Manager
,p.Lvl+1
,r.Salary
From #YourTable r
Join cteP p on r.Manager = p.Emp)
Select TopLvl = A.Emp
,Salary = sum(B.Salary)
from cteP A
Join cteP B on (B.Seq Like A.Seq+'%')
Where A.Lvl=1
Group By A.Emp
Returns
TopLvl Salary
F 13
T 26
Inside with T1 references to INNER JOIN T1 ON r.[Manager]=T1.[Emp] which is probably table in your DB. Outside with T1 references to result of with statement.

Limit the data set of a single table within a multi-table sql select statement

I'm working in an Oracle environment.
In a 1:M table relationship I want to write a query that will bring me each row from the "1" table and only 1 matching row from the "many" table.
To give a made up example... ( * = Primary Key/Foreign Key )
EMPLOYEE
*emp_id
name
department
PHONE_NUMBER
*emp_id
num
There are many phone numbers for one employee.
Let's say I wanted to return all employees and only one of their phone numbers. (Please forgive the far-fetched example. I'm trying to simulate a workplace scenario)
I tried to run:
SELECT emp.*, phone.num
FROM EMPLOYEE emp
JOIN PHONE_NUMBER phone
ON emp.emp_id = phone.emp_id
WHERE phone.ROWNUM <= 1;
It turns out (and it makes sense to me now) that ROWNUM only exists within the context of the results returned from the entire query. There is not a "ROWNUM" for each table's data set.
I also tried:
SELECT emp.*, phone.num
FROM EMPLOYEE emp
JOIN PHONE_NUMBER phone
ON emp.emp_id = phone.emp_id
WHERE phone.num = (SELECT MAX(num)
FROM PHONE_NUMBER);
That one just returned me one row total. I wanted the inner SELECT to run once for each row in EMPLOYEE.
I'm not sure how else to think about this. I basically want my result set to be the number of rows in the EMPLOYEE table and for each row the first matching row in the PHONE_NUMBER table.
Obviously there are all sorts of ways to do this with procedures and scripts and such but I feel like there is a single-query solution in there somewhere...
Any ideas?
I'd use a rank (or dense_rank or row_number depending on how you want to handle ties)
SELECT *
FROM (SELECT emp.*,
phone.num,
rank() over (partition by emp.emp_id
order by phone.num) rnk
FROM EMPLOYEE emp
JOIN PHONE_NUMBER phone
ON emp.emp_id = phone.emp_id)
WHERE rnk = 1
will rank the rows in phone for each emp_id by num and return the top row. If there could be two rows for the same emp_id with the same num, rank would assign both a rnk of 1 so you'd get duplicate rows. You could add additional conditions to the order by to break the tie. Or you could use row_number rather than rank to arbitrarily break the tie.
All above answers will work beautifully with the scenario you described.
But if you have some employees which are missing in phone tables, then you need to do a left outer join like below. (I faced similar scenario where I needed isolated parents also)
EMP
---------
emp_id Name
---------
1 AA
2 BB
3 CC
PHONE
----------
emp_id no
1 7555
1 7777
2 5555
select emp.emp_id,ph.no from emp left outer join
(
select emp_id,no,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY emp_id) as rnum
FROM phone) ph
on emp.emp_id = ph.emp_id
where ph.rnum = 1 or ph.rnum is null
Result
EMP_ID NO
1 7555
2 5555
3 (null)
If you want only one phone number, then use row_number():
SELECT e.*, p.num
FROM EMPLOYEE emp JOIN
(SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY emp_id) as seqnum
FROM PHONE_NUMBER p
) p
ON e.emp_id = p.emp_id and seqnum = 1;
Alternatively, you can use aggregation, to get the minimum or maximum value.
This is my solution. Simple but maybe wont scale well for lot of columns.
Sql Fiddle Demo
select e.emp_id, e.name, e.dep, min(p.phone_num)
from
EMPLOYEE e inner join
PHONE_NUMBER p on e.emp_id = p.emp_id
group by e.emp_id, e.name, e.dep
order by e.emp_id;
And this fix the query you try
Sql Fiddle 2
SELECT emp.*, phone.num
FROM EMPLOYEE emp
JOIN PHONE_NUMBER phone
ON emp.emp_id = phone.emp_id
WHERE phone.num = (SELECT MAX(num)
FROM PHONE_NUMBER p
WHERE p.emp_id = emp.emp_id );

select only name by maximal value

I have a request
select
w.Departament_Id, avg(w.Salary) as avgSal,
dep.Name as Name
from
Employee w
join
Departament dep
on
dep.Id = w.Departament_Id
group by
w.Departament_Id,
dep.Name
this request retuns a table contains all avg salary for each departament. Next the target is to select Name of departament with a maximal avgSal value.
How to solve it?
sort by the aggregate and take the top 1:
select TOP 1
w.Departament_Id,
avg(w.Salary) as avgSal,
dep.Name as Name
from Employee w
join Departament dep
on dep.Id = w.Departament_Id
group by
w.Departament_Id,
dep.Name
ORDER BY avg(w.Salary) DESC
The syntax may be slightly different depending on your server software (some will allow ORDER BY avgSal, some will not; some will use LIMIT instead of TOP), but that's the general idea.