Finding out when summed values reached a certain checkpoint in SQL - sql

First of all: I've found some possible answers to my problem in the previously asked questions, but I've encountered problems with getting them to work properly. I know the question was already asked, but the answers always were working code with little to no explaination on the method used.
So: I've got to find out when a customer reached the VIP status, which is when value of his orders exceeds 50 000. I've got 2 tables: one with orderid, customerid and orderdate, and second with orderid, quantity and unitprice.
The result of the query I'm writing should be 3 colums wide, one with the customerid, one with true/false named "is VIP?", and the third is the date of getting the VIP status(which is the date of order that summed with the previous ones gave a result of over 50 000)-the last one should be blank if the customer didn't reach the VIP status
select o.customerid, sum(od.quantity*od.unitprice),
case
when sum(od.quantity*od.unitprice)>50000 then 'VIP'
else 'Normal'
end as 'if vip'
from
orders o join [Order Details] od on od.orderid=o.orderid
group by o.customerid
That is as far as I got with the code, it returns the status of the customer and now I need to get the date when that happend.
.

You can easily calculate a running total using a window functions:
select o.customerid,
o.orderdate,
sum(od.quantity*od.unitprice) over (partition by o.customerid order by orderdate) as running_sum,
from orders o
join Order_Details od on od.orderid = o.orderid
order by customer_id, orderdate;
Now you need to find a way to detect the first row, where the running total exceeds the threshold:
The following query starts numbering the rows in a descending manner once the threshold is reached. Which in turn means the row with then number 1 is the first one to cross the threshold:
with totals as (
select o.customerid,
o.orderdate,
sum(od.quantity*od.unitprice) over (partition by o.customerid order by orderdate) as running_sum,
case
when
sum(od.quantity*od.unitprice) over (partition by o.customerid order by orderdate) > 50000 then row_number() over (partition by o.customerid order by orderdate desc)
else 0
end as rn
from orders o
join Order_Details od on od.orderid = o.orderid
)
select *
from totals
where rn = 1
order by customerid;
SQLFiddle example: http://sqlfiddle.com/#!6/a7f18/3

You get the cumulative sum using an Analytic Function, SUM OVER. And then add an aggregate to find the minimum date:
with cte as
( select o.customerid,
o.orderdate,
case when sum(od.quantity*od.unitprice) -- running total
over (partition by o.customerid
order by orderdate
rows unbounded preceding) > 50000
then 'Y'
else 'N'
end as VIP
from orders o
join Order_Details od on od.orderid = o.orderid
)
select customerid,
MAX(VIP) AS "isVIP?", -- either 'N' or 'Y'
MIN(CASE WHEN VIP = 'Y' THEN orderdate END) AS VIP_date -- only when VIP status reached
from cte
group by customerid
order by customers;
See fiddle

Not going to complicate the answer with logic to show 'vip' and 'vip date'. This will give you a running total for each customer order.
select o.orderid, o.customerid, o.orderdate, sum(od.quantity*od.unitprice) 'Total', (
select sum(od.quantity * od.unitprice) total
from orders o2
join [Order Details] od2 on od2.orderid=o2.orderid
where o2.orderID <= o.orderID
and o2.customerid = o.customerid) 'RunningTotal'
from orders o
join [Order Details] od
on od.orderid=o.orderid
group by o.orderid, o.customerid, o.orderdate
order by o.customerid

To answer your question on how to approach, you could consider going for an SQL trigger which runs on each update to the tables involved and sets the status when the threshold is hit.This would set the date as and when the event happens.
Another approach would be to use a stored procedure wherein you can use a loop top iterate over the records and arrive at the date.
The choice can be made based on the volume of the data, with the former bring suitable for extremely large amounts of data.

Related

How to write this SQL query more elegantly ( joining + max query )

Ok I am using the following example from w3school
https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all
and I want to get the date in which the amount was ordered
SELECT OrderDate
FROM Orders
WHERE OrderID = (SELECT OrderID
FROM OrderDetails
WHERE Quantity = (SELECT MAX(Quantity)
FROM OrderDetails));
This works but my guts tell me I need to use joining or having ??
You want the date of the order that has the maximum quantity.
It does not look like you do need two levels of subqueries. You could use a row-limiting subquery instead:
select orderdate
from orders
where orderid = (select orderid from from orderdetails order by quantity desc limit 1)
This is shorter, and does not fail if there is more than one order with the same, maximum quantity (while your original code does, because the subquery returns more than one row).
Another approach uses window functions:
select o.orderdate
from orders o
inner join (
select od.*, rank() over(order by quantity desc) rn
from orderdetails od
) od on od.orderid = o.orderid
where od.rn = 1
This will properly handle top ties, in the sense that it will return them all (while the first query returns only one of them).
I think this is much cleaner solution!
best regards
select max(od.quantity) as MaxOrder,orderdate
from orderdetails as od inner join orders as o on od.orderid=o.orderid

sql use aggregate function that counts a unique value with group by using inner joins

I searched and found similar questions online but not my particular one, they all use where or having clause.If theres one similar to mine please link it. It's a 2 part question and I have the first one done. Thank you in advance.
Okay so heres the question, part 1
"Find by customer, the total cost and the total discounted cost for each product on the order ?".
It also asks to use inner joins to find the customer and order it a specific way. Below is the answer.
SELECT
C.companyname, O.orderid, O.orderdate, P.productname,
OD.orderid, OD.unitprice, OD.qty, OD.discount,
(OD.unitprice * OD.qty - (OD.qty * OD.discount)) AS TotalCost,
(OD.qty * OD.discount) AS TotalDiscountedCost
FROM
Sales.Customers AS C
INNER JOIN
Sales.Orders AS O ON C.custid = O.custid
INNER JOIN
Sales.OrderDetails OD ON O.orderid = OD.orderid
INNER JOIN
Production.Products as P ON OD.productid = P.productid
ORDER BY
C.companyname, O.orderdate;
Now the second question is to
follow up and resume the first one by "customer and the order date year, the total cost and the total discounted cost on the order ?". It also asks for this, "Project following columns in the select clause as.
GroupByColumns.companyname
GroupByColumns.OrderdateYear
AggregationColumns.CountNumberOfIndividualOrders
AggregationColumns.CountNumberOfProductsOrders
AggregationColumns.TotalCost
AggregationColumns.TotalDiscountedCost
Finally to order by company name and orderdateYear( which are groups). Where im stuck is how to count the specific orders of qty that equal 1 as an aggregate function in the SELECT clause. I know it has to use the aggregate function COUNT because of the GROUP BY, just don't know how to. This is what I have.
SELECT
C.companyname, YEAR(O.orderdate) AS orderyear,OD.qty,
-- Where in the count function or if theres another way do I count all the
--single orders
--COUNT(OD.qty) AS indiviualorders,
(OD.unitprice * OD.qty - (OD.qty * OD.discount)) AS TotalCost,
(OD.qty * OD.discount) AS TotalDiscountedCost
FROM
Sales.Customers AS C
INNER JOIN
Sales.Orders AS O ON C.custid = O.custid
INNER JOIN
Sales.OrderDetails OD ON O.orderid = OD.orderid
INNER JOIN
Production.Products as P ON OD.productid = P.productid
GROUP BY
C.companyname, YEAR(O.orderdate)
ORDER BY
C.companyname, O.orderdate;
You case use a case statement inside a sum
SUM(CASE WHEN <xyz> THEN 1 ELSE 0 END)
But for the count of unique orders, use SELECT(DISTINCT ) on a key that is unique in the order table
SELECT COUNT(DISTINCT O.OrderID) As DistinctOrders FROM Table

Query to pull second order date for a customer(SQL 2014)

I have a schema with customers, orders and order dates.
A customer can have orders in multiple dates. I need a calculated member to bring the first order date and the second order date with other associated metrics.
I was able to get the first order date and associated data using min(order date) as a first order but having issues querying for the second order date. Any suggestion would help! Thanks
my query
---I have all the information in one table so my query looks like
Select customerid, order id, min(orderdate) as firstorderdate,...
From customer Where first ordedate between 01/01/2015’ and GETDATE()
(since I only want those customers who made their first purchase this year)
Query their second purchase
Select customerid, orderid, orderdate from ( select customerid,
orderid, orderdate, rwo_number() over (partition by customerid,
orderid order by orderdate) rn from customer
Where rn<=2
Without seeing your current query, it's difficult to understand. I assume your current query is like this:
select c.customerid, o.orderid, min(od.orderdate)
from customers c
join orders o on c.customerid = o.customerid
join orderdates od on o.orderid = od.orderid
group by c.customerid, o.orderid
Another way of doing the same query is to use row_number. Doing it this way, you're not restricted to just the first in the group:
select customerid, orderid, orderdate
from (
select c.customerid, o.orderid, od.orderdate,
row_number() over (partition by c.customerid, o.orderid
order by od.orderdate) rn
from customers c
join orders o on c.customerid = o.customerid
join orderdates od on o.orderid = od.orderid
) t
where rn <= 2

Getting max value before given date

I am pretty new to using MS SQL 2012 and I am trying to create a query that will:
Report the order id, the order date and the employee id that processed the order
report the maximum shipping cost among the orders processed by the same employee prior to that order
This is the code that I've come up with, but it returns the freight of the particular order date. Whereas I am trying to get the maximum freight from all the orders before the particular order.
select o.employeeid, o.orderid, o.orderdate, t2.maxfreight
from orders o
inner join
(
select employeeid, orderdate, max(freight) as maxfreight
from orders
group by EmployeeID, OrderDate
) t2
on o.EmployeeID = t2.EmployeeID
inner join
(
select employeeid, max(orderdate) as mostRecentOrderDate
from Orders
group by EmployeeID
) t3
on t2.EmployeeID = t3.EmployeeID
where o.freight = t2.maxfreight and t2.orderdate < t3.mostRecentOrderDate
Step one is to read the order:
select o.employeeid, o.orderid, o.orderdate
from orders o
where o.orderid = #ParticularOrder;
That gives you everything you need to go out and get the previous orders from the same employee and join each one to the row you get from above.
select o.employeeid, o.orderid, o.orderdate, o2.freight
from orders o
join orders o2
on o2.employeeid = o.employeeid
and o2.orderdate < o.orderdate
where o.orderid = #ParticularOrder;
Now you have a whole bunch of rows with the first three values the same and the fourth is the freight cost of each previous order. So just group by the first three fields and select the maximum of the previous orders.
select o.employeeid, o.orderid, o.orderdate, max( o2.freight ) as maxfreight
from orders o
join orders o2
on o2.employeeid = o.employeeid
and o2.orderdate < o.orderdate
where o.orderid = #ParticularOrder
group by o.employeeid, o.orderid, o.orderdate;
Done. Build your query in stages and many times it will turn out to be much simpler than you at first thought.
It is unclear why you are using t3. From the question it doesn't sound like the employee's most recent order date is relevant at all, unless I am misunderstanding (which is absolutely possible).
I believe the issue lies in t2. You are grouping by orderdate, which will return the max freight for that date and employeeid, as you describe. You need to calculate a maximum total from all orders that occurred before the date that the order occurred on, for that employee, for every row you are returning.
It probably makes more sense to use a subquery for this.
SELECT o.employeeid, o.orderid, o.orderdate, m.maxfreight
FROM
orders o LEFT OUTER JOIN
(SELECT max(freight) as maxfreight
FROM orders AS f
WHERE f.orderdate <= o.orderdate AND f.employeeid = o.employeeid
) AS m
Hoping this is syntactically correct as I'm not in front of SSMS right now. I also included a left outer join as your previous query with an inner join would have excluded any rows where an employee had no previous orders (i.e. first order ever).
You can do what you want with a correlated subquery or apply. Here is one way:
select o.employeeid, o.orderid, o.orderdate, t2.maxfreight
from orders o outer apply
(select max(freight) as maxfreight
from orders o2
where o2.employeeid = o.employeid and
o2.orderdate < o.orderdate
) t2;
In SQL Server 2012+, you can also do this with a cumulative maximum:
select o.employeeid, o.orderid, o.orderdate,
max(freight) over (partition by employeeid
order by o.orderdate rows between unbounded preceding and 1 preceding
) as maxfreight
from orders o;

SQL Query to find the maximum of a set of averages

This is a query based on the Northwind Database in MS SQL Server 2005.
First I have to get the average of the UnitPrice from OrderDetails table, and group it by ProductID for that particular column alone and alias it as AveragePrice.
Then I need to find the maximum(AveragePrice) which is nothing but the max of previous column, how can I do it??? This is a kind of very tricky for me its taking me ages to think on it.
select
O.CustomerID,
E.EmployeeID,
E.FirstName+space(1)+E.LastName FullName,
OD.OrderID,
OD.ProductID,
(select avg(DO.UnitPrice) from OrderDetails
DO where OD.ProductID = DO.ProductID
group by DO.ProductID) AveragePrice ,
from OrderDetails OD
join Orders O
on OD.OrderID = O.OrderID
join Customers C
on C.CustomerID = O.CustomerID
join Employees E
on E.EmployeeID = O.EmployeeID
This is not a Homework question, am learning SQL, but am really stuck at this point, please help me.
It's 2 steps: "the ungrouped maximum of the grouped averages"
You can expand this as needed which shows how to apply an aggregate on top of an aggregate
SELECT
MAX(AveragePrice) AS MaxAveragePrice
FROM
(
select
avg(UnitPrice) AS AveragePrice, ProductID
from
OrderDetails
group by
ProductID
) foo
Or with CTE
;WITH AvgStuff AS
(
select
avg(UnitPrice) AS AveragePrice
from
OrderDetails
group by
ProductID
)
SELECT
MAX(AveragePrice) AS MaxAveragePrice
FROM
AvgStuff