joining tables , data between dates - sql

I am practicing with northwind database: I am quite new to sql.
Question I am trying to solve is :
Q. Total Sales for each customer in October 1996 (based on OrderDate). Show the result in CustomerID, CompanyName, and [total sales], sorted in [total sales] in Decending order.
I have used this code but doesn't seems to be correct , please advise.
select c.customerid , c.companyname , o.orderdate , sum(od.unitprice *od.Quantity*1-od.Discount) as totalsales
from customers as c , orders as o , [Order Details] as od
where o.customerid = c.CustomerID
and o.OrderID = od.OrderID
and o.OrderDate >= '1996/10/01' and o.orderdate <= '1996/10/31'
group by c.customerid , c.companyname, o.orderdate
order by totalsales desc
;
*******************************

I suspect is it just the method of calculation inside the sum function
SELECT
c.customerid
, c.companyname
, o.orderdate
, SUM((od.unitprice * od.Quantity) * (1 - od.Discount)) AS totalsales
FROM customers AS c
INNER JOIN orders AS o ON o.customerid = c.CustomerID
INNER JOIN [Order Details] AS od ON o.OrderID = od.OrderID
WHERE o.OrderDate >= '1996-10-01'
AND o.orderdate < '1996-11-01' -- move up one day, use less than
GROUP BY
c.customerid
, c.companyname
, o.orderdate
ORDER BY
totalsales DESC
;
(od.unitprice * od.Quantity) provides total discounted price, then
the discount rate is (1 - od.Discount)
multiply those (od.unitprice * od.Quantity) * (1 - od.Discount) for total discounted price
Please note I have changed the syntax of the joins! PLEASE learn this more modern syntax. Don't use commas between table names in the from clause, then conditions such as AND o.customerid = c.CustomerID move to after ON instead of within the where clause..
Also, the most reliable date literals in SQL Server are yyyymmdd and the second best is yyyy-mm-dd. It's good to see you using year first, but I would suggest using dashes not slashes, or (even better) no delimiter. e.g.
WHERE o.OrderDate >= '19961001'
AND o.orderdate < '19961101'
Also note that I have removed the <= and replaced it with < and moved that higher date to the first of the next month. It is actually easier this way as every month has a day 1, just use less than this higher date.

Related

Challenging PostgreSQL SELECT Statement for Northwind Database (NEED HELP - BEGINNER)

I began learning SQL about a month ago and my dad has been giving me practice queries to run
with the Northwind database to help practice my DML. This most recent one he gave me was as follows:
-- Return Month, Product name, SalesForMonth for highest selling product in each
-- month in 1997. (see issues with creating said query below)
(I am currently using PostgreSQL on pgAdmin4)
I was able to come up with the following query that returns the required columns with the correct information for ONLY a single month:
SELECT CAST( EXTRACT( MONTH FROM o.orderdate) AS integer) AS Month, p.productname,
ROUND(CAST(SUM(od.quantity * od.unitprice) AS numeric), 2) SalesForMonth
FROM order_details od
INNER JOIN orders o ON od.orderid = o.orderid
INNER JOIN products p ON od.productid = p.productid
WHERE EXTRACT( YEAR FROM o.orderdate) = 1997 AND EXTRACT( MONTH FROM o.orderdate) = 1
GROUP BY Month, p.productname
ORDER BY salesformonth DESC
LIMIT 1
By making 12 of these queries and changing the extract-month bit in the WHERE statement from 1-12 and UNIONing them all together, I can produce the desired result but I wondered if there was an easier way that I was missing to display the same result but only using 1 query. Interested to see what y'all can come up with.
SIDE NOTE: My initial thought is that it has something to do with subqueries because what you're effectively trying to do is display the MAX(SUM(values)) but can't actually do that since you can't nest aggregate function.
Your query gives you the top selling product for a given month, and you want the same logic for multipe months at once.
Starting from your existing and working query, a simple approach is to use WITH TIES:
SELECT DATE_TRUNC('month', o.orderdate) DateMonth,
p.productname,
ROUND(CAST(SUM(od.quantity * od.unitprice) AS numeric), 2) SalesForMonth
FROM order_details od
INNER JOIN orders o ON od.orderid = o.orderid
INNER JOIN products p ON od.productid = p.productid
WHERE o.orderdate >= DATE '1997-01-01' AND o.orderdate < DATE '1998-01-01'
GROUP BY DateMonth, p.productname
ORDER BY RANK() OVER(
PARTITION BY DATE_TRUNC('month', o.orderdate)
ORDER BY SUM(od.quantity * od.unitprice) DESC
)
FETCH FIRST ROW WITH TIES
We can also use DISTINCT ON:
SELECT DISTINCT ON (DateMonth)
DATE_TRUNC('month', o.orderdate) DateMonth,
p.productname,
ROUND(CAST(SUM(od.quantity * od.unitprice) AS numeric), 2) SalesForMonth
FROM order_details od
INNER JOIN orders o ON od.orderid = o.orderid
INNER JOIN products p ON od.productid = p.productid
WHERE o.orderdate >= DATE '1997-01-01' AND o.orderdate < DATE '1998-01-01'
GROUP BY DateMonth, p.productname
ORDER BY DateMonth, SalesForMonth DESC

New record after a year of no records

I want to find returning customers who have placed an order after a year without orders. I have managed the below but am having a tough time adding the year gap. Something like "and count of orders between dates = 0"... any ideas would be appreciated, I cant seem to figure out the required syntax at all.
SELECT
Min(Orders.[Order Date]) AS [MinOfOrder Date],
Max(Orders.[Order Date]) AS [MaxOfOrder Date],
Orders.CustomerID
FROM
Customers
INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID
GROUP BY
Orders.CustomerID
HAVING
(
((Min(Orders.[Order Date])) < Date() -365)
AND ((Max(Orders.[Order Date])) > Date() -30)
);
You can use exists and not exists to get the first order after a year gap:
select o.*
from orders as o
where exists (select 1
from orders as o2
where o2.customerid = o.customerid and
o2.orderdate < dateadd("yyyy", o.orderdate, -1)
) and
not exists (select 1
from orders as o2
where o2.customerid = o.customerid and
o2.orderdate >= dateadd("yyyy", o.orderdate, -1) and
o2.orderdate < o.orderdate
);
You can join in the customers information if you need that.
Selecting records based on values in other records is tricky. Use a correlated subquery to pull value from another record. Need a unique identifier field - autonumber should serve. Consider:
Query1:
SELECT Orders.*, (SELECT Max(OrderDate)
FROM Orders AS Dupe
WHERE Dupe.CustomerID = Orders.CustomerID
AND Dupe.ID < Orders.ID) AS PrevOrderDate
FROM Orders;
Query2:
SELECT Query1.* FROM Query1 WHERE ((([OrderDate]-[PrevOrderDate])>365));

Creating an SQL query that eliminates duplicate months/year

Hello Stack Overflow community - hopefully i'm on the right track with this one, but i'm trying to write a query where a report out shows the number of orders placed by month/year. The report currently brings up all the days where i'm trying to join them all by month/year collectively. Hopefully this makes sense, i'm pretty new to this, be gentle please ;)
select distinct month(o.orderdate) 'Month',
year(o.orderdate) 'Year', sum(od.Quantity) as Orders
from OrderDetails od
join Products p
on od.ProductID = p.ProductID
join Orders o
on od.OrderID = o.OrderID
group by o.orderdate
Order by year, month asc;
You need to group by what you want to define each row. In your case, that is the year and month:
select year(orderdate) as yyyy, month(o.orderdate) as mm,
sum(od.Quantity) as Orders
from OrderDetails od join
Products p
on od.ProductID = p.ProductID join
Orders o
on od.OrderID = o.OrderID
group by year(o.orderdate), month(o.orderdate)
Order by yyyy, mm asc;
Notes:
I changed the column names to yyyy and mm so they do not conflict with the reserved words year and month.
Don't use single quotes for column aliases. This is a bad habit that will eventually cause problems in your query.
I always use as for column aliases (to help prevent missing comma mistakes), but never for table aliases.
The product table is not needed for this query.
Edit: If you want a count of orders, which your query suggests, then this might be more appropriate:
select year(o.orderdate) as yyyy, month(o.o.orderdate) as mm,
count(*) as Orders
from orders o
group by year(o.orderdate), month(o.orderdate)
Order by yyyy, mm asc;
You have to group by month and year
select distinct month(o.orderdate) 'Month',
year(o.orderdate) 'Year', sum(od.Quantity) as Orders
from OrderDetails od
join Products p
on od.ProductID = p.ProductID
join Orders o
on od.OrderID = o.OrderID
group by month(o.orderdate), year(o.orderdate)
Order by [Year],[month]

Left join returning bad values

I'm not very good with SQL queries but I attempted to write this one:
SELECT DATEPART(YY,Orders.OrderDate) as Year,
DATEPART(MM,Orders.OrderDate) as Month,
(SUM(case when OrderDetails.ProductCode = 'XXX' then
OrderDetails.ProductPrice else 0 end) + SUM(Orders.Total))
AS XXX
FROM Orders
LEFT JOIN OrderDetails ON Orders.OrderID = OrderDetails.OrderID
WHERE Orders.OrderStatus = 'Shipped'
GROUP BY DATEPART(MM,Orders.OrderDate), DATEPART(YY,Orders.OrderDate)
ORDER BY DATEPART(YY,Orders.OrderDate),DATEPART(MM,Orders.OrderDate)
The OrderDetails is linked to the Orders table by the field OrderID. In this SELECT query I'm trying to get the SUM of OrderDetails.ProductPrice when the OrderDetails.ProductCode is XXX and add it to the Orders.Total to get total amounts for each month/year.
The query is working except for one problem (that's probably either a amateur mistake or has been worked around several times). When performing the LEFT JOIN, the OrderDetails table can have multiple records linked to the Orders table which is throwing bad results in the SUM(Orders.Total). I've isolated that issue I just can't seem to fix it.
Can anybody point me in the right direction?
If we assume that the XXX product only appears at most once for each order, then this should work:
SELECT year(o.OrderDate) as Year, month(o.OrderDate) as Month,
(COALESCE(SUM(od.ProductPrice), 0) + SUM(o.Total)) AS XXX
FROM Orders o LEFT JOIN
OrderDetails od
ON o.OrderID = od.OrderID AND od.ProductCode = 'XXX'
WHERE o.OrderStatus = 'Shipped'
GROUP BY year(o.OrderDate), month(o.OrderDate)
ORDER BY year(o.OrderDate), month(o.OrderDate);
If it can appear multiple times, then move that part of the aggregation to a subquery:
SELECT year(o.OrderDate) as Year, month(o.OrderDate) as Month,
(COALESCE(XXX, 0) + SUM(o.Total)) AS XXX
FROM Orders o LEFT JOIN
(SELECT od.OrderId, SUM(od.ProductPrice) as XXX
FROM OrderDetails od
WHERE od.ProductCode = 'XXX'
GROUP BY od.OrderId
) od
ON o.OrderID = od.OrderID
WHERE o.OrderStatus = 'Shipped'
GROUP BY year(o.OrderDate), month(o.OrderDate)
ORDER BY year(o.OrderDate), month(o.OrderDate);

Does this SQL statement require a nested SELECT query?

Here is what I have so far but the results are wrong.
SELECT c.CompanyName,
COUNT(o.OrderID) AS [Total Orders],
SUM(
(od.UnitPrice -
(od.UnitPrice * od.Discount))* Quantity) AS [Purchase Total]
FROM Customers AS c,
Orders AS o,
[Order Details] AS od
WHERE c.CustomerID = o.CustomerID
AND o.OrderID = od.OrderID
GROUP BY c.CompanyName
ORDER BY c.CompanyName;
The issue I am having is with the count, it is off by double or more. I believe that this is because the OrderID appears multiple times in the Order Details table. I think I need a nested SELECT statement but I am unsure how to do that.
Would I be removing the SUM() expression, Order Details, and the AND clause from the first query? Or am I way off?
With help I have gotten the COUNT field to work but now my SUM field is wrong. This is my most recent attempt and it produces the same value for every customer.
SELECT c.CompanyName,
COUNT(o.OrderID) AS [Total Orders],
(SELECT SUM(
(odIN.UnitPrice -
(odIN.UnitPrice * odIN.Discount)) * odIN.Quantity) AS [OrderTotal]
FROM [Order Details] AS odIN, Orders As oIN
WHERE odIN.OrderID = oIN.OrderID) AS [Purchase Total]
FROM
Customers AS c, Orders AS o
WHERE c.CustomerID = o.CustomerID
GROUP BY c.CompanyName
ORDER BY c.CompanyName;
I was unsuccessful at getting the query to fully work the way I wanted it to. Then I realized that maybe maybe I was looking for the wrong data. So I switched the name for the COUNT field to Num Products Purchased.
I would still like to get the other way working, but I think that will require creating a temporary table or view that could be used to do one of the calculations and then call it from the query. That is something I'll have to figure out.
Thank you for the attempts to help.
Because Access doesn't have COUNT(DISTINCT) then you need to create an inner query.
What this does is compute the sum of each item in an order in the inner query, and then sums up all the order totals for the customer as the purchase total. An individual OrderID will not be counted twice, as o and od now have a one to one relationship.
There might be a syntax error in there somewhere, but the idea should work.
SELECT c.CompanyName,
COUNT(o.OrderID) AS [Total Orders],
SUM(od.OrderTotal) AS [Purchase Total]
FROM
Customers AS c,
Orders AS o,
(SELECT odIn.OrderID,
SUM(
(odIn.UnitPrice -
(odIn.UnitPrice * odIn.Discount)) * odIn.Quantity) AS [OrderTotal]
FROM [Order Details] AS odIn
GROUP BY odIn.OrderID) AS od
WHERE c.CustomerID = o.CustomerID
AND o.OrderID = od.OrderID
GROUP BY c.CompanyName
ORDER BY c.CompanyName;
if the problem is because OrderID appears multiple times, try:
SELECT c.CompanyName, COUNT(DISTINCT o.OrderID) AS [Total Orders], SUM((od.UnitPrice - (od.UnitPrice * od.Discount)) * Quantity) AS [Purchase Total]
FROM Customers AS c, Orders AS o, [Order Details] AS od
WHERE c.CustomerID = o.CustomerID AND o.OrderID = od.OrderID
GROUP BY c.CompanyName
ORDER BY c.CompanyName;
The distinct clause lets you count only each appearance.
First I would like to thank Daniel for his help. I did finally get the query to work with help from another source. Daniel's solution is better as it requires less code and even formats the sum in currency.
Here is the one I got to work:
SELECT vtOrdCnt.*, ROUND(vtTotCost.PurchaseTotal, 2) AS [Purchase Total]
FROM
(
SELECT CustomerID, COUNT(OrderID) AS TotalOrders
FROM orders
GROUP BY CustomerID
) AS vtOrdCnt,
(
SELECT CustomerID, SUM(UnitPrice * (1-Discount)*Quantity) AS PurchaseTotal
FROM Orders AS o, [Order Details] AS od
WHERE o.orderID = od.orderID
GROUP BY CustomerID
) AS vtTotCost
WHERE vtOrdCnt.CustomerID = vtTotCost.CustomerID
ORDER BY vtOrdCnt.CustomerId
By using Aliases and two select statements in the FROM clause it allowed the query to function the way I wanted it to.