Select specific columns from two tables - sql

Suppose I have two tables tblEmployee and tblEmpSalary. I need to write a SQL statement to get a list of all employees, their name and salary, who receive the highest salary in each department.
Sample table data is here:

You could use ranking functions in this case:
WITH ranked AS (
SELECT
e.*,
s.monSalary,
rnk = RANK() OVER (PARTITION BY e.strDepartment ORDER BY s.monSalary DESC)
FROM tblEmplopyee e
INNER JOIN tblEmpSalary s ON e.intEmployeeID = s.intEmployeeID
)
SELECT
intEmploeeID,
strEmpName,
strDepartment,
monSalary
FROM ranked
WHERE rnk = 1
The RANK() function will do if you only need those who's got the topmost salary. With RANK(), the query may return more than employee per department if they have the same salary.
Alternatively, you can use DENSE_RANK() instead of RANK(), with the same effect, but DENSE_RANK() would also allow you to get employees with top n salaries. (You would be able to specify that in the WHERE condition like this:
WHERE rnk <= n
)
If, however, you need exactly one employee per department, even if there are several of them matching the requirement, use ROW_NUMBER() instead of RANK(). But then you'll probably need to add another criterion to the ORDER BY clause of the ranking function, e.g. like this:
... ORDER BY s.monSalary DESC, e.strEmpName ASC)
In fact, ROW_NUMBER() would simply make your query employee-oriented rather than salary-oriented. With ROW_NUMBER(), you would be able to have your query return top n most-paid employees, using the same condition as with DENSE_RANK():
WHERE rnk <= n
You can read more about ranking functions in SQL Server on MSDN:
Ranking Functions (Transact-SQL)

SELECT e.strEmpName, s.monSalary
FROM tblEmployee e
JOIN tblEmpSalary s ON e.intEmployeeID = s.intEmployeeID
WHERE e.strDepartment + '-' + CAST(s.monSalary AS varchar(20)) IN (
SELECT e2.strDepartment + '-' + CAST(MAX(s2.monSalary) AS varchar(20))
FROM tblEmployee e2
JOIN tblEmpSalary s2 ON e2.intEmployeeID = s2.intEmployeeID
GROUP BY e2.strDepartment)
Disclaimer: I can't test this query right now, so it could have some small detail wrong.

SELECT a.d, a.m, b.strEmpName
FROM (
SELECT strDepartment d, MAX(monSalary) m
FROM (
SELECT *
FROM tblEmployee e
LEFT JOIN tblEmpSalary s ON e.inEmployeeID = s.intEmployeeID
)
GROUP BY strDepartment
) a
LEFT JOIN (
SELECT *
FROM tblEmployee e
LEFT JOIN tblEmpSalary s ON e.inEmployeeID = s.intEmployeeID
) b ON a.d=b.strDepartment AND a.m=b.M

SELECT tblEmployee.strEmpName, max_salaries.strDepartment, max_salaries.salary
FROM (SELECT tblEmployee.strDepartment, MAX(monSalary)
FROM tblEmployee INNER JOIN tblEmpSalary
ON tblEmployee.intEmployeeID = tblEmpSalary.intEmployeeID
GROUP BY tblEmployee.strDepartment) max_salaries
INNER JOIN tblEmployee ON tblEmployee.strDepartment = max_salaries.strDepartment
INNER JOIN tblEmpSalary ON tblEmpSalary.monSalary = max_salaries.salary
AND tblEmpSalary.intEmployeeID = tblEmployee.intEmployeeID
In case of two or more employees with equal max salaries - this will return all of them for the specified department.

Related

ORA-00933 when trying to compare counts

This is the question
List the name of employee who work on more projects than employee 'Grace'', Show three columns in result: name of employee, project count of employee, grace's project count.
This is my code
SELECT employee."NAME", T1."# OF PROJECTS",
(SELECT COUNT(pid) FROM workon WHERE empid = 30) AS "Grace's Project"
FROM employee,
(SELECT empid, COUNT(pid) AS "# OF PROJECTS"
FROM workon
GROUP BY empid
ORDER BY empid)AS T1
WHERE T1."# OF PROJECTS" > (SELECT COUNT(pid) FROM workon WHERE empid = 30)
AND t1.empid = employee.EMPID
I keep getting ORA-00933: SQL command not properly ended. what am I missing?
The only error in your query is that Oracle does not accept AS for table aliases. Remove it and your query runs just fine.
There are two things I'd like to mention, though:
You are using an ancient join syntax you shouldn't use anymore. Comma-separated joins were made redundant by the introduction of explicit joins (e.g. INNER JOIN ... ON) in 1992.
Your query is a little over-complicated. Most of all, because you are counting projects thrice, once for all employees, twice for Grace. You can avoid this by using WITH clauses.
Here is the query built-up step by step with WITH clauses:
WITH emp AS
(
SELECT empid, e.name, COUNT(*) AS projects
FROM workon w
JOIN employee e USING(empid)
GROUP BY empid, e.name
ORDER BY empid
)
, grace AS
(
SELECT * FROM emp WHERE name = 'Grace'
)
SELECT
emp.name,
emp.projects as "# OF PROJECTS",
grace.projects as "Grace's Projects"
FROM emp
CROSS JOIN grace
WHERE emp.projects > grace.projects
ORDER BY emp.projects DESC, emp.name;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=f40e2c33541c76f0af112be967370784
I would use window functions:
select ew.name, ew.num_projects
from (select e.empid, e."NAME", count(*) as num_projects,
max(case when ew."NAME" = 'Grace' then count(*) else 0 end) over () as grace_num_projects
from employee e join
workon w
on w.empid = e.empid
group by e.empid, e."NAME"
) ew
where num_projects > grace_num_projects;
SELECT DISTINCT
e.empid,
,e."NAME"
FROM employee e
INNER JOIN (SELECT
empid
,count(*) as num_projects
FROM workon
GROUP BY empid) w ON w.empid = e.empid
LEFT JOIN (SELECT
1 AS ID
COUNT(*) as grace_projects
FROM workon
GROUP BY empid
WHERE empid = 30) g ON g.ID = 1
WHERE w.num_projects > g.grace_projects;
So here's what I am doing.
I am counting the projects before they are joined, that should decrease the overhead for the query, as the return on the join is shrank considerably prior to joining.
An index on that WORKON table by EmpID would speed up the query considerably.
Then, I query Graces figures, because I want them to return a value against any person, it should only return one result for a count, and then just join that by an arbitrary value so it returns against all rows
Again, it should utilise the same index.
Because it is calculating Graces figures first, it should only need to do this once, whereas a subquery would need calculate graces figures for each employee which is an unnecessary overhead.
This is then filtered in the where clause.

Slow MS Access Sub Query

I have three tables in Access:
employees
----------------------------------
id (pk),name
times
----------------------
id (pk),employee_id,event_time
time_notes
----------------------
id (pk),time_id,note
I want to get the record for each employee record from the times table with an event_time immediately prior to some time. Doing that is simple enough with this:
select employees.id, employees.name,
(select top 1 times.id from times where times.employee_id=employees.id and times.event_time<=#2018-01-30 14:21:48# ORDER BY times.event_time DESC) as time_id
from employees
However, I also want to get some indication of whether there's a matching record in the time_notes table:
select employees.id, employees.name,
(select top 1 time_notes.id from time_notes where time_notes.time_id=(select top 1 times.id from times where times.employee_id=employees.id and times.event_time<=#2018-01-30 14:21:48# ORDER BY times.event_time DESC)) as time_note_present,
(select top 1 times.id from times where times.employee_id=employees.id and times.event_time<=#2018-01-30 14:21:48# ORDER BY times.event_time DESC) as last_time_id
from employees
This does work but it's SOOOOO SLOW. We're talking 10 seconds or more if there's 100 records in the employee table. The problem is peculiar to Access as I can't use the last_time_id result of the other sub-query like I can in MySQL or SQL Server.
I am looking for tips on how to speed this up. Either a different query, indexes. Something.
Not sure if something like this would work for you?
SELECT
employees.id,
employees.name,
time_notes.id AS time_note_present,
times.id AS last_time_id
FROM
(
employees LEFT JOIN
(
times INNER JOIN
(
SELECT times.employee_id AS lt_employee_id, max(times.event_time) AS lt_event_time
FROM times
WHERE times.event_time <= #2018-01-30 14:21:48#
GROUP BY times.employee_id
)
AS last_times
ON times.event_time = last_times.lt_event_time AND times.employee_id = last_times.lt_employee_id
)
ON employees.id = times.employee_id
)
LEFT JOIN time_notes ON times.id = time_notes.time_id;
(Completely untested and may contain typos)
Basically, your query is running multiple correlated subqueries even a nested one in a WHERE clause. Correlated queries calculate a value separately for each row, corresponding to outer query.
Similar to #LeeMac, simply join all your tables to an aggregate query for the max event_time grouped by employee_id which will run once across all rows. Below times is the baseFROM table joined to the aggregate query, employees, and time_notes tables:
select e.id, e.name, t.event_time, n.note
from ((times t
inner join
(select sub.employee_id, max(sub.event_time) as max_event_time
from times sub
where sub.event_time <= #2018-01-30 14:21:48#
group by sub.employee_id
) as agg_qry
on t.employee_id = agg_qry.employee_id and t.event_time = agg_qry.max_event_time)
inner join employees e
on e.id = t.employee_id)
left join time_notes n
on n.time_id = t.id

How to get the value of max() group when in subquery?

So i woud like to find the department name or department id(dpmid) for the group that has the max average of age among the other group and this is my query:
select
MAX(avg_age) as 'Max average age' FROM (
SELECT
AVG(userage) AS avg_age FROM user_data GROUP BY
(select dpmid from department_branch where
(select dpmbid from user_department_branch where
user_data.userid = user_department_branch.userid)=department_branch.dpmbid)
) AS query1
this code show only the max value of average age and when i try to show the name of the group it will show the wrong group name.
So, How to show the name of max group that has subquery from another table???
You may try this..
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##' ,department_name)) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I've done some change on ipothesys that the department name is placed in a "department" anagraphical table.. so, as it needed put in join a table in plus, then I changed your query, eventually if the department name is placed (but I don't thing so) in the branch_department table you can add the field and its treatment to your query
update
In adjunct to as said, if you wanto to avoid identical average cases you can furtherly make univocal the averages by appending a rownum id in this way:
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##', CONCAT( #rownum:=#rownum+1, CONCAT('##' ,department_name)))) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
,(SELECT #rownum:=0) r
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I took a shot at what I think you are looking for. The following will give you the department branch with the highest average age. I assumed the department_branch table had a department_name field. You may need an additional join to get the department.
SELECT db.department_name, udb.dpmid, AVG(userage) as `Average age`
FROM user_data as ud
JOIN user_department_branch as udb
ON udb.userid = ud.userid
JOIN department_branch as db
ON db.dpmbid = udb.dpmbid
GROUP BY udb.dpmid
ORDER BY `Average age` DESC
LIMIT 1

SQL Server cross apply not working?

http://sqlfiddle.com/#!3/78273/1
create table emptb1
(
id int,
name varchar(20),
dept int
)
insert into emptb1 values (1,'vish',10);
insert into emptb1 values (2,'vish',10);
insert into emptb1 values (3,'vish',30);
insert into emptb1 values (4,'vish',20);
create table depttb1
(
id int,
name varchar(20)
)
insert into depttb1 values(10,'IT')
insert into depttb1 values(20,'AC')
insert into depttb1 values(30,'LIC')
select * from emptb1
select e.id, e.name, a.id
from emptb1 e
cross apply
(
select top 1 * from depttb1 d
where d.id = e.dept
order by d.id desc
) a
I was trying to learn cross apply as it's similar as inner join but works with function.
In above query I'm assuming it should take only dept=30 because order d.id desc will give only top 1st id which is 30 and then it should return employees with dept id = 30 but it's giving me all the rows and all the deptid.
What's wrong with query or I'm wrong interpreting the concept of cross apply.
You say "In above query I'm assuming it should take only dept=30 because order d.id desc will give only top 1st id which is 30 and then it should return employees with dept id = 30".
That's not how it works. Here's your query (reformatted a little for clarity):
select e.id, e.name, a.id
from emptb1 e
cross apply
(
select top 1 *
from depttb1 d
where d.id = e.dept
order by d.id desc
) a
The APPLY keyword means that the inner query is (logically) called once for each row of the outer query. For what happens inside the inner query, it's helpful to understand the logical order that the clauses of a SELECT are executed in. This order is:
FROM clause
WHERE clause
SELECT columns
ORDER BY clause
TOP operator
Note that in your inner query then, the TOP operator gets applied last, well after the WHERE clause. This means the where d.id = e.dept will first reduce the inner rows to those whose d.id matches the e.dept of the outer row (which is not necessarily 30), then sort them, and then return the first one. And it does this for every row in the outer query. So obviously, many of them are not going to be 30.
What you are trying to would be more akin to this (still retaining the CROSS APPLY):
select e.id, e.name, a.id
from emptb1 e
cross apply
(
select top 1 *
from
(
select top 1 *
from depttb1 d
order by d.id desc
) b
where b.id = e.dept
) a
Here, the logic has been reordered by use of another, nested, sub-query that insures that the ORDER BY, then TOP 1 get applied before the WHERE clause. (Note that this would not normally the recommended way to do this as nested sub-queries can hamper readability, I just used it here to retain the CROSS APPLY and to retain the rest of the original structure).
To exand on Damien's comment, the inner query:
select top 1 * from depttb1 d
where d.id = e.dept
order by d.id desc
is going to run for every row in the outer query:
select e.id, e.name, a.id
from emptb1 e
So you will always get a match from the inner query for each row. I think you were expecting the inner query to run only one time, but that's not what APPLY does.
So, taking the first row from your outer query, with an ID of 1 and a dept id of 10, your inner query will translate to:
select top 1 * from depttb1 d
where d.id = 10 //this is the dept id for the current row from your outer query
order by d.id desc
To solve this problem without a cross apply, use a sub query. However in your example it will only return one row, the last department entered assuming id value is increasing.
-- Using a sub query to find max dept
select e.id, e.name
from emptb1 e
where e.dept in
(
select top 1 id
from depttb1
order by id desc
)
The idea behind a CROSS APPLY is kinda like a CROSS JOIN. This will return all rows. It is used by DBA's with many of the Dynamic Management View (DMVs) that are Table Value Functions (TVF)
What you want is a OUTER APPLY kinda like a LEFT JOIN.
select e.id, e.name
from emptb1 e
outer apply
(
select top 1 d.id from depttb1 d order by d.id desc
) AS m (id)
where e.dept = m.id
Check out my articles on these concepts.
CROSS APPLY - http://craftydba.com/?p=3767
OUTER APPLY - http://craftydba.com/?p=3796
TABLE VALUE FUNCTION (INLINE) - http://craftydba.com/?p=3733
TABLE VALUE FUNCTION (MULTI LINE) - http://craftydba.com/?p=3754

How to get order by with inner query

I am new to sql, can any one please help me with getting ordered data from the internal query
select
*
from emp
where id
(select order from department where name = 'testing' order by order asc).
I am getting order data from the inner query and with the id's I get I should get the result from the emp table to be in the same order of inner query. Can anyone help me with this. TIA.
If it's reasonable to re-write your query as a join:
select e.*
from emp e
inner join department d
on e.id = d.order
where d.name = 'testing'
order by d.order asc
Where this would change your results is if there are multiple rows in department with the same order value matching one or more rows in emp - in which case this query will return multiple rows, whereas the original would not. But if such a condition doesn't apply, then this is the query I'd recommend.
Damien's answer is quite cool and perfect. but if you still want to go with subquery then try this
select *
from emp
where id in (select order from department where name = 'testing' order by order asc)
order by id asc
May this help you
This will give the right number of rows in case there more than 1 match between emp.id and department.order
select * from emp e
where exists
(select 1 from department d where d.name = 'testing'
and e.id = d.order1) -- order is a reserved word in several sql languages
order by id
It seems there is something funny going on between your tables. Would would department contain any information about emp(I assume it is employee table) ?
There is no guarantee that there is an actual temporary table with the inner query and that it is sorted and processed in a certain way. However, you can sort the outer query by emp.id.
select * from emp where id in
(select order from department where name = 'testing')
order by id asc
select e.* from emp as e, department d where e.id=d.order and d.name='testing' order by d.order