SQL double group by - sql

I need to select employees' names from those who has sold products, for the biggest total sum of money in each of the years, in Northwind database. I've managed to create a valid query like this:
WITH TEMP_QUERY AS
(
SELECT
DATEPART(YEAR, OrderDate) AS 'Year'
,FirstName + ' ' + LastName AS 'Employee'
,SUM(UnitPrice * Quantity) AS 'Total year sale'
FROM Employees
INNER JOIN Orders
ON Employees.EmployeeID = Orders.EmployeeID
INNER JOIN [Order Details]
ON Orders.OrderID = [Order Details].OrderID
GROUP BY FirstName + ' ' + LastName, DATEPART(YEAR, OrderDate)
)
SELECT
DATEPART(YEAR, OrderDate) AS 'Year'
,FirstName + ' ' + LastName AS 'Employee'
,SUM(UnitPrice * Quantity) AS 'Total year sale'
FROM Employees
INNER JOIN Orders
ON Employees.EmployeeID = Orders.EmployeeID
INNER JOIN [Order Details]
ON Orders.OrderID = [Order Details].OrderID
GROUP BY
FirstName + ' ' + LastName
,DATEPART(YEAR, OrderDate)
HAVING SUM(UnitPrice * Quantity) IN
(
SELECT MAX(main.[Total year sale]) FROM TEMP_QUERY AS main
INNER JOIN TEMP_QUERY AS e ON e.Employee = main.Employee
GROUP BY main.Year
)
ORDER BY 1;
However I wonder if there's a simpler way of doing this with or without CTE (probably is)
Database scheme.
https://docs.yugabyte.com/images/sample-data/northwind/northwind-er-diagram.png

First, your main query is the same as those one used in CTE (except HAVING and ORDER BY clauses), so you can just write it as SELECT ... FROM temp_query.
Second, I suggest you not to use the combination of FirstName and LastName for aggregation in CTE, since in theory there can be multiple rows for different employees with same values. You have the EmployeeId you a free to use it instead.
Third, since now the main query does not have any aggregation, you do not need a HAVING clause, it can be replaced by WHERE clause.
Fourth, the filter condition (in HAVING clause of your query) does not need self joining of CTE for getting the maximum of Total year sale. Just select MAX("Total year sale") with GROUP BY year.
Fifth, since I have suggested to use EmployeeId to perform the aggregation in CTE, join the Employees table to get the corresponding FirstName and LastName values for an employee in results.
Finally the query would look like this
WITH employee_total_sales (
SELECT
e.employeeid,
DATEPART(YEAR, OrderDate) AS year,
SUM(UnitPrice * Quantity) AS total_sale
FROM Employees e
INNER JOIN Orders o ON e.EmployeeID = o.EmployeeID
INNER JOIN [Order Details] od ON o.OrderID = od.OrderID
GROUP BY e.employeeid, DATEPART(YEAR, OrderDate)
)
SELECT
year AS "Year",
FirstName + ' ' + LastName AS "Employee",
total_sale AS "Total year sale"
FROM employee_total_sales ets
JOIN employees e ON e.employeeid = ets.employeeid
WHERE total_sales IN (
SELECT
MAX(total_sale)
FROM employee_total_sales
GROUP BY year
)
ORDER BY year

Related

Find employee who made highest sales to the customer who made most purchases

I'm having trouble with this question in my Database homework And need to answer this question:
Which employee has the highest sales to the customer who has made the most purchases?
And these are my database tables
And these are my attempts to write this query
--select Customers.Firstname,Products.Name,Sales.Quantity from Customers
--inner join Sales
--on Customers.CustomerId=Sales.CustomerId
--inner join Products
--on Sales.productId=Products.ProductId
--where Products.Name like 'Mobile'
--Select Customers.CustomerId,max(COUNT(Customers.CustomerId)) As Customecount,Emploees.EmploeeId,max(COUNT(Emploees.EmploeeId))as EmploeeeCount from Emploees
--inner join Sales
--on Emploees.EmploeeId=Sales.EmploeeId
--inner join Customers
--on Customers.CustomerId=Sales.CustomerId
--group by Customers.CustomerId,Emploees.EmploeeId ,Count(Sales.productId)as productCount,Count(Emploees.EmploeeId)as emploeeCount,Count(Customers.CustomerId)as customerCount
select * from
(select Distinct Customers.CustomerId,Sales.productId,COUNT(Sales.productId)as CountProduct from Customers
inner join Sales
on Customers.CustomerId=Sales.CustomerId
inner join Emploees
on Emploees.EmploeeId=Sales.EmploeeId
group by Sales.productId,Emploees.EmploeeId,Customers.CustomerId,Sales.productId) as Result
--gr
But these don't work
Please, help me to write this Query.
Try to solve the problem step by step. Find the customer id with most orders by total:
SELECT TOP 1 sales.customerid
FROM sales
JOIN products ON sales.productid = products.productid
GROUP BY sales.customerid
ORDER BY SUM(sales.quantity * products.price) DESC
Next step is to find the employee with most sales by count to that customer (changing it to by total is trivial):
SELECT TOP 1 sales.salespersonid
FROM sales
WHERE sales.customerid = (
SELECT TOP 1 sales.customerid
FROM sales
JOIN products ON sales.productid = products.productid
GROUP BY sales.customerid
ORDER BY SUM(sales.quantity * products.price)
)
GROUP BY sales.salespersonid
ORDER BY COUNT(sales.salesid) DESC
Finally select the employee record:
SELECT *
FROM employee
WHERE employeeid = (
SELECT TOP 1 sales.salespersonid
FROM sales
WHERE sales.customerid = (
SELECT TOP 1 sales.customerid
FROM sales
JOIN products ON sales.productid = products.productid
GROUP BY sales.customerid
ORDER BY SUM(sales.quantity * products.price)
)
GROUP BY sales.salespersonid
ORDER BY COUNT(sales.salesid) DESC
)
Maybe something like this....
First get the customer with most purchases and then find all employees who have sold to that customer and return the top 1 employee with the most sales.
SELECT TOP (1)
e.EmploeeId
, SUM(s.quantity * p.Price) TotalSales
FROM Emploees e
inner join Sales s ON e.EmploeeId = s.EmploeeId
inner join Product p ON s.productId = s.productId
WHERE s.CustomerId = (
-- Get the customer with most purchases
SELECT TOP (1) x.CustomerId
FROM ( SELECT
c.CustomerId
, SUM(s.quantity * p.Price) TotalSales
FROM Customers c
inner join Sales s ON c.CustomerId = s.CustomerId
inner join Product p ON s.productId = o.productId
GROUP BY c.CustomerId
) x
ORDER BY TotalSales DESC
)
GROUP BY e.EmploeeId
ORDER BY TotalSales DESC
To find Most Sales and Purchases by count (Number of sales/Purchases) following query will do the trick:
SELECT TOP (1)
e.EmploeeId
, COUNT(*) TotalSales
FROM Emploees e
inner join Sales s ON e.EmploeeId = s.EmploeeId
WHERE s.CustomerId = (
SELECT TOP (1) x.CustomerId
FROM ( SELECT
c.CustomerId
, COUNT(*) TotalSales
FROM Customers c
inner join Sales s ON c.CustomerId = s.CustomerId
GROUP BY c.CustomerId
) x
ORDER BY TotalSales DESC
)
GROUP BY e.EmploeeId
ORDER BY TotalSales DESC
In one of the comments you said that "most sales" mean more sale quantity. This answer takes this criteria into consideration.
SELECT TOP (1) SalesPersonID,
(FirstName + ' ' + MiddleName + ' ' + LastName) AS EmployeeName
FROM Sales S
JOIN Employees E ON S.SalesPersonID = E.EmployeeID
WHERE CustomerID =
(
-- Sub-query that returnes CustomerID with most quantities bought
SELECT TOP (1) CustomerID
FROM Sales
GROUP BY CustomerID
ORDER BY SUM(Quantity) DESC
)
GROUP BY SalesPersonID,
(FirstName + ' ' + MiddleName + ' ' + LastName)
ORDER BY SUM(Quantity)

T-SQL SELECT with multiple variables

I am trying to get this output using a SQL statement and the NORTHWIND database:
Employee Name:Nancy Davolio
Number of Sales:345
Total Sales:192107.60
Employee Name:Andrew Fuller
Number of Sales:241
Total Sales:166537.75
Employee Name:Janet Leverling
Number of Sales:321
Total Sales:202812.84
Employee Name:Margaret Peacock
Number of Sales:420
Total Sales:232890.85
Employee Name:Steven Buchanan
Number of Sales:117
Total Sales:68792.28
...and 4 more entries
When I use this statement:
USE Northwind
DECLARE #EmployeeName VARCHAR(40),
#NumberOfSales INT,
#TotalSales DECIMAL(10,2),
#Counter TINYINT = 1,
#NumEmployees INT = IDENT_CURRENT('dbo.Employees');
WHILE #Counter < #NumEmployees
BEGIN
--SELECT #EmployeeName = E.FirstName+' '+E.LastName
--SELECT #NumberOfSales = count(od.OrderID)
SELECT #TotalSales = SUM(unitprice * quantity * (1 - Discount))
FROM Employees E
JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
WHERE E.EmployeeID = #Counter
PRINT 'Employee Name: '--+ #EmployeeName;
PRINT 'Number of Sales: '--+ LTRIM(STR(#NumberOfSales));
PRINT 'Total Sales: '+CONVERT(varchar(10),#TotalSales);
PRINT '';
SET #Counter += 1;
END
I can get each select to work singly but I cannot figure out the syntax to get a single SELECT statement to do all the work. I should also be able to do this with three SET statements but I've not been able to figure that out either. Pointers to both possibilities would be awesome.
Here's that actual step verbiage:
"Within the loop, use a SELECT statement to retrieve the first and last name of each employee, the number of orders handled by each employee and the total sales amount for each employee (you are processing each employee one by one). You will need to join multiple tables together and use aggregate functions to get a count and a total. Assign the concatenated full name, number of sales and total sales amount to the appropriate variables."
Output should be in Messages tab, no table or format other than the expected output listed above.
There is no need for loop(RBAR - Row By Agonizing Row approach should be avoided if possible):
SELECT EmployeeID
,[Employee Name] = E.FirstName+' '+E.LastName
,[TotalSales] = SUM(unitprice * quantity * (1-Discount))
,[NumberOfSales] = COUNT(DISTINCT o.OrderID)
FROM Employees E
JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
GROUP BY E.EmployeeID, E.FirstName+' '+E.LastName
ORDER BY E.EmployeeID;
EDIT:
Loop version - assigning multiple variables at once.
USE Northwind
DECLARE #EmployeeName VARCHAR(40),
#NumberOfSales INT,
#TotalSales DECIMAL(10,2),
#Counter TINYINT = 1,
#NumEmployees INT = IDENT_CURRENT('dbo.Employees');
WHILE #Counter < #NumEmployees
BEGIN
SELECT #EmployeeName = E.FirstName+' '+E.LastName
,#NumberOfSales = COUNT(DISTINCT o.OrderID)
,#TotalSales = SUM(unitprice * quantity * (1 - Discount))
FROM Employees E
JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
WHERE E.EmployeeID = #Counter
GROUP BY E.FirstName+' '+E.LastName;
PRINT 'Employee Name: '+ #EmployeeName;
PRINT 'Number of Sales: '+ LTRIM(STR(#NumberOfSales));
PRINT 'Total Sales: '+ CONVERT(varchar(10),#TotalSales);
PRINT '';
SET #Counter += 1;
END
Please note that using WHILE loop maybe very inefficient when you have gaps(i.e. you are starting from 1 up to IDENT_CURRENT, it may be a situation where you have ids like 1,5, 200671 and you end up with unecessary looping).
EDIT 2:
It seems the GROUP BY is required when multiple assigns take place in the select
I've added GROUP BY because FirstName and LastName was not wrapped with aggregated function. You could skip that clause but then you need to add MIN/MAX function:
SELECT #EmployeeName = MIN(E.FirstName)+' '+MIN(E.LastName)
,#NumberOfSales = COUNT(DISTINCT o.OrderID)
,#TotalSales = SUM(unitprice * quantity * (1 - Discount))
FROM Employees E
JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
WHERE E.EmployeeID = #Counter;
-- and we are sure that all values for First/Last nane are the same because of
-- WHERE E.EmployeeID = #Counter
Related: Group by clause
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause
This should do it. I used CROSS APPLY to unpivot the set and then format it accordingly. You can read more about it in the article called: "CROSS APPLY an Alternative Method to Unpivot". Since SQL works with sets, input and output from SQL should always be a set in my humble opinion.
I am afraid that the way you formatted might not be a SQL's job but still do-able with a "single" select statement as a set operation:
;WITH CTE AS
(
SELECT
EMPLOYEENAME = E.FirstName +' '+ E.LastName,
NUMBEROFORDERS = COUNT(OD.OrderID),
TOTALSALES = SUM(unitprice * quantity * (1-Discount))
FROM Employees E
INNER JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
INNER JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
GROUP BY E.FirstName + ' ' + E.LastName
)
SELECT COLNAME, ColValue
FROM CTE
CROSS APPLY ( VALUES ('Employe Name:', EMPLOYEENAME),
('Number of Sales:', LTRIM(STR(NUMBEROFORDERS, 25, 5)) ),
('Total Sales:', LTRIM(STR(TOTALSALES, 25, 5)) ),
('','')
) A (COLNAME, ColValue)
Sample output is following:
COLNAME ColValue
------------- | -------------
Employe Name: | Nancy Davolio
Number of Sales:| 345.00000
Total Sales: | 192107.60432

SQL Distinct Sum

SELECT DISTINCT
E.FirstName + ' ' + E.LastName [Full Name],
P.ProductName,
OD.Quantity
FROM Employees E,
Products P,
[Order Details] OD,
Orders O
WHERE
E.EmployeeID = O.EmployeeID
AND O.OrderID = OD.OrderID
AND OD.ProductID = P.ProductID
In the Northwind gives back duplicate FullNames and ProductNames because of the Quantity which is changed (because of the date shipped each time).
I want to present only a Name to a specific ProductName with the Total Quantity and not divided.
You need to use GROUP BY with SUM:
SELECT
e.FirstName + ' ' + e.LastName AS [Full Name],
p.ProductName,
SUM(od.Quantity) AS [Quantity]
FROM Employees e
INNER JOIN Orders o
ON o.EmployeeID = e.EmployeeID
INNER JOIN [Order Details] od
ON od.OrderID = o.OrderID
INNER JOIN Products p
ON p.ProductID = od.ProductID
GROUP BY
e.FirstName + ' ' + e.LastName,
p.ProductName
Note, you need to stop using the old-style JOIN syntax.
I think,it was a good question for discussion.
Correct query always depend upon your actual requirement.
I think your table is too much normalise.In such situation most of them will also keep Employeeid in order_detail table.
At the same time,most of them keep sum value in Order table.
Like sum of quantity,sum of amount etc per orderid in order table.
you can also create view without aggregate function joining all the table.
IMHO,Using Group By clause on so many column and that too on varchar column is bad idea.
Try something like this,
;With CTE as
(
SELECT
E.FirstName + ' ' + E.LastName [Full Name],
O.OrderID,od.qty,P.ProductName
FROM Employees E
inner join Orders O on E.EmployeeID = O.EmployeeID
inner join [Order Details] OD on o.orderid=od.orderid
inner join [Products] P on p.ProductID=od.ProductID
)
,CTE1 as
(
select od.orderid, sum(qty) TotalQty
from CTE c
group by c.orderid
)
select c.[Full Name],c1.TotalQty, P.ProductName from cte c
inner join cte1 c1 on c.orderid=c1.orderid

SQL Server 2012 not getting values from subqueries

I am pretty new to SQL Server 2012 and I am doing an exercise where I am trying to obtain
1)The EmployeeID, LastName, and FirstName for each employee
2)The OrderID and the total price of the most expensive order
3)The ShipName
I have gotten most of the way there, getting all the info for each employee and the total price of their most expensive order.
Here is the code I have gotten done so far:
select e.EmployeeID, e.FirstName, e.LastName, max(od.totPrice) as mostExpTotOrder
from Employees e
inner join
Orders o
on e.EmployeeID = o.EmployeeID
inner join
(
select OrderID, sum(unitprice) as totPrice
from [Order Details]
where discount = 0
group by OrderID
) od
on o.OrderID = od.OrderID
group by e.EmployeeID, e.FirstName, e.LastName
When I try to enter in o.OrderID and o.ShipName to the outer query, the query grabs every OrderID, whereas I am just trying to get the OrderID of the most expensive order each employee has processed.
select e.EmployeeID, e.FirstName, e.LastName, max(od.totPrice) as mostExpTotOrder, o.OrderID, o.ShipName
from Employees e
inner join
Orders o
on e.EmployeeID = o.EmployeeID
inner join
(
select OrderID, sum(unitprice) as totPrice
from [Order Details]
where discount = 0
group by OrderID
) od
on o.OrderID = od.OrderID
group by e.EmployeeID, e.FirstName, e.LastName, o.OrderID, o.ShipName
Am I missing something pretty basic?
When you add the order Id to the aggregation, Then you get what you ask for.
You are getting the max price per employee PER ORDER.
You didn't mention in any way in your query that you want the order for which the max price was associated to.
To achieve that, you'll need to join the Aggregated Query by the employee Id with the original query, for example by the employee Id and the Price (Assuming both will identify the row uniquely) and then select the order Id.
A small sample to demonstrate:
SELECT *
into #tmp
FROM
(
select 1 as OrderId , 1 as EmployeeId , 100 as Price
UNION
select 2 as OrderId , 1 as EmployeeId , 200 as Price
UNION
select 3 as OrderId , 2 as EmployeeId , 100 as Price
UNION
select 4 as OrderId , 2 as EmployeeId , 200 as Price
UNION
select 5 as OrderId , 2 as EmployeeId , 300 as Price
) a
SELECT a.* , b.OrderId
FROM (
SELECT EmployeeId , Max(Price) Price
FROM #tmp a
group by EmployeeId
) a
inner join #tmp b
on a.EmployeeId = b.EmployeeId
and a.Price = b.Price

SQL-Server, confront Customers with Employees function

I might have a really simple question, but it doesn't seem i can find the solution yet. Basically, i have to write a SQL-Server function, based on Northwind DB. It has to:
Take 2 dates as arguments and display, without repetition these Customer's data ID | Name | City | Address , for those customers where the total purchase he had made from at least one Employee , is greater than the average sale made by this Employee between the two dates.
So the main steps should be:
1. Retrieve the total purchase made from a Customer from each Employee. I know how to get the total purchase from each Company:
SELECT
Customers.CompanyName, SUM(UnitPrice*Quantity)
FROM Orders inner join [Order Details]
ON Orders.OrderID=[Order Details].OrderID INNER JOIN
Customers ON Orders.CustomerID=Customers.CustomerID
GROUP BY Customers.CompanyName
But how can i get those made from each employee?
2.Confront this with the average sales of this Employee between the given dates. I can get the average for each employee:
SELECT FirstName+' '+LastName, AVG(UnitPrice*Quantity)
FROM Orders inner join [Order Details]
ON Orders.OrderID=[Order Details].OrderID
INNER JOIN Employees
ON Orders.EmployeeID=Employees.EmployeeID
WHERE OrderDate BETWEEN #dt1 and #dt2
GROUP BY FirstName+' '+LastName
Note that i'm only pasting the query part, but here, the Employee should depend on the first query (probably this should be put inside a subquery)
Everything should be put inside a single function (it should not be split in two). The Northwind DB diagram is: Northwind Diagram . Please help!
Hope I got the logic right:
create function x (#from datetime, #to datetime)
returns table
as
return (
with cust as (
select o.customerid, o.employeeid, sum(unitprice*quantity) as cust_purchase
from orders o
inner join [order details] d on o.orderid=d.orderid
where o.orderdate between #from and #to
group by o.customerid, o.employeeid
),
emp as (
select o.employeeid, avg(unitprice*quantity) as emp_sale
from orders o
inner join [order details] d on o.orderid=d.orderid
where o.orderdate between #from and #to
group by o.employeeid
)
select c.customerid, c.companyname, c.city, c.address
from cust
inner join emp on cust.employeeid = emp.employeeid
and cust.cust_purchase > emp.emp_sale
inner join customers c on cust.customerid = c.customerid
)
go
select * from x ('19980401', '19980430')