SELECT AVG(invoiceamount) per debtor where date< date row - sql

I have a SQL table with the following columns: Invoice ID, debtor ID, invoice date and invoice amount. where invoice ID is unique.
I'm trying to create an extra column with the average invoice amount. So per row I want the average invoice amount of the debtor, but only of the invoices where the invoice date <= the invoicedate of the column.
I'm not sure where to start, all ideas are welcome

Try this-
SELECT
A.Invoice_ID,
A.debtor_ID,
A.invoice_date,
A.invoice_amount,
(
SELECT AVG(B.invoice_amount)
FROM your_table B
WHERE B.debtor_ID = A.debtor_ID
AND B.invoice_date <= A.invoice_date
) AS average_invoice_amount
FROM your_table A

You want to use window functions:
select t.*,
avg(invoice_amount) over (partition by debtor_id order by invoice_date) as running_average
from t;
I strongly recommend this over a correlated subquery because it should be much faster -- even if you attempt to optimize the correlated subquery with indexes.

Related

Best approach to display all the users who have more than 1 purchases in a month in SQL

I have two tables in an Oracle Database, one of which is all the purchases done by all the customers over many years (purchase_logs). It has a unique purchase_id that is paired with a customer_id.The other table contains the user info of all the customers. Both have a common key of customer_id.
I want to display the user info of customers who have more than 1 unique item (NOT the item quantity) purchased in any month (i.e if A customer bought 4 unique items in february 2020 they would be valid as well as someone who bought 2 items in june). I was wondering what should my correct approach be and also how to correct execute that approach.
The two approaches that I can see are
Approach 1
Count the overall number of purchases done by all customers, filter the ones that are greater than 1 and then check if they any of them were done within a month.
Use this as a subquery in the where clause of the main query for retrieving the customer info for all the customer_id which match this condition.
This is what i've done so far,this retrieves the customer ids of all the customers who have more than 1 purchases in total. But I do not understand how to filter out all the purchases that did not occur in a single arbitrary month.
SELECT * FROM customer_details
WHERE customer_id IN (
SELECT cust_id from purchase_logs
group by cust_id
having count(*) >= 2);
Approach 2
Create a temporary table to Count the number of monthly purchases of a specific user_id then find the MAX() of the whole table and check if that MAX value is bigger than 1 or not. Then if it is provide it as true for the main query's where clause for the customer_info.
Approach 2 feels like the more logical option but I cannot seem to understand how to write the proper subquery for it as the command MAX(COUNT(customer_id)) from purchase_logs does not seem to be a valid query.
This is the DDL diagram.
This is the Sample Data of Purchase_logs
Customer_info
and Item_info
and the expected output for this sample data would be
It is certainly possible that there is a simpler approach that I am not seeing right now.
Would appreciate any suggestions and tips on this.
You need this query:
SELECT DISTINCT cust_id
FROM purchase_logs
GROUP BY cust_id, TO_CHAR(purchase_date, 'YYYY-MON')
HAVING COUNT(DISTINCT item_id) > 1;
to get all the cust_ids of the customers who have more than 1 unique item purchased in any month and you can use with the operator IN:
SELECT *
FROM customer_details
WHERE customer_id IN (
SELECT DISTINCT cust_id -- here DISTINCT may be removed as it does not make any difference when the result is used with IN
FROM purchase_logs
GROUP BY cust_id, TO_CHAR(purchase_date, 'YYYY-MON')
HAVING COUNT(DISTINCT item_id) > 1
);
One approach might be to try
with multiplepurchase as (
select customer_id,month(purchasedate),count(*) as order_count
from purchase_logs
group by customer_id,month(purchasedate)
having count(*)>=2)
select customer_id,username,usercategory
from mutiplepurchase a
left join userinfo b
on a.customer_id=b.customer_id
Expanding on #MT0 answer:
SELECT *
FROM customer_details CD
WHERE exists (
SELECT cust_id
FROM purchase_logs PL
where CD.customer_id = PL.customer_id
GROUP BY cust_id, item_id, to_char(purchase_date,'YYYYMM')
HAVING count(*) >= 2
);
I want to display the user info of customers who have more than 1 purchases in a single arbitrary month.
Just add a WHERE filter to your sub-query.
So assuming that you wanted the month of July 2021 and you had a purchase_date column (with a DATE or TIMESTAMP data type) in your purchase_logs table then you can use:
SELECT *
FROM customer_details
WHERE customer_id IN (
SELECT cust_id
FROM purchase_logs
WHERE DATE '2021-07-01' <= purchase_date
AND purchase_date < DATE '2021-08-01'
GROUP BY cust_id
HAVING count(*) >= 2
);
If you want the users where they have bought two-or-more items in any single calendar month then:
SELECT *
FROM customer_details c
WHERE EXISTS (
SELECT 1
FROM purchase_logs p
WHERE c.customer_id = p.cust_id
GROUP BY cust_id, TRUNC(purchase_date, 'MM')
HAVING count(*) >= 2
);

Multiple Aggregation in SQL

loan_no loan_amt contact date customer_id salesman_id
I have the following table. I need to somehow get the average of loan_no and the average of loan_amt for the people with more than one loan_no. I need to somehow plug in the avg and count functions.
I am seriously struggling with that. I was also thinking of a pivot function.
I would really appreciate it if someone can suggest a SQL code
My efforts so far:
select count (loan_no), tcustomer_id
from table
group by customer_id
having count (loan_no) > 1
Now I just do not know how to also include the avg function.
Not sure why do you need average Loan_no but you can still get it through -
select customer_id, avg(loan_no), avg(loan_amt)
from (select *, count(*) over(partition by customer_id) cnt
from table)
where cnt > 1
group by customer_id
You can use two levels of aggregation:
select avg(num_loans), sum(total) / sum(num_loans)
from (select customer_id, count(*) as num_loans, sum(loan_amt) as total
from table
group by customer_id
) t
where num_loans > 1;

SQL - Pull distinct row based on max value

I am trying to pull the most recent sale amount for each salesperson. The salespeople have made a sale on multiple days, I only want the most recent one.
My attempt below:
SELECT salesperson, amount
FROM table
WHERE date = (SELECT MAX(date) FROM table);
Use correlated subquery :
SELECT t.salesperson, t.amount
FROM table t
WHERE t.date = (SELECT MAX(t1.date)
FROM table t1
WHERE t1.salesperson = t.salesperson -- for each salesperson
);
If you are using PostgreSQL, you can take advantage of DISTINCT ON:
SELECT DISTINCT ON (salesperson) salesperson, amount
FROM table t
ORDER BY salesperson, date DESC
This will return only one row for each salesperson. The ORDER BY clause says to return the one with the largest date for that salesperson.
Unfortunately, DISTINCT ON is not supported by other databases.

Improve performance of select on select query using temp table

As for Table structure, the table has weekly product prices for per country.
My goal here is to select the lowest price of each product for the most recent week/year per country per product.
The query below fulfills this goal, but is pretty slow performance wise. I was wondering if there is a more efficient way of doing the same task.
In the first part Im selecting the latest Year and week of prices per country. I included the CASE When to account for new year.
Im saving this in a #temptable.
Then I am selecting the min price based on the previous selected Year, Week and Country combo.
DECLARE #date DATE SET #date=getdate()
SELECT YearNb, Max(WeekNb) AS WeekNb, ISOCountryCode INTO #TempTable FROM PriceBenchWeekly
WHERE PriceBenchWeekly.YearNb = CASE WHEN DATEPART(ww,#date) = 1 THEN
Year(#date)-1
ELSE
Year(#date)
END
GROUP BY YearNb, ISOCountryCode
SELECT ProdNb,Min(WeeklyPrice) AS MinPrice, MarketPlayerCode, 'MKT' AS PriceOriginTypeCode, NatCoCode
FROM CE.PriceBenchWeekly INNER JOIN #TempTable ON PriceBenchWeekly.YearNb = #TempTable.YearNb AND
PriceBenchWeekly.WeekNb = #TempTable.WeekNb AND PriceBenchWeekly.ISOCountryCode = #TempTable.ISOCountryCode
GROUP BY PriceBenchweekly.YearNb, PriceBenchWeekly.ISOCountryCode, BNCode, MarketPlayerCode
the table has weekly product prices for per country. My goal here is to select the lowest price of each product for the most recent week/year per country per product.
Use window functions. Without sample data and desired results, it is a little hard to figure out what you really want. But the following gets the minimum price for each product from the most recent week in the data:
select pbw.*
from (select pbw.*,
min(weeklyprice) over (partition by prodnb) as min_weeklyprice
from (select pbw.*,
dense_rank() over (order by year desc, weeknb desc) as seqnum
from CE.PriceBenchWeekly pbw
) pbw
where seqnum = 1
) pbw
where weeklyprice = min_weeklyprice;
If you want to go with temp tables, do not create it using select into, use CREATE TABLE #TempTable instead, then you can create a non clustered index for Year, Week and Country code...
Anyway, I would prefer outer apply
SELECT DISTINCT A.ProductCode, A.CountryCode, B.YearNo, B.WeekNo, B.MinPrice
FROM YourTable A
OUTER APPLY (
SELECT TOP 1 YearNo, WeekNo, Min(Price) AS MinPrice
FROM YourTable
WHERE ProductCode = A.ProductCode AND CountryCode = B.CountryCode
GROUP BY YearNo, WeekNo
ORDER BY YearNo DESC, WeekNo DESC
) B

Select values with duplicate max values sql

I have a table made up of dates and sales totals for the particular date. I would like to be able to query the table and select the following: max sales, the date associated with the max sale figure, sum of all sales, and the minimum date in the table. One additional complication is that there are duplicate max values. I don't care which max value is chosen but I just want one at random. This is for Oracle.
Here is what I tried. It was using a sub query.
Select sales, date, min(date), sum(sales) from table
Where sales = (select distinct(max(sales)) from table)
select
max(sales),
max(date_) keep (dense_rank first order by sales desc),
sum(sales),
min(date_)
from
table_
See also This SQL Fiddle