get no value when I run the query to this question - sql

The assignment question is...
List the order's customer name, order status, date ordered, count of items on the order, and average quantity ordered where the count of items on the order is greater than 300.
I'm using Adventure Works 2019 for the assignment.
The answer I have been able to come up with is....
SELECT
LastName + ', ' + FirstName AS 'Customer Name',
ssoh.Status AS 'Order Status',
ssoh.OrderDate AS 'Date Order',
SUM(ssod.Orderqty) AS 'Count of Items',
AVG(ssod.Orderqty) AS 'Average Quantity'
FROM
Person.Person pp
JOIN Sales.SalesOrderHeader ssoh ON pp.BusinessEntityID = ssoh.CustomerID
JOIN Sales.SalesOrderDetail ssod on ssoh.SalesOrderID = ssod.SalesOrderid
GROUP BY
LastName + ', ' + FirstName,
ssoh.OrderDate,
ssoh.Status
HAVING
SUM(ssod.OrderQty) > 300
When I change the inner join to an outer join I get null values for the customer name. I doubled checked the "customerID" foreign key to make sure it the same as the primary key "BusinessEntityID" and get results. Any ideas would be greatly appreciated. Thanks

Based on having a quick look at the schema online (I don't have it installed), it's my understanding that you need to join Person.Person through Sales.Customer to Sales.SalesOrderHeader. So perhaps try the following:
FROM
Person.Person pp
JOIN Sales.Customer sc ON pp.BusinessEntityID= sc.PersonID
JOIN Sales.SalesOrderHeader ssoh ON sc.CustomerID= ssoh.CustomerID
JOIN Sales.SalesOrderDetail ssod on ssoh.SalesOrderID = ssod.SalesOrderid

First, grouping by LastName + ', ' + FirstName will provide you inaccurate results.
Because there are approx 380 names with duplications. Sample:
Second, Manachi is right about the join.
So, your final query should be like this:
WITH cte AS (
SELECT
BusinessEntityID AS CustomerId,
LastName + ', ' + FirstName AS 'Customer Name',
ssoh.Status AS 'Order Status',
ssoh.OrderDate AS 'Date Order',
SUM(ssod.Orderqty) AS 'Count of Items',
AVG(ssod.Orderqty) AS 'Average Quantity'
FROM
Person.Person pp
JOIN Sales.Customer c ON c.PersonID = pp.BusinessEntityID
JOIN Sales.SalesOrderHeader ssoh ON c.CustomerID = ssoh.CustomerID
JOIN Sales.SalesOrderDetail ssod on ssoh.SalesOrderID = ssod.SalesOrderid
GROUP BY
BusinessEntityID,
LastName + ', ' + FirstName,
ssoh.OrderDate,
ssoh.Status
HAVING
SUM(ssod.OrderQty) > 300
)
SELECT
[Customer Name],
[Order Status],
[Date Order],
[Count of Items],
[Average Quantity]
FROM cte

Related

SQL double group by

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

Top sales performer is in each month for a specified year

Using the adventure works 2017 test database I need to see who the top sales performer is in each month for a specified year. The management is only interested in the sales of bike “Components”. Create a stored procedure to get this information.
The year must be an input parameter.
Show the firstname and surname in one field.
Show the total value of the sales and the month for each top performer.
Additional marks will be allocated for using a single statement
So far I have this:
CREATE PROCEDURE getTopSalesByYear (#Year int)
AS
BEGIN
SET NOCOUNT ON
SELECT FirstName + ' ' + LastName AS SalesPerson,
sp.BusinessEntityID,
DATENAME(MONTH,SOH.OrderDate) as SalesMonth,
SUM(SOH.SubTotal) AS TotalSales FROM sales.SalesOrderHeader SOH
INNER JOIN sales.SalesOrderDetail SOD ON SOH.SalesOrderId = SOD.SalesOrderId
INNER JOIN sales.SalesPerson sp on soh.SalesPersonID = sp.BusinessEntityID
INNER JOIN Person.Person p on p.BusinessEntityID = sp.BusinessEntityID
INNER JOIN Production.Product Pr on sod.ProductID = pr.ProductID
INNER JOIN Production.ProductCategory pc on pc.ProductCategoryID = pr.ProductSubcategoryID
INNER JOIN Production.ProductSubcategory psc on pc.ProductCategoryID = pc.ProductcategoryID
WHERE psc.ProductCategoryID = 2
GROUP BY p.FirstName,p.LastName,sp.BusinessEntityID, DATENAME(MONTH,SOH.OrderDate)
ORDER BY TotalSales desc
This is what I have so far but it needs to be a procedure with the year being passed. Also noting that I do not know where to pass the parameter to what value.
You need to use ROW_NUMBER to get the best performer per month.
To check that a date is within a particular year, instead of comapring using the YEAR function, it is best to calculate the beginning and end points. The end should be exclusive, this is called a half-open interval.
You can also group by EOMONTH (end of month) which can be a little more efficient than grouping by DATENAME(MONTH, you can calculate the actual name afterwards.
CREATE PROCEDURE getTopSalesByYear (#Year int)
AS
SET NOCOUNT ON;
SELECT
s.FirstName + ' ' + s.LastName AS SalesPerson,
DATENAME(MONTH, s.SalesMonth) AS SalesMonth,
s.TotalSales
FROM (
SELECT
p.FirstName,
p.LastName,
p.BusinessEntityID,
EOMONTH(SOH.OrderDate) AS SalesMonth,
SUM(SOH.SubTotal) AS TotalSales,
ROW_NUMBER() OVER (PARTITION BY EOMONTH(SOH.OrderDate) ORDER BY SUM(SOH.SubTotal) DESC) AS rn
FROM sales.SalesOrderHeader SOH
INNER JOIN sales.SalesOrderDetail SOD ON SOH.SalesOrderId = SOD.SalesOrderId
INNER JOIN sales.SalesPerson sp on soh.SalesPersonID = sp.BusinessEntityID
INNER JOIN Person.Person p on p.BusinessEntityID = sp.BusinessEntityID
INNER JOIN Production.Product Pr on sod.ProductID = pr.ProductID
WHERE pr.ProductCategoryID = 2
AND SOH.OrderDate >= DATEFROMPARTS(#Year , 1, 1)
AND SOH.OrderDate < DATEFROMPARTS(#Year + 1, 1, 1)
GROUP BY
p.FirstName,
p.LastName,
p.BusinessEntityID,
EOMONTH(SOH.OrderDate)
) s
WHERE s.rn = 1
ORDER BY SalesMonth;
GO
Note that this will only give you results for a month if there are actually sales in that month. If there are no sales, you will not get 0, there will be no row for that month.
Thank you for the assistance, see below finished and working query.
CREATE PROCEDURE getTopSalesByYear (#Year int)
AS
SET NOCOUNT ON;
SELECT
s.FirstName + ' ' + s.LastName AS SalesPerson,
DATENAME(MONTH, s.SalesMonth) AS SalesMonth,
s.TotalSales
FROM (
SELECT
p.FirstName,
p.LastName,
p.BusinessEntityID,
EOMONTH(SOH.OrderDate) AS SalesMonth,
SUM(SOH.SubTotal) AS TotalSales,
ROW_NUMBER() OVER (PARTITION BY EOMONTH(SOH.OrderDate) ORDER BY SUM(SOH.SubTotal) DESC) AS rn
FROM sales.SalesOrderHeader SOH
INNER JOIN sales.SalesOrderDetail SOD ON SOH.SalesOrderId = SOD.SalesOrderId
INNER JOIN sales.SalesPerson sp on soh.SalesPersonID = sp.BusinessEntityID
INNER JOIN Person.Person p on p.BusinessEntityID = sp.BusinessEntityID
INNER JOIN Production.Product Pr on sod.ProductID = pr.ProductID
WHERE Pr.ProductSubcategoryID = 2
AND SOH.OrderDate >= DATEFROMPARTS(#Year , 1, 1)
AND SOH.OrderDate < DATEFROMPARTS(#Year + 1, 1, 1)
GROUP BY
p.FirstName,
p.LastName,
p.BusinessEntityID,
EOMONTH(SOH.OrderDate)
) s
WHERE s.rn = 1
ORDER BY SalesMonth;
GO
--Execute
--Exec getTopSalesByYear 2011

SQL Server - SELECT statement not returning results

Using the AdventureWorks database, I have been given a question to "List the orders customer name, order status, date ordered, count of items on the order, and average quantity ordered where the count of items on the order is greater than 300". However, my below SELECT statement does not return any results... What am I doing wrong?
SELECT scpii.LastName + ', ' + scpii.FirstName AS 'Customer Name', ssoh.Status AS 'Order Status', ssoh.OrderDate AS 'Date Ordered', SUM (ssod.OrderQty) AS 'Count of Items', AVG (ssod.OrderQty) AS 'Average Quantity'
FROM Sales.CustomerPII scpii
INNER JOIN Sales.SalesOrderHeader ssoh
ON ssoh.CustomerID = scpii.CustomerID
INNER JOIN Sales.SalesOrderDetail ssod
ON ssod.SalesOrderID = ssoh.SalesOrderID
GROUP BY scpii.LastName, scpii.FirstName, ssoh.Status, ssoh.OrderDate, ssod.OrderQty
HAVING SUM(ssod.OrderQty) > 300;
You dont need to Group By on ProductID and Orderqty. If you do a groupping on them, you will be grouping it to a single order item level. So you will never be able to count more than 300
Count of items on order should really be sum of the Orderqty.
Try:
SELECT scpii.LastName + ', ' + scpii.FirstName AS 'Customer Name',
ssoh.Status AS 'Order Status',
ssoh.OrderDate AS 'Date Ordered',
SUM (ssod.OrderQty) AS 'Count of Items',
AVG (ssod.OrderQty) AS 'Average Quantity'
FROM Sales.CustomerPII scpii
INNER JOIN Sales.SalesOrderHeader ssoh
ON ssoh.CustomerID = scpii.CustomerID
INNER JOIN Sales.SalesOrderDetail ssod
ON ssod.SalesOrderID = ssoh.SalesOrderID
GROUP BY scpii.LastName,
scpii.FirstName,
ssoh.Status,
ssoh.OrderDate
HAVING SUM (ssod.OrderQty) > 300;

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

Find Average Sales Amount Per Customer using AdventureWorks2012

I am following a tutorial using MS AdventureWorks2012 and I wanted to write a query to find average sales amount per customer (or in other words, Average Sales for each customer) using AdventureWorks2012. Below is my attempt and it doesn't run. What Am I doing wrong here ?
SELECT soh.CustomerID AS 'Customer ID'
,p.FirstName + ' ' + p.LastName AS 'Customer Name'
,AVG(soh.TotalDue) AS 'Average Sales Amount Per Customer'
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.Customer AS c ON c.CustomerID = soh.CustomerID
INNER JOIN Person.BusinessEntityContact AS bec ON bec.PersonID = c.PersonID
INNER JOIN Person.Person AS p ON p.BusinessEntityID = bec.BusinessEntityID
GROUP BY p.FirstName , p.LastName, soh.CustomerID;
Your query runs, it just returns an empty result set.
If you look at BusinessEntityContact, it relates a BusinessEntityID, which is a customer business, to a PersonID, who is a person that is the contact for the business. So if you change your query to this:
SELECT soh.CustomerID AS 'Customer ID', p.FirstName + ' ' + p.LastName AS 'Customer Name',
AVG(soh.TotalDue) AS 'Average Sales Amount Per Customer'
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.Customer AS c ON c.CustomerID = soh.CustomerID
INNER JOIN Person.BusinessEntityContact AS bec ON bec.PersonID = c.PersonID
INNER JOIN Person.Person AS p ON p.BusinessEntityID = bec.PersonID
GROUP BY p.FirstName , p.LastName, soh.CustomerID;
(note the third inner join)
You will get 635 rows.
In SQL single quote ' identifies a string. If you must to have spaces in your result column names then you can identify column names with double quotes " or preferably by wrapping the column name in square brackes.
SELECT
soh.CustomerID AS [Customer ID]
,p.FirstName + ' ' + p.LastName AS [Customer Name]
,AVG(soh.TotalDue) AS [Average Sales Amount Per Customer]
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.Customer AS c ON c.CustomerID = soh.CustomerID
INNER JOIN Person.BusinessEntityContact AS bec ON bec.PersonID = c.PersonID
INNER JOIN Person.Person AS p ON p.BusinessEntityID = bec.BusinessEntityID
GROUP BY p.FirstName , p.LastName, soh.CustomerID;
Otherwise your syntax looks okay to me