SQL Query to return the Top 2 Values - sql

I am trying to return the top 2 most ordered items in our customer database. Below is what I have for the most ordered item but I am having trouble figuring out how to create another column for the 2nd most ordered item.
What is the best way to create the 2nd column?
SELECT FirstName, EmailAddress, Id, PreferredLocationId,
(
SELECT TOP 1 [Description] FROM [Order] o
INNER JOIN [OrderItem] oi ON oi.OrderId = o.OrderId
WHERE o.CustomerId = Customer.Id
GROUP BY [Description]
ORDER BY COUNT(*) DESC
) AS MostOrderedItem
FROM Customer
GROUP BY FirstName, EmailAddress, Id, PreferredLocationId

Lot's of different ways to handle this if you're using SQL Server 2012. I'm going to use a CTE to get the first two rows and use ROW_NUMBER()
WITH cte AS (
SELECT CustomerId, [Description]
, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY COUNT(*) DESC) [RowID]
FROM [Order] o
INNER JOIN [OrderItem] oi ON oi.OrderId = o.OrderId
GROUP BY CustomerId, [Description]
)
SELECT FirstName, EmailAddress, Id, PreferredLocationId, cte1.Description, cte2.Description
FROM Customer
LEFT JOIN cte cte1 ON cte1.CustomerID = Customer.CustomerId AND cte1.RowID = 1
LEFT JOIN cte cte2 ON cte2.CustomerID = Customer.CustomerId AND cte2.RowID = 2
The Common Table Expression creates the list of all customers, descriptions and their row number. Note that if you have ties, you're not guarunteed which description will come first. You can add to to the windowing function description so that if there is a tie, whatever comes first in the alphabet will be the tie breaker.

Related

Select Top1 from multiple-column query for each product ID

I have a query in SQLServer that returns the last entry in our stock of a given product, as well as many other columns. Something like:
SELECT
TOP(1) EntryDate,
EntryPrice,
TaxID,
TransportCost,
...
FROM
StockEntries
WHERE
ProductID = #ID
ORDER BY
EntryDate DESC
I cannot use MAX to get the last entry because sometimes it returns duplicate rows (when there are two entries at the same day).
I would like to execute this query for every product we have. I could do this if the query returned only 1 row, such as:
SELECT
ProductID p,
(
SELECT
TOP(1) s.EntryDate
FROM
StockEntries s
WHERE
s.ProductID = p.ProductID
ORDER BY
s.EntryDate DESC
)
FROM
Products p
But as it returns multiple rows, I cannot see a straight way to do this.
Any ideas?
As you have phrased the question, cross apply seems very appropriate:
SELECT p.*, s.*
FROM products p CROSS APPLY
(SELECT TOP(1) s.*
FROM StockEntries s
WHERE s.ProductID = p.ProductID
ORDER BY s.EntryDate DESC
) s;
APPLY also allows you to select other columns from StockEntries.
you can use ROW_NUMBER() to rank each row and then just get the rows with the highest entry date per product.
SELECT *
FROM (SELECT p.productid,
s.EntryDate,
s.EntryPrice,
s.TaxID,
s.TransportCost,
ROW_NUMBER() OVER (PARTITION BY p.productid ORDER BY s.entrydate DESC) rownum
FROM products p
JOIN StockEntries s ON s.ProductID = p.ProductID
) t
WHERE rownum = 1

Distinct one column on two tables SQL Server 2008

I have two different tables and I want to use distinct on only one column. I want to get recent records only. what query should I write in SQL Server 2008? I want to use distinct one only CustomerID not any other column.
Table1: Customer
Columns: CustomerID, CustomerName
Table2: Order
Columns: OrderID, CustomerID, OrderName
I tried two SQL queries both not working
First query
select Distinct on (CustomerID) CustomerID, CustomerName, OrderID, OrderName
from Customer
left join Order on Customer.CustomerID = Order.CustomerID
Second query:
select Max(Distinct ID)
CustomerID, CustomerName, OrderID, OrderName
from
Customer
left join
Order on Customer.CustomerID = Order.CustomerID
Assuming the higher OrderID is latest, this should work if I understand your requirement righly:
select
c.CustomerID,
c.CustomerName,
o.OrderID,
o.OrderName
from Customer as c
inner join (select
CustomerID,
max(update_date) as max_update_date
from Customer
group by CustomerID) as mc
on mc.CustomerID = c.CustomerID
and mc.max_update_date = c.update_date
inner join Order as o
on o.CustomerID = c.CustomerID
inner join (select
CustomerID,
max(OrderID) as max_OrderID
from Order
group by CustomerID) as m
on m.CustomerID = c.CustomerID
and m.max_OrderID = o.OrderID

SQL Query for counting number of orders per customer and Total Dollar amount

I have two tables
Order with columns:
OrderID,OrderDate,CID,EmployeeID
And OrderItem with columns:
OrderID,ItemID,Quantity,SalePrice
I need to return the CustomerID(CID), number of orders per customer, and each customers total amount for all orders.
So far I have two separate queries. One gives me the count of customer orders....
SELECT CID, Count(Order.OrderID) AS TotalOrders
FROM [Order]
Where CID = CID
GROUP BY CID
Order BY Count(Order.OrderID) DESC;
And the other gives me the total sales. I'm having trouble combining them...
SELECT CID, Sum(OrderItem.Quantity*OrderItem.SalePrice) AS TotalDollarAmount
FROM OrderItem, [Order]
WHERE OrderItem.OrderID = [Order].OrderID
GROUP BY CID
I'm doing this in Access 2010.
You would use COUNT(DISTINCT ...) in other SQL engines:
SELECT CID,
Count(DISTINCT O.OrderID) AS TotalOrders,
Sum(OI.Quantity*OI.SalePrice) AS TotalDollarAmount
FROM [Order] O
INNER JOIN [OrderItem] OI
ON O.OrderID = OI.OrderID
GROUP BY CID
Order BY Count(DISTINCT O.OrderID) DESC
Which Access unfortunately does not support. Instead you can first get the Order dollar amounts and then join them before figuring the order counts:
SELECT CID,
COUNT(Orders.OrderID) AS TotalOrders,
SUM(OrderAmounts.DollarAmount) AS TotalDollarAmount
FROM [Orders]
INNER JOIN (SELECT OrderID, Sum(Quantity*SalePrice) AS DollarAmount
FROM OrderItems GROUP BY OrderID) AS OrderAmounts
ON Orders.OrderID = OrderAmounts.OrderID
GROUP BY CID
ORDER BY Count(Orders.OrderID) DESC
If you need to include Customers that have orders with no items (unusual but possible), change INNER JOIN to LEFT OUTER JOIN.
Create a query which uses your 2 existing queries as subqueriers, and join the 2 subqueries on CID. Define your ORDER BY in the parent query instead of in a subquery.
SELECT
sub1.CID,
sub1.TotalOrders,
sub2.TotalDollarAmount
FROM
(
SELECT
CID,
Count(Order.OrderID) AS TotalOrders
FROM [Order]
GROUP BY CID
) AS sub1
INNER JOIN
(
SELECT
CID,
Sum(OrderItem.Quantity*OrderItem.SalePrice)
AS TotalDollarAmount
FROM OrderItem INNER JOIN [Order]
ON OrderItem.OrderID = [Order].OrderID
GROUP BY CID
) AS sub2
ON sub1.CID = sub2.CID
ORDER BY sub1.TotalOrders DESC;

SQL: improving join efficiency

If I turn this sub-query which selects sales persons and their highest price paid for any item they sell:
select *,
(select top 1 highestProductPrice
from orders o
where o.salespersonid = s.id
order by highestProductPrice desc ) as highestProductPrice
from salespersons s
in to this join in order to improve efficiency:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders ) o on s.id = o.salespersonid
It still touches every order record (it enumerates the entire table before filtering by salespersonid it seems.) However you cannot do this:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders
where orders.salepersonid = s.id) o on s.id = o.salespersonid
The where clause in the join causes a `multi-part identifier "s.id" could not be bound.
Is there any way to join the top 1 out of each order group with a join but without touching each record in orders?
Try
SELECT
S.*,
T.HighestProductPrice
FROM
SalesPersons S
CROSS APPLY
(
SELECT TOP 1 O.HighestProductPrice
FROM Orders O
WHERE O.SalesPersonid = S.Id
ORDER BY O.SalesPersonid, O.HighestProductPrice DESC
) T
would
select s.*, max(highestProductPrice)
from salespersons s
join orders o on o.salespersonid = s.id
group by s.*
or
select s.*, highestProductPrice
from salespersons s join (select salepersonid,
max(highestProductPrice) as highestProductPrice
from orders o) as o on o.salespersonid = s.id
work?

How to get last children records with parent record from database

I have database with two tables:
Customers (Id PK, LastName)
and
Orders (Id PK, CustomerId FK, ProductName, Price, etc.)
I want to retrieve only customer' last orders details together with customer name.
I use .NET L2SQL but I think it's SQL question more than LINQ question so I post here SQL query I tried:
SELECT [t0].[LastName], (
SELECT [t2].[ProductName]
FROM (
SELECT TOP (1) [t1].[ProductName]
FROM [Orders] AS [t1]
WHERE [t1].[CustomerId] = [t0].[Id]
ORDER BY [t1].[Id] DESC
) AS [t2]
) AS [ProductName], (
SELECT [t4].[Price]
FROM (
SELECT TOP (1) [t3].[Price]
FROM [Orders] AS [t3]
WHERE [t3].[CustomerId] = [t0].[Id]
ORDER BY [t3].[Id] DESC
) AS [t4]
) AS [Price]
FROM [Customers] AS [t0]
Problem is that Orders has more columns (30) and with each column the query gets bigger and slower because I need to add next subqueries.
Is there any better way?
In SQL Server 2005 and above:
SELECT *
FROM (
SELECT o.*,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY o.id DESC) rn
FROM customers c
LEFT JOIN
orders o
ON o.customerId = c.id
) q
WHERE rn = 1
or this:
SELECT *
FROM customers c
OUTER APPLY
(
SELECT TOP 1 *
FROM orders o
WHERE o.customerId = c.id
ORDER BY
o.id DESC
) o
In SQL Server 2000:
SELECT *
FROM customers с
LEFT JOIN
orders o
ON o.id =
(
SELECT TOP 1 id
FROM orders oi
WHERE oi.customerId = c.id
ORDER BY
oi.id DESC
)