Get Last Record From Each Month - sql

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.

Related

Find number of days between two date records in the same table

I have a DATE, ORDERID, CLIENTID and STOREID.
I'm trying to get the average number of days between the FIRST order date and THIRD order date for all clients who have placed at least 3 orders.
This is what I have so far but when I add OrderId, it doesn’t return anything anymore.
select Date, OrderId, ClientId
from ClientOrders
group by Date, OrderId, ClientId
having count(ClientId) > 3
We can use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY ClientId ORDER BY Date) rn
FROM ClientOrders
)
SELECT AVG(diff)
FROM
(
SELECT DATEDIFF(day,
MAX(CASE WHEN rn = 1 THEN Date END),
MAX(CASE WHEN rn = 3 THEN Date END)) AS diff
FROM cte
GROUP BY ClientId
HAVING COUNT(*) >= 3 -- at least 3 orders
) t;

How to get min value at max date in sql?

I have a table with snapshot data. It has productid and date and quantity columns. I need to find min value in the max date. Let's say, we have product X: X had the last snapshot at Y date but it has two snapshots at Y with 9 and 8 quantity values. I need to get
product_id | date | quantity
X Y 8
So far I came up with this.
select
productid
, max(snapshot_date) max_date
, min(quantity) min_quantity
from snapshot_table
group by 1
It works but I don't know why. Why this does not bring min value for each date?
I would use RANK here along with a scalar subquery:
WITH cte AS (
SELECT *, RANK() OVER (ORDER BY quantity) rnk
FROM snapshot_table
WHERE snapshot_date = (SELECT MAX(snapshot_date) FROM snapshot_table)
)
SELECT productid, snapshot_date, quantity
FROM cte
WHERE rnk = 1;
Note that this solution caters to the possibility that two or more records happened to be tied for having the lower quantity among those most recent records.
Edit: We could simplify by doing away with the CTE and instead using the QUALIFY clause for the restriction on the RANK:
SELECT productid, snapshot_date, quantity
FROM snapshot_table
WHERE snapshot_date = (SELECT MAX(snapshot_date) FROM snapshot_table)
QUALIFY RANK() OVER (ORDER BY quantity) = 1;
Consider also below approach
select distinct product_id,
max(snapshot_date) over product as max_date,
first_value(quantity) over(product order by snapshot_date desc, quantity) as min_quantity
from your_table
window product as (partition by product_id)
use row_number()
with cte as (select *,
row_number() over(partition by product_id order by date desc) rn
from table_name) select * from cte where rn=1

Trouble getting SQL Server subquery to pick desired results

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)

sql - find users who made 3 consecutive actions

i use sql server and i have this table :
ID Date Amount
I need to write a query that returns only users that have made at least 3 consecutive purchases, each one larger than the other.
I know that i need to use partition_id and row_number but i dont know how to do it
Thank you in advance
If you want three purchases in a row with increases in amount, then use lead() to get the next amounts:
select t.*
from (select t.*,
lead(amount, 1) over (partition by id order by date) as next_date,
lead(amount, 2) over (partition by id order by date) as next2_amount
from t
) t
where next_amount > amount and next2_amount > next_amount;
I originally missed the "greater than" part of the question. If you wanted purchases on three days in a row, then:
If you want three days in a row and there is at most one purchase per day, then you can use:
select t.*
from (select t.*,
lead(date, 2) over (partition by id order by date) as next2_date
from t
) t
where next2_date = dateadd(day, 2, date);
If you can have duplicates on a date, I would suggest this variant:
select t.*
from (select t.*,
lead(date, 2) over (partition by id order by date) as next2_date
from (select distinct id, date from t) t
) t
where next2_date = dateadd(day, 2, date);

Finding a date with the largest sum

I have a database of transactions, accounts, profit/loss, and date. I need to find the dates which the largest profit occurs by account. I have already found a way to find these actually max/min values but I can't seem to be able to pull the actual date from it. My code so far is like this:
Select accountnum, min(ammount)
from table
where date > '02-Jan-13'
group by accountnum
order by accountnum
Ideally I would like to see account num, the min or max, and then the date which this occurred on.
Try something like this to get the min and max amount for each customer and the date it happened.
WITH max_amount as (
SELECT accountnum, max(amount) amount, date
FROM TABLE
GROUP BY accountnum, date
),
min_amount as (
SELECT accountnum, min(amount) amount, date
FROM TABLE
GROUP BY accountnum, date
)
SELECT t.accountnum, ma.amount, ma.date, mi.amount, ma.date
FROM table t
JOIN max_amount ma
ON ma.accountnum = t.accountnum
JOIN min_amount mi
ON mi.accountnum = t.accountnum
If you want the data for just this year you could add a where clause to the end of the statement
WHERE t.date > '02-Jan-13'
The easiest way to do this is using window/analytic functions. These are ANSI standard and most databases support them (MySQL and Access being two notable exceptions).
Here is one way:
select t.accountnum, min_amount, max_amount,
min(case when amount = min_amount then date end) as min_amount_date,
min(case when amount = min_amount then date end) as max_amount_date,
from (Select t.*,
min(amount) over (partition by accountnum) as min_amount,
max(amount) over (partition by accountnum) as max_amount
from table t
where date > '02-Jan-13'
) t
group by accountnum, min_amount, max_amount;
order by accountnum
The subquery calculates the minimum and maximum amount for each account, using min() as a window function. The outer query selects these values. It then uses conditional aggregation to get the first date when each of those values occurred.
;with cte as
(
select accountnum, ammount, date,
row_number() over (partition by accountnum order by ammount desc) rn,
max(ammount) over (partition by accountnum) maxamount,
min(ammount) over (partition by accountnum) minamount
from table
where date > '20130102'
)
select accountnum,
ammount as amount,
date as date_of_max_amount,
minamount,
maxamount
from cte where rn = 1