Northwind - List of top ten employees - sql

I have another trouble with my SQL queries.
My task is to create a list of top 10 employees, with the most sales in 1997.
So far I have this, simple query that shows me list of employees and which order they've sold.
SELECT
Orders.EmployeeID,
Orders.OrderID
FROM
Employees
JOIN
Orders ON Orders.EmployeeID = Employees.EmployeeID
ORDER BY
Orders.EmployeeID;
Now I want to group up those numbers, I need to know how many sales each employee did in 1997. How to do that?

You can get the results that you need without JOIN and GROUP BY if you order by results of a subquery:
SELECT TOP 10 *
FROM Employees e
ORDER BY (
SELECT COUNT(*)
FROM Sales s
WHERE s.EmployeeId=e.EmployeeId
AND DATEPART(year, o.OrderDate)=1997
) DESC
This yields top ten employees by the number of sales transactions.
If you need anything from Sales, say, the count, you would need to go the GROUP BY route:
SELECT TOP 10 * FROM (
SELECT e.EmployeeId, COUNT(*) AS SalesCount
FROM Employees e
LEFT OUTER JOIN Orders o ON o.EmployeeId=e.EmployeeId
AND DATEPART(year, o.OrderDate)=1997
GROUP BY e.EmployeeId
) groups
ORDER BY SalesCount DESC

WITH CTE AS
(
SELECT EmployeeId,COUNT(*) as cn,
DENSE_RANK(ORDER BY COUNT(*) DESC) AS rn
FROM orders
WHERE DATEPART(year,OrderDate)=1997
GROUP BY EmployeeId
)
SELECT e.*,COALESCE(o.cn,0) AS CountOrders
FROM Employees e
LEFT JOIN CTE o
ON e.EmployeeId=o.EmployeeId
WHERE o.rn<=10

It can reduce to this
SELECT top(10) EmployeeID, count(*) as cnt
FROM Orders
group by EmployeeID
ORDER BY count(*) desc;

Related

Get employees which have smaller salary than any of those who sold more than one order

Sorry for long title but I wanted to explain the situation.
My brains are almost exploding I have tried this for five hours now, and I don't get it. Got just a big headache.
I want to get those employees which haec smaller salary than anyone of those who has sold one or more orders. There is two tables EMPLOYEES and ORDERS.
EMPLOYEES have these columns; First name, Last name, Social security number, Salary.
ORDERS have these columns; Social security number, OrderID
This is best solution so far.
SELECT H.SOCSECNUM, H.LASTNAME, H.FIRSTNAME, H.SALARY
FROM EMPLOYEES H
WHERE H.SALARY < ALL(SELECT COUNT(T.ORDERID)
FROM ORDERS T
WHERE H.SOCSECNUM = T.SOCSECNUM
HAVING COUNT(T.ORDERID) > 0
);
I understand this like get social security number, name and salary of all employees which have smaller salary than anyone who sold more than 0 orders.
Am I stupid or what is wrong in that? Now this prints all employees which have 0 orders. If i change 0 -> 1 it prints all employees which have one or less orders.
I think that's the query you wanted to write:
select e.*
from employees e
where h.salary < all (
select h1.salary
from employees e1
where exists (select 1 from orders o1 where o1.socsecnum = e1.socsecnum)
)
The subquery returns the list of salaries of employees that performed at least one sale. You can then use all to filter the table against that.
You could also use aggregation:
select e.*
from employees e
where h.salary < (
select min(h1.salary)
from employees e1
where exists (select 1 from orders o1 where o1.socsecnum = e1.socsecnum)
)
You need the number of orders and salary for employees. This is helpful in one row. So, use JOIN:
select e.*, numorders
from employees e left join
(select o.socseqnum, count(*) as numorders
from orders o
group by o.socseqnum
) o
on e.socseqnum = o.socseqnum;
Then, I would use window functions to get the minimum salary for the one-order employees and use a comparison to that:
select eo.*
from (select e.*, o.numorders,
min(case when numorders = 1 then e.salary end) over () as min_salary_1order
from employees e left join
(select o.socseqnum, count(*) as numorders
from orders o
group by o.socseqnum
) o
on e.socseqnum = o.socseqnum
) eo
where salary < min_salary_1order
Using uncorrelated subquery
select *
from employees
where salary < (select min(salary)
from employees
where socsecnum in (select socsecnum
from orders
where socsecnum is not null));

SQL nested aggregate grouping error

I'm trying to find name of product which has sold maximum units, I've two tables, purchases and products, products has pname and pid, purchases has pid, qty(units sold).
I've managed this
select p.pname, sum(q.qty) from purchases q
inner join products p on p.pid=q.pid
where p.pid=q.pid
group by p.pname
order by sum(q.qty) desc
I'm getting the result in descending order but I need only the top most selling units, multiple products can have top most selling units. When I use
max(sum(q.qty))
I get grouping error.
One approach is to derive the values first using a common table expression.
Simply put you can't wrap aggregates in other aggregates. You may be able to wrap an aggregate around an analytic however.
with cte as (select p.pname, sum(q.qty) from purchases q
inner join products p on p.pid=q.pid
where p.pid=q.pid
group by p.pname
order by sum(q.qty) desc)
Select pname, max(purchases)
from cte
group by pname
You can use ctes to do this.
1)First get the total quantity of each product
2)Then get the maximum of all those totals
3)Join it with your original query
with totals as (select pid, sum(qty) totalqty from purchases group by pid)
, t1 as (select p.pid, p.pname, sum(q.qty) totqty
from purchases q
inner join products p on p.pid=q.pid
group by p.pname)
, t2 as (select max(totalqty) maxtotal from totals)
select pname, totqty
from t1
join t2 on t1.totqty = t2.maxtotal
Analytics can simplify this for you. If you have more than one product with the same sum(qty) and that happens to be the max(sum(qty)), then this should get you them:
select pname, quantity
FROM (
select p.pname
, sum(q.qty) quantity
,rank() over (order by sum(q.qty desc) ranking
from purchases q
inner join products p on p.pid=q.pid
group by p.pname
)
where ranking = 1

SQL GROUP BY FirstName, Lastname and Year

I have made the query below=>
SELECT FirstName,
LastName,
COUNT(*) AantalBestellingen,
YEAR(Orders.OrderDate) as Jaar
from Employees
RIGHT JOIN Orders ON (Employees.EmployeeID=Orders.EmployeeID)
WHERE Employees.ReportsTo IS NOT NULL
GROUP BY Employees.FirstName, Employees.LastName, YEAR (Orders.OrderDate)
ORDER BY YEAR (Orders.OrderDate)
I need to select the first name, lastname from the employees and the total orders they have processed within the last year.
After adding the group by year to the query it's not showing the unique employees anymore but it shows the years. I need to get only the unique employees and the last order year they have processed an order in.
Any advice what i'm doing wrong?
One way to do this is with an inner select:
SELECT
FirstName,
LastName,
COUNT(*) as AantalBestellingen,
(select YEAR(max(Orders.OrderDate)) from Orders O
where Employees.EmployeeID=O.EmployeeID) as Jaar
from
Employees
RIGHT JOIN Orders
ON (Employees.EmployeeID=Orders.EmployeeID)
WHERE
Employees.ReportsTo IS NOT NULL
GROUP BY
Employees.FirstName,
Employees.LastName
ORDER BY Jaar
You can place the Last Year condition in WHERE clause as follows
SELECT FirstName,
LastName,
COUNT(*) AantalBestellingen,
from Employees
RIGHT JOIN Orders ON (Employees.EmployeeID=Orders.EmployeeID)
WHERE Employees.ReportsTo IS NOT NULL AND YEAR(Orders.OrderDate) = 2014
GROUP BY Employees.FirstName, Employees.LastName
Can you try this one... This will give all employee, his only last year order count and last year.
;with cteMaxYear as
(select max(YEAR(Orders.OrderDate)) MaxOrderYear, EmployeeID
from Orders
group by EmployeeID)
SELECT FirstName,
LastName,
COUNT(*) AantalBestellingen,
m.MaxOrderYear as Jaar
from Employees e
left outer join cteMaxYear m
inner join Orders o ON m.EmployeeID=o.EmployeeID and m.MaxOrderYear=o.YEAR(Orders.OrderDate)
on e.EmployeeID = m.EmployeeID
WHERE e.ReportsTo IS NOT NULL
GROUP BY e.FirstName, e.LastName, m.MaxOrderYear
ORDER BY m.MaxOrderYear

query with subquery with 1 result(max) for each year

I have to make a query where I show for each year wich shipper had the maximum total cost.
My query now show for each year the total cost of each shipper. So in the result i must have a list of the years, for each year the shipper and the total cost.
Thanks in advance.
select year(OrderDate), s.ShipperID, sum(freight)
from orders o
join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate),s.ShipperID
Select a.FreightYear, a,ShipperID, a.FreightValue
from
(
select year(OrderDate) FreightYear, s.ShipperID, sum(freight) FreightValue
from orders o
join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate),s.ShipperID
) a
inner join
(
select FreightYear, max(FrieghtTotal) MaxFreight
from
(
select year(OrderDate) FreightYear, s.ShipperID, sum(freight) FreightTotal
from orders o
join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate),s.ShipperID
) x
group by FreightYear
) max on max.FreightYear = a.FreightYear and max.MaxFreight = a.FreightValue
order by FreightYear
Inner query a is your original query, getting the value of freight by shipper.
Inner query max gets the max value for each year, and then query max is joined to query a, restricting the rows in a to be those with a value for a year = to the max value for the year.
Cheers -
It's marginally shorter if you use windowing functions.
select shippers_ranked.OrderYear as OrderYear,
shippers_ranked.ShipperId as ShipperId,
shippers_ranked.TotalFreight as TotalFreight
from
(
select shippers_freight.*, row_number() over (partition by shippers_freight.OrderYear order by shippers_freight.TotalFreight desc) as Ranking
from
(
select year(OrderDate) as OrderYear,
s.ShipperID as ShipperId,
sum(freight) as TotalFreight
from orders o
inner join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate), s.ShipperID
) shippers_freight
) shippers_ranked
where shippers_ranked.Ranking = 1
order by shippers_ranked.OrderYear
;
You need to decide what you would like to happen if two shippers have the same TotalFreight for a year - as the code above stands you will get one row (non-deterministically). If you would like one row, I would add ShipperId to the order by in the over() clause so that you always get the same row. If in the same TotalFreight case you would like multiple rows returned, use dense_rank() rather than row_number().

SQL: improving join efficiency

If I turn this sub-query which selects sales persons and their highest price paid for any item they sell:
select *,
(select top 1 highestProductPrice
from orders o
where o.salespersonid = s.id
order by highestProductPrice desc ) as highestProductPrice
from salespersons s
in to this join in order to improve efficiency:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders ) o on s.id = o.salespersonid
It still touches every order record (it enumerates the entire table before filtering by salespersonid it seems.) However you cannot do this:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders
where orders.salepersonid = s.id) o on s.id = o.salespersonid
The where clause in the join causes a `multi-part identifier "s.id" could not be bound.
Is there any way to join the top 1 out of each order group with a join but without touching each record in orders?
Try
SELECT
S.*,
T.HighestProductPrice
FROM
SalesPersons S
CROSS APPLY
(
SELECT TOP 1 O.HighestProductPrice
FROM Orders O
WHERE O.SalesPersonid = S.Id
ORDER BY O.SalesPersonid, O.HighestProductPrice DESC
) T
would
select s.*, max(highestProductPrice)
from salespersons s
join orders o on o.salespersonid = s.id
group by s.*
or
select s.*, highestProductPrice
from salespersons s join (select salepersonid,
max(highestProductPrice) as highestProductPrice
from orders o) as o on o.salespersonid = s.id
work?