Query For getting the Salary of employee - sql

Insert into Employee values (1,'Abdul Rehman','Street No 12','Kamra Kalan')
Insert into Employee values (2,'Iram Bhatti','Street No 10','Attock')
Insert into Employee values (3,'Danial Aziz','Street No 12','Kamra Kalan')
Insert into Employee values (4,'Kashif Butt','Street No 10','Attock')
Insert into Employee values (5,'Zohaib Butt','Street No 13','Peshawar')
insert into Company values (1,'First Bank Co-Operation','Hydrabaad');
insert into Company values (2,'Small Bank Co-Operation','Kashmir');
Insert into Works values (1,2,5000)
Insert into Works values (2,1,40000)
Insert into Works values (1,3,56000)
Insert into Works values (1,4,8000)
Insert into Works values (2,2,78000)
Question:
Write a query for getting the name of employees who earn more than every employee of Small Bank Co operation.
My query Solution:
Select Employee.person_name from Works
inner join Employee on Employee.person_Id
=Works.Person_Id inner join Company on
Company.Company_Id=Works.Company_Id
and Salary>(Select Salary from works
Where Company.Company_name='Small Bank Co-Operation')
But This query not works for me how can I get this one?

I think the simplest solution is just to join all of the tables and then find the employee who has a salary equal to the max salary from the works table.
SELECT e.person_name
FROM employee e inner join works w
on e.person_Id = w.Person_Id
inner join company c
on w.Company_Id = c.Company_Id
WHERE c.Company_name = 'Small Bank Co-Operation'
and w.Salary = (SELECT max(works.Salary)
FROM works)

How much does an employee earn?
select person_id, sum(salary)
from works
group by person_id;
We can even extend this to see whether this is Small Bank employee:
select
person_id,
sum(salary),
max(case when Company_Id =
(select Company_Id from company where company_name = 'Small Bank Co-Operation')
then 1 else 0 end
) as is_small_banker
from works
group by person_id;
Now, use this to compare:
with salaries as
(
select
person_id,
sum(salary) as total,
max(case when Company_Id =
(select Company_Id from company where company_name = 'Small Bank Co-Operation')
then 1 else 0 end
) as is_small_banker
from works
group by person_id
)
select e.person_name
from employee e
join salaries s on s.person_id = e.person_id
where total > all
(
select total
from salaries
where is_small_banker = 1
)
order by e.person_name;
This is just one way to do this. You can do the same with NOT EXISTS for example (i.e. where not exists a Small Bank worker with the same or a higher salary).
Update
You have meanwhile tagged your request with SQL Server and told me that you are getting this error:
Msg 130, Level 15, State 1, Line 126 Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Obviously, SQL Server has problems with the conditional aggregation. You can circumvent this by joining the compabny table instead:
with salaries as
(
select
w.person_id,
sum(w.salary) as total,
max(case when c.company_name = 'Small Bank Co-Operation' then 1 else 0 end
) as is_small_banker
from works w
join company c on c.company_Id = w.company_Id
group by w.person_id
);
Update 2
If the works table could only have one work (company and salary) per employee, the whole query would reduce to:
select e.person_name
from employee e
join works w on w.person_id = e.person_id
where salary > all
(
select salary
from works
where id_company =
(select company_Id from company where company_name = 'Small Bank Co-Operation')
)
order by e.person_name;

Related

SQL: How to concatenate two cells from one column of multiple rows if all of the other row's cells are equal

Looking for DepartmentName1 + ', ' + DepartmentName2
I'm trying to merge two rows into one row when only one column has different values. Specifically I'm trying to list the name, job title, gender, pay rate, hire date and department name of the top 100 highest paid employees of the AdventureWorks2017 database. Here is the code I have so far:
SELECT TOP 100 (P.FirstName + ' ' + P.LastName) AS Name, HRE.JobTitle, HRE.Gender,
CAST(HRPH.Rate AS Decimal(10,2)) AS PayRate, HRE.HireDate, HRD.Name AS Department
FROM ((((Person.Person AS P
INNER JOIN HumanResources.Employee AS HRE
ON P.BusinessEntityID = HRE.BusinessEntityID)
INNER JOIN
(SELECT BusinessEntityID, MAX(RateChangeDate) AS RCD, MAX(Rate) AS Rate
FROM HumanResources.EmployeePayHistory
GROUP BY BusinessEntityID) AS HRPH
ON HRE.BusinessEntityID = HRPH.BusinessEntityID)
INNER JOIN HumanResources.EmployeeDepartmentHistory AS HRDH
ON HRE.BusinessEntityID = HRDH.BusinessEntityID)
INNER JOIN HumanResources.Department AS HRD
ON HRDH.DepartmentID = HRD.DepartmentID)
ORDER BY HRPH.Rate DESC;
This gives me the following result:
Two questions:
How can I get every 'Name' to be listed only once, regardless of DepartmentName? For example: Rows 5 & 6 to be only Row 5: Laura Norman | Chief Financial Officer | F | 60.10 | 2009-01-31 | Executive, Finance.
OR, David Bradley...|...Marketing, Purchasing
Does my code include an employee that may have gotten a pay cut? Meaning, the RateChangeDate (RCD) is MAX but the Rate is not?
Using Microsoft SQL Server 2019
I bet you can make use of the string_agg() to aggregate the values with a delimiter in a query field.
SELECT TOP 100 (P.FirstName + ' ' + P.LastName) AS Name, HRE.JobTitle, HRE.Gender,
CAST(HRPH.Rate AS Decimal(10,2)) AS PayRate, HRE.HireDate, STRING_AGG(HRD.Name,',') AS Department
FROM ((((Person.Person AS P
INNER JOIN HumanResources.Employee AS HRE
ON P.BusinessEntityID = HRE.BusinessEntityID)
INNER JOIN
(SELECT BusinessEntityID, MAX(RateChangeDate) AS RCD, MAX(Rate) AS Rate
FROM HumanResources.EmployeePayHistory
GROUP BY BusinessEntityID) AS HRPH
ON HRE.BusinessEntityID = HRPH.BusinessEntityID)
INNER JOIN HumanResources.EmployeeDepartmentHistory AS HRDH
ON HRE.BusinessEntityID = HRDH.BusinessEntityID)
INNER JOIN HumanResources.Department AS HRD
ON HRDH.DepartmentID = HRD.DepartmentID)
GROUP BY P.FirstName,P.LastName,HRE.JobTitle, HRE.Gender, HRPH.Rate, HRE.HireDate
ORDER BY HRPH.Rate DESC;
To answer the second part, I took the liberty of creating an example and you may be able to work into your solution. The data you are working with lacks a unique key and using FirstName, LastName, and Gender is an obviously bad candidate for a unique key. You also mention RateChangeDate but do not mention how to handle that value when the data aggregates. The query below basically ignores RateChangeDate on the output and marks the records that have a decrease in pay. Another query into the data is needed to remove those records, below I did it using a HAVING clause.
DECLARE #X TABLE (ID INT, Rate MONEY, RateChangeDate DATETIME, Department NVARCHAR(50))
INSERT #X VALUES
(1,25.00,'01/01/2021','A'),
(1,23.00,'05/01/2021','A'),
(2,25.00,'01/01/2021','A'),
(3,25.00,'01/01/2021','A'),
(3,26.00,'02/01/2021','A'),
(4,25.00,'01/01/2021','A'),
(4,25.00,'01/01/2021','B')
SELECT
ID,
SUM(LatestRate) AS LatestRate,
MAX(MaxRateChange) AS RateChanges,
Departments
FROM
(
SELECT
ID,
STRING_AGG(Department,',') AS Departments,
Rate,
MAX(RateChangeDate) AS MaxRateChange,
CASE WHEN LAG(Rate) OVER (PARTITION BY ID ORDER BY RateChangeDate) > Rate THEN 1 ELSE 0 END AS DecreaseInPay,
CASE WHEN MAX(RateChangeDate)OVER(PARTITION BY ID) = RateChangeDate THEN Rate ELSE NULL END LatestRate
FROM
#X
GROUP BY
ID,Rate,RateChangeDate
)AS X
GROUP BY
ID,Departments
HAVING
MAX(DecreaseInPay) = 0

How to use count() without the output changing when I use join -Postgresql

I need to find the sum of all the rental days of a car dealership (including
period_begin and period_end, not unique) for all cars of that department.Divided by the total number of different (unique) employees that department ever had.
I have 5 tables
Department(PK departmentnr, name, FK postcode, FK place_name)
Employee(PK employeenr, FK email)
Contract(PK periode_begin, PK periode_end, FK departmentnr, FK employeenr)
registerform (,PK periode_end,PK,periode_end,FKemail,FK,
Fk numberplate,periode_end,periode_begin)
car(PK numberplate,FK departmentnr,FK Brand,FK model)
when I go step by step
part 1
The total employees per department
select departmentnr,Count(employeenr)FROM contract
group by departmentnr
part 2
the amount of days the cars were hired
SELECT DISTINCT departmentnr,
Sum((( Date(periode_end) - Date(periode_begin) + 1 ))) AS
average
FROM registerform r
INNER JOIN car w using(numberplate)
GROUP BY departmentnr
I get the correct ouput but when I try to get these 2 together
SELECT distinct departmentnr,
(
sum(((date(r.periode_end) - date(r.periode_begin) + 1))) / (
select
count(employeenr))
)
as average
from
registerform r
inner join
car w using(numberplate)
inner join
contract using(departmentnr)
inner join
employee using(employeenr)
group by
departmentnr
then my output gets absurd.
How can I fix this and is there a way to make the code more efficient.
Aggregated before you JOIN. So, one method is:
SELECT c.departmentnr, co.num_employees,
Sum( Date(r.periode_end) - Date(r.periode_begin) + 1 ) AS average
FROM registerform r JOIN
car c
USING (numberplate) LEFT JOIN
(SELECT co.departmentnr, Count(*) as num_employees
FROM contract co
GROUP BY co.departmentnr
) co
ON co.departmentnr = c.departmentnr
GROUP BY c.departmentnr, co.num_employees;

How do i include data from a joined table that doesnt meet the WHERE condition as a null value(SQL)

I have 2 tables employees and Medical leave, which are related by the Employee ID, and basically the Medical leave table will have multiple data of a single employee who takes multiple leaves, I want to filter out the data by the month of the medical leave, and include the employees whose medical leave doesnt occur on the filtered month as a null value.
EMPLOYEES MEDICAL
|employee|ID| |ID|DateOfLeave|
A 1 1 2019/1/3
B 2 1 2019/4/15
C 3 2 2019/5/16
D 4
The sql statement i came up with filters the specific month of the leave and counts the number of times they took a leave on that month such as January, it also includes employees who doesnt have any leave as a '0', however the employees who have medical leaves which doesnt occur on January doesnt show up in the result set at all, how can i show them to have 0 medical leaves in the month of january?
select employees.employee, employees.ID,
count(medical.DateOfLeave) as NumberOfLeaves
from employees
left outer join medical on employees.ID = medical.ID
where (MONTH(DateOfLeave) = 1) or (medical.DateOfLeave is null)
group by employees.employee,employees.ID
RESULT SET
|Employee|ID|NumberOfLeaves|
A 1 1
C 3 0
D 4 0
As you can see B disappears,but i want it to show in the result set as a '0' like employee C and D
I know its because employee B's medical data doesnt meet the condition of the where clause, but how do i write a statement that includes employees who have medical leaves which doesnt occur on january in the result set as a 0??
Your query is only showing results for employees that have leave in January or do not have leave at all.
Instead, you only want to join to records in your medical table if there are records for the specified month, then you'll group your results and get the counts from there.
In order to do this, you need to change the condition for your join to include your Month filter.
Here's a working example using table variables
DECLARE #Employees TABLE (ID INT, Employee CHAR(1))
DECLARE #Medical TABLE (ID INT, DateOfLeave DATE)
INSERT INTO #Employees
VALUES (1, 'A'), (2,'B'), (3,'C'), (4, 'D')
INSERT INTO #Medical
VALUES (1, '2019-01-03'), (1, '2019-04-15'), (2, '2019-05-16')
SELECT e.employee,
e.ID,
count(m.DateOfLeave) AS NumberOfLeaves
FROM #Employees e
LEFT OUTER JOIN #Medical m ON e.ID = m.ID AND MONTH(DateOfLeave) = 1
GROUP BY e.employee,e.ID
left outer join will first join the table and then filter. in your case B is left out in the join itself so thats why the filter is not working. you can try this
select employees.employee, employees.ID,
nvl(count(medical.DateOfLeave),0) as NumberOfLeaves
from employees
left outer join (select * from medical
where (MONTH(DateOfLeave) = 1))
on employees.ID = medical.ID
group by employees.employee,employees.ID
or you can also try this
select e.id, employee, count(dateofleave)
from employee e, medical m
where e.id = m.id
and month(m.DateOfLeave) = 1
group by e.id, employee
UNION ALL
select id, employee, 0
from employee e
where not exists(select 1 from medical m
where e.id = m.id
and month(m.DateOfLeave) = 1

SELECT * FROM table in addition of aggregation function

Short context:
I would like to show a list of all companies except if they are in the sector 'defense' or 'government' and their individual total spent on training classes. Only the companies that have this total amount above 1000 must be shown.
So I wrote the following query:
SELECT NAME, ADDRESS, ZIP_CODE, CITY, SUM(FEE-PROMOTION) AS "Total spent on training at REX"
FROM COMPANY INNER JOIN PERSON ON (COMPANY_NUMBER = EMPLOYER) INNER JOIN ENROLLMENT ON (PERSON_ID = STUDENT)
WHERE SECTOR_CODE NOT IN (SELECT CODE
FROM SECTOR
WHERE DESCRIPTION = 'Government' OR DESCRIPTION = 'Defense')
GROUP BY NAME, ADDRESS, ZIP_CODE, CITY
HAVING SUM(FEE-PROMOTION) > 1000
ORDER BY SUM(FEE-PROMOTION) DESC
Now what I actually need is, instead of defining every single column in the COMPANY table, I would like to show ALL columns of the COMPANY table using *.
SELECT * (all tables from COMPANY here), SUM(FEE-PROMOTION) AS "Total spent on training at REX"
FROM COMPANY INNER JOIN PERSON ON (COMPANY_NUMBER = EMPLOYER) INNER JOIN ENROLLMENT ON (PERSON_ID = STUDENT)
WHERE SECTOR_CODE NOT IN (SELECT CODE
FROM SECTOR
WHERE DESCRIPTION = 'Government' OR DESCRIPTION = 'Defense')
GROUP BY * (How to fix it here?)
HAVING SUM(FEE-PROMOTION) > 1000
ORDER BY SUM(FEE-PROMOTION) DESC
I could define every single column from COMPANY in the SELECT and that solution will do the job (as in the first example), but how can I make the query shorter using "SELECT * from the table COMPANY"?
The key idea is to summarize in the subquery to get the total spend for the company. This allows you to remove the aggregation from the outer query:
select c.*, pe.total_spend
from company c join
sector s
on c.sector_code = s.code left join
(select p.employer, sum(e.fee - e.promotion) as training_spend
from person p join
enrollment e
on p.person_id = e.student
group by p.employer
) pe
on pe.employer = c.company_number
where s.sector not in ('Government', 'Defense') and
pe.total_spend > 1000

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 );