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

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

Related

How to find the date of the last item sold sql server

Hi Guys I am having a bit of trouble writing the most efficient and optimized query for this question:
Find the order ID and date of the last discontinued item sold.
I have my code below as well as the metadata for the tables. I am not sure if my code will produce the correct output because I have no way of testing it and I am not sure if my code will be the best way to complete this query. Any advice would help.
Select
orders.orderid,
Max(orders.orderdate)
from orders
inner join order_details on orders.orderid = order_details.orderid
inner join products on order_details.productid = products.productid
where discontinued = 1
group by orders.orderid ```
Using row_number is the easiest way:
select orderID,OrderDate from (
select o.orderID,o.OrderDate,rn = row_number() over (order by orderdate desc)
from products p
join orderDetails od on od.productID=p.productID
join orders o on o.orderID=od.orderID
where p.discontinued = 1) sub
where sub.rn = 1
your query is pretty much already what you want, the fastest way is to simply order by the required column and select top 1
select top (1) o.orderid, o.orderdate
from orders o
join order_details od on od.orderid = o.orderid
join products p on p.productid = od.productid
where p.discontinued = 1
order by o.orderdate desc
This will be more performant than using a window function to number all the rows before selecting row one.
I have a very similar arrangement of tables with the ubiquitous orders/orderitems/products arrangement including a similar deleted flag for products so it's easy to test both side by side, this query is a bit more performant than using the row_number equivalent, using a table of 13.5m orders and 6m products. Execution times for both were sub-second but this query was slightly faster.

How do you add OrderID to this? I cant get it to display without getting a error on all OrderID sections

I cant get it to display without getting an error on all OrderID sections. I tried adding it to the top and then the subquery and it errors saying ambouious column. How would be the correct way to do this?
TABLE SETUP:
Customers Table:
CustomerID, EmailAddress, Password, FirstName, LastName, ShippingAddressID, BillingAddressID
OrderItems Table:
ItemID, OrderID, ProductID, ItemPrice, DiscountAmount, DiscountTotal, PriceTotal, ItemTotal, Quantity
Order Table:
OrderID, CustomerID, OrderDate, ShipAmount, TaxAmount, ShipDate, ShipAddressID, CardType, CardNumber,CardExpires, BillingAddressID
CODE:
SELECT c.EmailAddress, MAX(OrderCost) AS
LargestOrder
FROM Customers c
JOIN ORDERS o ON c.CustomerID = o.CustomerID
JOIN (Select Orders.OrderID, ItemPrice * Quanity AS OrderCost FROM Orders, OrderItems
WHERE OrderItems.OrderID = Orders.OrderID)Largest
ON Largest.OrderID = o.OrderID
GROUP BY c.EmailAddresses
It really helps for you to put some effort into a script that demonstrates your problem. That way no one needs to guess about how you defined things and the actual values you use.
Here is one approach based on my guess. Note "one" - there are techniques to achieve the same result. The cte calculates the total cost for each order just like your code attempted. Since you suggested top, I used that as well to demonstrate its usage. Grabbing the top first row based on descending OrderCost will get you the order with the highest total cost. Just join the cte to Orders and Customers to include the columns you desire.
with cte as (select OrderID, sum(ItemPrice * Quantity) as OrderCost
from #OrderItems
group by OrderID)
select top 1 Ord.OrderID, Ord.OrderDate, cust.EmailAddress, cte.OrderCost
from cte inner join #Orders as Ord
on cte.OrderID = Ord.OrderID
inner join #Customers as Cust
on Ord.CustomerID = Cust.CustomerID
order by cte.OrderCost desc;
rextester here
And everyone hopes that you are not actually saving credit card information - because that would be bad in many ways.

Finding out when summed values reached a certain checkpoint in 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.

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