Query That Returns First Results from Subquery - sql

I'm trying to create a query that returns the results from a subquery in the result set.
Here are the tables I'm using:
Orders OrderDetails
------- -----------
orderId orderDetailId
(other data) orderId
productName
I'd like to get the first two order details for each order (Most orders have only one or two details). Here's an example of the desired result set:
orderId (other order data) productName1 productName2
------- ------------------ ------------ ------------
1 (other order data) apple grape
2 (other order data) orange banana
3 (other order data) apple orange
This is what I tried so far:
SELECT o.orderid,
Max(CASE WHEN detail = 1 THEN oi.productname END) AS ProductName1,
Max(CASE WHEN detail = 2 THEN oi.productname END) AS ProductName2
FROM orders AS o
OUTER apply (SELECT TOP 2 oi.*,
Row_number() OVER (ORDER BY orderdetailid) AS detail
FROM orderdetails AS oi
WHERE oi.orderid = o.orderid) AS oi
GROUP BY o.orderid
I'm doing this in the custom reporting module of a hosted ecommerce solution and getting the following unhelpful syntax error: SQL Error: Incorrect syntax near '('.
Unfortunately I don't know what version of SQL Server I'm using. Customer support knows nothing and select ##Version doesn't work.
Note, it appears the row_number() function is not properly supported even though error messages reference the function by name.
Thanks for the help!

Here is an alternative that does not use cross apply. Your ranking was correct but I added a partition by the order.
SELECT
*
FROM
(
SELECT
o.orderid,
ProductName=oi.productcode,
RowNumber=ROW_NUMBER() OVER (PARTITION BY o.orderid ORDER BY oi.orderdetailid)
FROM
orders as o
INNER JOIN orderdetailid oi ON oi.orderid=o.orderid
)AS X
WHERE
RowNumber=1
without using row_number
SELECT
orderdetails.*
Q1.*
FROM
(
SELECT
o.*,
FirstOrderDetailID=(SELECT MIN(orderdetails.orderdetailsid) FROM orderdetails WHERE orderid=o.orderid)
FROM
orders o
)AS Q1
LEFT OUTER JOIN orderdetails oi ON oi.orderdetailsid=Q1.FirstOrderDetailID

If you are just selecting orderid and the product, you don't need the join at all:
select orderid, productcode
from (SELECT oi.orderid, oi.productcode,
row_number() over (partition by oi.orderid order by oi.orderdetailid) as seqnum
FROM orderdetails oi
) oi
where seqnum = 1;
This may not fix the problem if row_number() is not working, but it simplifies the query. You can do this with the min() method as well:
select orderid, productcode
from orderdetails oi
where oi.orderdetailid in (select min(orderdetailid) from orderdetails group by orderid);

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

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.

select orders from first time customers

I need help building a SQL query that returns orders from customers who have only ordered once.
The tables and relevant fields are as follows:
Order Customer
------- -----------
orderId customerId
orderDate
customerId
etc.
I'm looking for a result set of Order records where there is only one occurence of the customer id. For the following data set...
[orderId] [customerId] [orderDate] [etc.]
---------- ------------ ------------ ------------
o1 c1 1/1/14 foo
o2 c2 1/1/14 baz
o3 c3 1/3/14 bar
o4 c2 1/3/14 wibble
I would like the results to be
[orderId] [orderDate] [etc.]
--------- ----------- ------
o1 1/1/14 foo
o3 1/3/14 bar
Orders o2 and o4 are ommitted because c2 has ordered twice.
Any help would be greatly appreciated.
Sorry, didn't put my failed attempt. This is what I tried...
SELECT customerId,
orderId,
orderDate,
Count(*)
FROM Orders
GROUP BY orderId,
orderDate,
customerID
HAVING Count(*) = 1
ORDER BY orderId
It appears to return all the orders.
Try the following (assuming SQL Server 2005+):
;WITH CTE AS
(
SELECT *,
N = COUNT(*) OVER(PARTITION BY customerId)
FROM Orders
)
SELECT *
FROM CTE
WHERE N = 1
Since sometimes a pedestrian approach is preferred over complex CTEs, you can use a derived table if you want (but since it's using the OVER clause, you'll still need SQL Server 2005+):
SELECT *
FROM ( SELECT *,
N = COUNT(*) OVER(PARTITION BY customerId)
FROM Orders) T
WHERE N = 1
Alternatively (if for example you are in an older than 2005 version of SQL-Server), you can use the GROUP BY / HAVING COUNT(*)=1 method to find customers with only 1 order and then join back to the Orders table (no need for aggregate functions in all the columns):
SELECT o.*
FROM Orders o
JOIN
( SELECT customerId
FROM Orders
GROUP BY customerId
HAVING COUNT(*) = 1
) c
ON c.customerId = o.customerId ;
or use NOT EXISTS (no COUNT() needed and it works even in MySQL):
SELECT o.*
FROM Orders o
WHERE NOT EXISTS
( SELECT 1
FROM Orders c
WHERE c.customerId = o.customerId
AND c.orderId <> o.orderId
) ;
This will list all first-time customers in your ORDERS table.
SELECT [customerID],
MIN([orderId]) AS [orderId],
MIN([orderDate]) AS [orderDate],
MIN([etc.]) AS [etc.]
FROM [Orders]
GROUP BY [customerID]
HAVING Count(*) = 1
ORDER BY [customerID]
In order to bring back all the additional columns you would need to wrap them in an aggregate such as MIN/MAX.
It is arbitrary which to use as there will only be one row per group anyway. This does assume that all columns in the table are of datatypes valid for such aggregation however (examples of datatypes that aren't are BIT, or XML)

Distinct across similar records in SQL Server 2008 database

I have a SQL Server 2008 database. This database has a table called Product, Order, and OrderProduct. These three tables look like the following:
Product
-------
ID
Name
Description
Order
-----
ID
OrderDate
Status
OrderProduct
------------
OrderID
ProductID
Quantity
I am trying to identify the last three unique products a person ordered. However, I also need to include the last date on which the product was ordered. My problem is I keep getting a result set like this:
Can of Beans (10/10/2011)
Soda (10/09/2011)
Can of Beans (10/08/2011)
The second "Can of Beans" should not be there because I already showed "Can of Beans". My query looks like this:
SELECT TOP 3 DISTINCT
p.[Name],
o.[OrderDate]
FROM
[Product] p,
[Order] o
[OrderProduct] l
WHERE
l.[ProductID]=p.[ID] and
l.[OrderID]=o.[ID]
ORDER BY
o.[OrderDate] DESC
I understand that the reason DISTINCT won't work is because of the order dates are different. However, I'm not sure how to remedy this. Can somebody tell me how to fix this?
WITH cteProducts AS (
SELECT p.Name, o.OrderDate,
ROW_NUMBER() OVER(PARTITION BY p.Name ORDER BY o.OrderDate DESC) as RowNum
FROM Product p
INNER JOIN OrderProduct op
ON p.ID = op.ProductID
INNER JOIN Order o
ON op.OrderID = o.ID
)
SELECT TOP 3 Name, OrderDate
FROM cteProducts
WHERE RowNum = 1
ORDER BY OrderDate DESC;
Have you tried GROUP BY?
SELECT TOP 3
p.[Name],
max(o.[OrderDate])
FROM
[Product] p,
[Order] o
[OrderProduct] l
WHERE
l.[ProductID]=p.[ID] and
l.[OrderID]=o.[ID]
GROUP BY p.[Name]
ORDER BY
max(o.[OrderDate]) DESC
Try grouping like :
SELECT TOP 3
p.[Name],
MAX(o.[OrderDate])
FROM
[Product] p,
[Order] o
[OrderProduct] l
WHERE
l.[ProductID]=p.[ID] and
l.[OrderID]=o.[ID]
GROUP BY p.[Name]
ORDER BY
MAX(o.[OrderDate]) DESC
Use GROUP BY... it's been a while since I've used SQL Server, but the query will look something like this:
SELECT TOP 3
p.[Name],
max(o.[OrderDate]) AS MostRecentOrderDate
FROM
[Product] p,
[Order] o
[OrderProduct] l
WHERE
l.[ProductID]=p.[ID] and
l.[OrderID]=o.[ID]
GROUP BY p.[Name]
ORDER BY
MostRecentOrderDate DESC
Or to show the first time they ordered that product, choose min() instead of max()

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