Trouble getting SQL Server subquery to pick desired results - sql

I am given a database to use in SQL server.
The tables are:
Price (prodID, from, price)
Product (prodID, name, quantity)
PO (prodID, orderID, amount)
Order (orderID, date, address, status, trackingNumber, custID,
shipID)
Shipping (shipID, company, time, price)
Customer (custID, name)
Address (addrID, custID, address)
I need to Determine the ID and current price of each product.
The from attribute in the Price table are the dates that the prices were updated i.e. each ID in the table has multiple prices and dates associated with them but there is no common date between all of the IDs and the dates are in the 'YYYY-MM-DD' format and range is from 2018 to 2019-12-31.
My current query looks like:
select distinct p.prodID, p.price
from Price as p
where p.[from] >= '2019-12-23' and p.[from] in (select [from]
from Price
group by [from]
having max([from]) <= '2019-12-31')
order by p.prodID;
which returns a table with multiple prices for some of the IDs and also excludes other IDs altogether.
I was told that I needed a subquery to perform this.
I believe that I may be being too specific in my query to produce the desired results.
My main goal is to fix my current query to select one of each prodID and price from the most recent from date.

One option uses window functions:
select *
from (
select p.*, row_number() over(partition by p.prodid order by p.from desc) rn
from price p
where p.from <= convert(date, getdate())
) t
where rn = 1
This returns the latest row for each prodid where from is not greater that the current date.
As an alternative, you could also use with ties:
select top (1) with ties p.*
from price p
where p.from <= convert(date, getdate())
order by row_number() over(partition by p.prodid order by p.from desc)

Related

Calculating Top N items per dimension

I have the following query that shows total sales for each product on an hourly basis. However, it is very big data and I don't want to see all products, so would like to see the top 1000 product_id based on sales for each date, hour, and category_id dimensions.
SELECT date,
hour,
category_id,
product_id,
sum(sales) AS sales
FROM a
LEFT JOIN
ON a.product_id = b.product_id
WHERE date(date) >= date('2021-01-01')
GROUP BY 1, 2, 3, 4
How to do it in the Athena?
Thanks in advance.
You can use rank function on your result and then filter out corresponding ranks:
SELECT date,
hour,
category_id,
product_id,
sales
FROM
(
SELECT *,
rank() OVER (PARTITION BY date, hour, category_id
ORDER BY sales DESC) AS rnk
FROM (your query)
)
WHERE rnk <= 1000

Difference between multiple dates

I am working in a database with multiple orders of multiple suppliers. Now I would like to know the difference in days between order 1 and order 2, order 2 and order 3, order 3 and order 4 and so on.. For each supplier on its own. I need this to generate the Standard Deviation for each supplier based on their days between orders.
Hopefully someone can help..
What you describe is lag() with aggregation:
select supplier,
stddev(orderdate - prev_orderdate) as std_orderdate
from (select t.*,
lag(orderdate) over (partition by supplier order by orderdate) as prev_orderdate
from t
) t
group by supplier;
You would typically use window function lag() and date arithmetics.
Assuming the following data structure for table orders:
order_id int primary key
supplier_id int
order_date date
You would go:
select
i.*,
order_date
- lag(order_date) over(partition by supplier_id order by order_date) date_diff
from orders o
Which gives you, for each order, the difference in days from the previous order of the same supplier (or null if this is the first order of the supplier).
You can then compute the standard deviation with aggregation:
select supplier_id, stddev(date_diff)
from (
select
o.*,
order_date
- lag(order_date) over(partition by supplier_id order by order_date) date_diff
from orders o
) x
group by supplier_id

Query on MAX on date column, and COUNT of another column

I performed the following query with cte's, but I was wondering if there was a simpler way of writing the code, maybe with subqueries? I'm retrieving everything from one table SALES, but I'm using 3 columns: AgentID, SaleDate, and OrderID.
WITH RECENT_SALE AS(
SELECT AGENTID,(
SALEDATE,
ROW_NUMBER() OVER (PARTITION BY AGENTID ORDER BY SALEDATE DESC) AS RN
FROM SALES
)
,
COUNT_SALE AS (
SELECT AGENTID,
COUNT(ORDERID) AS COUNTORDERS
FROM SALES
)
SELECT RECENT_SALE.MRN,
SALEDATE,
COUNTORDERS
FROM RECENT_SALE
INNER JOIN COUNT_SALE ON RECENT_SALE.AGENTID = COUNT_SALE.AGENTID;
It looks to me like you're just trying to get the total number of sales per agent as well as the date of his or her most recent sale? If I understand your structure correctly (and I may not), then it seems pretty straightforward. I'm guessing orderid is the primary key of SALES?
SELECT agentid, MAX(saledate) AS saledate -- Most recent sale date
, COUNT(orderid) AS countsales -- total sales
FROM sales
GROUP BY agentid;
There does not seem to be any need for CTEs or subqueries here.
Try this:
SELECT
saledate,
AGENTID,
count(orderid) over(partition by AGENTID order by saledate)
FROM SALES
group by
saledate,
AGENTID

Get Last Record From Each Month

Unfortunately SQL doesn't come to me very easily. I have two tables, a Loan table and a LoanPayments table.
LoanPayments Table:
ID (Primary Key), LoanID (matches an ID on loan table), PaymentDate, Amount, etc.
I need a sql statement that can give me the last payment entered on each month (if there is one). My current statement isn't giving me the results. There is also the problem that sometimes there will be a tie for the greatest date in that month, so I need to be able to deal with that too (my idea was to select the largest ID in the case of a tie).
This is what I have so far (I know it's wrong but I don't know why.):
SELECT lp.ID, lp.LoanID, lp.PaymentDate
FROM LoanPayments lp
WHERE lp.PaymentDate in (
SELECT DISTINCT MAX(PaymentDate) as PaymentDate
FROM LoanPayments
WHERE IsDeleted = 0
AND ReturnDate is null
GROUP BY YEAR(PaymentDate), Month(PaymentDate)
)
AND CAST(PaymentDate as date) >= CAST(DATEADD(mm, -24, GETDATE()) as date)
The last part is just filtering it so I only get the last 24 months of payments. Thanks for your help and for taking the time to help me with this issue.
You could use the ROW_NUMBER() function here:
SELECT *
FROM (SELECT lp.ID, lp.LoanID, lp.PaymentDate
, ROW_NUMBER() OVER (PARTITION BY YEAR(PaymentDate), Month(PaymentDate) ORDER BY PaymentDate DESC) 'RowRank'
FROM LoanPayments lp
)sub
WHERE RowRank = 1
That's just the most recent PaymentDate for each month, if you wanted it by LoanID you'd add LoanID to the PARTITION BY list. If you were interested in preserving ties you could use RANK() instead of ROW_NUMBER()
STEP 1: Use a windowing function to add a column that holds that max PaymentDate by month
SELECT
ID,
LoanID,
PaymentDate,
MAX(PaymentDate) OVER(PARTITION BY YEAR(PaymentDate), MONTH(PaymentDate)) AS MaxPaymentDate,
ROW_NUMBER() OVER(PARTITION BY PaymentDate ORDER BY ID) AS TieBreaker
FROM LoanPayments
WHERE IsDeleted = 0
AND ReturnDate is null
STEP 2: Filter those results to just the rows you want
SELECT ID,LoanID,PaymentDate
FROM (
SELECT
ID,
LoanID,
PaymentDate,
MAX(PaymentDate) OVER(PARTITION BY YEAR(PaymentDate), MONTH(PaymentDate)) AS MaxPaymentDate,
ROW_NUMBER() OVER(PARTITION BY PaymentDate ORDER BY ID) AS TieBreaker
FROM LoanPayments
WHERE IsDeleted = 0
AND ReturnDate is null
) t1
WHERE PaymentDate = MaxPaymentDate AND TieBreaker = 1
This method is more efficient than doing a self-join.

Can I limit the amount of rows to be used for a group in a GROUP BY statement

I'm having an odd problem
I have a table with the columns product_id, sales and day
Not all products have sales every day. I'd like to get the average number of sales that each product had in the last 10 days where it had sales
Usually I'd get the average like this
SELECT product_id, AVG(sales)
FROM table
GROUP BY product_id
Is there a way to limit the amount of rows to be taken into consideration for each product?
I'm afraid it's not possible but I wanted to check if someone has an idea
Update to clarify:
Product may be sold on days 1,3,5,10,15,17,20.
Since I don't want to get an the average of all days but only the average of the days where the product did actually get sold doing something like
SELECT product_id, AVG(sales)
FROM table
WHERE day > '01/01/2009'
GROUP BY product_id
won't work
If you want the last 10 calendar day since products had a sale:
SELECT product_id, AVG(sales)
FROM table t
JOIN (
SELECT product_id, MAX(sales_date) as max_sales_date
FROM table
GROUP BY product_id
) t_max ON t.product_id = t_max.product_id
AND DATEDIFF(day, t.sales_date, t_max.max_sales_date) < 10
GROUP BY product_id;
The date difference is SQL server specific, you'd have to replace it with your server syntax for date difference functions.
To get the last 10 days when the product had any sale:
SELECT product_id, AVG(sales)
FROM (
SELECT product_id, sales, DENSE_RANK() OVER
(PARTITION BY product_id ORDER BY sales_date DESC) AS rn
FROM Table
) As t_rn
WHERE rn <= 10
GROUP BY product_id;
This asumes sales_date is a date, not a datetime. You'd have to extract the date part if the field is datetime.
And finaly a windowing function free version:
SELECT product_id, AVG(sales)
FROM Table t
WHERE sales_date IN (
SELECT TOP(10) sales_date
FROM Table s
WHERE t.product_id = s.product_id
ORDER BY sales_date DESC)
GROUP BY product_id;
Again, sales_date is asumed to be date, not datetime. Use other limiting syntax if TOP is not suported by your server.
Give this a whirl. The sub-query selects the last ten days of a product where there was a sale, the outer query does the aggregation.
SELECT t1.product_id, SUM(t1.sales) / COUNT(t1.*)
FROM table t1
INNER JOIN (
SELECT TOP 10 day, Product_ID
FROM table t2
WHERE (t2.product_ID=t1.Product_ID)
ORDER BY DAY DESC
)
ON (t2.day=t1.day)
GROUP BY t1.product_id
BTW: This approach uses a correlated subquery, which may not be very performant, but it should work in theory.
I'm not sure if I get it right but If you'd like to get the average of sales for last 10 days for you products you can do as follows :
SELECT Product_Id,Sum(Sales)/Count(*) FROM (SELECT ProductId,Sales FROM Table WHERE SaleDAte>=#Date) table GROUP BY Product_id HAVING Count(*)>0
OR You can use AVG Aggregate function which is easier :
SELECT Product_Id,AVG(Sales) FROM (SELECT ProductId,Sales FROM Table WHERE SaleDAte>=#Date) table GROUP BY Product_id
Updated
Now I got what you meant ,As far as I know it is not possible to do this in one query.It could be possible if we could do something like this(Northwind database):
select a.CustomerId,count(a.OrderId)
from Orders a INNER JOIN(SELECT CustomerId,OrderDate FROM Orders Order By OrderDate) AS b ON a.CustomerId=b.CustomerId GROUP BY a.CustomerId Having count(a.OrderId)<10
but you can't use order by in subqueries unless you use TOP which is not suitable for this case.But maybe you can do it as follows:
SELECT PorductId,Sales INTO #temp FROM table Order By Day
select a.ProductId,Sum(a.Sales) /Count(a.Sales)
from table a INNER JOIN #temp AS b ON a.ProductId=b.ProductId GROUP BY a.ProductId Having count(a.Sales)<=10
If this is a table of sales transactions, then there should not be any rows in there for days on which there were no Sales. I.e., If ProductId 21 had no sales on 1 June, then this table should not have any rows with productId = 21 and day = '1 June'... Therefore you should not have to filter anything out - there should not be anything to filter out
Select ProductId, Avg(Sales) AvgSales
From Table
Group By ProductId
should work fine. So if it's not, then you have not explained the problem completely or accurately.
Also, in yr question, you show Avg(Sales) in the example SQL query but then in the text you mention "average number of sales that each product ... " Do you want the average sales amount, or the average count of sales transactions? And do you want this average by Product alone (i.e., one output value reported for each product) or do you want the average per product per day ?
If you want the average per product alone, for just thpse sales in the ten days prior to now? or the ten days prior to the date of the last sale for each product?
If the latter then
Select ProductId, Avg(Sales) AvgSales
From Table T
Where day > (Select Max(Day) - 10
From Table
Where ProductId = T.ProductID)
Group By ProductId
If you want the average per product alone, for just those sales in the ten days with sales prior to the date of the last sale for each product, then
Select ProductId, Avg(Sales) AvgSales
From Table T
Where (Select Count(Distinct day) From Table
Where ProductId = T.ProductID
And Day > T.Day) <= 10
Group By ProductId