Select latest available SQL entry state - sql

Consider this DDL:
CREATE TABLE cash_depot_state
(
id INTEGER NOT NULL PRIMARY KEY,
date DATE,
amount REAL,
cash_depot_id INTEGER
);
INSERT INTO cash_depot_state (date, amount, cash_depot_id)
VALUES (DATE('2022-03-02'), 382489, 5);
INSERT INTO cash_depot_state (date, amount, cash_depot_id)
VALUES (DATE('2022-03-03'), 750, 2);
INSERT INTO cash_depot_state (date, amount, cash_depot_id)
VALUES (DATE('2022-03-04'), 750, 3);
INSERT INTO cash_depot_state (date, amount, cash_depot_id)
VALUES (DATE('2022-03-05'), 0, 5);
For an array of dates I need to select sum of all cash depots' actual amounts:
2022-03-01 - no data available - expect 0
2022-03-02 - cash depot #5 has changed it's value to 382489 - expect 382489
2022-03-03 - cash depot #2 has changed it's value to 750 - expect 382489 + 750
2022-03-03 - cash depot #3 has changed it's value to 750 - expect 382489 + 750 + 750
2022-03-04 - cash depot #5 has changed it's value to 0 - expect 0 + 750 + 750
My best attempt: http://sqlfiddle.com/#!5/94ad0d/1
But I can't figure out how to pick winner of a subgroup

You could define the latest amount per cash depot as the record that has row number 1, when you divvy up records by cash_depot_id, and order them descending by date:
SELECT
id,
cash_depot_id,
date,
amount,
ROW_NUMBER() OVER (PARTITION BY cash_depot_id ORDER BY date DESC) rn
FROM
cash_depot_state
This will highlight the latest data from your table - all the relevant rows will have rn = 1:
id
cash_depot_id
date
amount
rn
2
2
2022-03-03
750.0
1
3
3
2022-03-04
750.0
1
4
5
2022-03-05
0.0
1
1
5
2022-03-02
382489.0
2
Now you can use a WHERE clause to filter records to a certain date, e.g. WHERE data <= '2022-03-05':
SELECT
SUM(amount) sum_amount
FROM
(
SELECT amount, ROW_NUMBER() OVER (PARTITION BY cash_depot_id ORDER BY date DESC) rn
FROM cash_depot_state
WHERE date <= '2022-03-05'
) latest
WHERE
rn = 1;
will return 1500.
A more traditional way to solve this would be a correlated sub-query:
SELECT
SUM(amount) sum_amount
FROM
cash_depot_state s
WHERE
date = (
SELECT MAX(date)
FROM cash_depot_state
WHERE date <= '2022-03-05' AND cash_depot_id = s.cash_depot_id
)
or a join against a materialized sub-query:
SELECT
SUM(amount) sum_amount
FROM
cash_depot_state s
INNER JOIN (
SELECT MAX(date) date, cash_depot_id
FROM cash_depot_state
WHERE date <= '2022-03-05'
GROUP BY cash_depot_id
) latest ON latest.cash_depot_id = s.cash_depot_id AND latest.date = s.date
In large tables, these are potentially faster than the ROW_NUMBER() variant. YMMV, take measurements.
An index that covers date, cash_depot_id, and amount helps all shown approaches:
CREATE INDEX ix_latest_cash ON cash_depot_state (date DESC, cash_depot_id ASC, amount);
To run against a CTE that produces a calendar, any of the above can be correlated as a subquery
WITH RECURSIVE dates(date) AS (
SELECT '2022-03-01'
UNION ALL
SELECT date(date, '+1 day') FROM dates WHERE date < DATE('now')
)
SELECT
date,
IFNULL(
(
-- any of the above approaches with `WHERE date <= dates.date`
), 0
) balance
FROM
dates;
e.g. http://sqlfiddle.com/#!5/94ad0d/12

Related

How to differentiate iteration using date filed in bigquery

I have a process that occur every 30 days but can take few days.
How can I differentiate between each iteration in order to sum the output of the process?
for Example
the output I except is
Name
Date
amount
iteration (optional)
Sophia Liu
2016-01-01
4
1
Sophia Liu
2016-02-01
5
2
Nikki Leith
2016-01-02
5
1
Nikki Leith
2016-02-01
10
2
I tried using lag function on the date filed and using the difference between that column and the date column.
WITH base AS
(SELECT 'Sophia Liu' as name, DATE '2016-01-01' as date, 3 as amount
UNION ALL SELECT 'Sophia Liu', DATE '2016-01-02', 1
UNION ALL SELECT 'Sophia Liu', DATE '2016-02-01', 3
UNION ALL SELECT 'Sophia Liu', DATE '2016-02-02', 2
UNION ALL SELECT 'Nikki Leith', DATE '2016-01-02', 5
UNION ALL SELECT 'Nikki Leith', DATE '2016-02-01', 5
UNION ALL SELECT 'Nikki Leith', DATE '2016-02-02', 3
UNION ALL SELECT 'Nikki Leith', DATE '2016-02-03', 1
UNION ALL SELECT 'Nikki Leith', DATE '2016-02-04', 1)
select
name
,date
,lag(date) over (partition by name order by date) as lag_func
,date_diff(date,lag(date) over (partition by name order by date),day) date_differacne
,case when date_diff(date,lag(date) over (partition by name order by date),day) >= 10
or date_diff(date,lag(date) over (partition by name order by date),day) is null then true else false end as new_iteration
,amount
from base
Edited answer
After your clarification and looking at what's actually in your SQL code. I'm guessing you are looking for a solution to what's called a gaps and islands problem. That is, you want to identify the "islands" of activity and sum the amount for each iteration or island. Taking your example you can first identify the start of a new session (or "gap") and then use that to create a unique iteration ("island") identifier for each user. You can then use that identifier to perform a SUM().
gaps as (
select
name,
date,
amount,
if(date_diff(date, lag(date,1) over(partition by name order by date), DAY) >= 10, 1, 0) new_iteration
from base
),
islands as (
select
*,
1 + sum(new_iteration) over(partition by name order by date) iteration_id
from gaps
)
select
*,
sum(amount) over(partition by name, iteration_id) iteration_amount
from islands
Previous answer
Sounds like you just need a RANK() to count the iterations in your window functions. Depending on your need you can then sum cumulative or total amounts in a similar window function. Something like this:
select
name
,date
,rank() over (partition by name order by date) as iteration
,sum(amount) over (partition by name order by date) as cumulative_amount
,sum(amount) over (partition by name) as total_amount
,amount
from base

Oracle SQL - create select statement which will retrieve every end date of the month but compare the values the day or two after the end of month

Let's say I have a customer table.
customer_name || date || amount
-----------------------------------
A 31-OCT-20 100
A 01-NOV-20 100
A 02-NOV-20 200
B 31-OCT-20 300
B 01-NOV-20 325
B 02-NOV-20 350
I need to create a select statement which will retrieve every end date of the month and compare the values for the amounts respective to the day or two after. If the amount for the day or two is different from the end date of that month, display the recent changed amount.
Example 1 - Retrieve customer A for 31-OCT-20, compare to 01-NOV-20 and 02-NOV-20, output 200 for the amount.
Example 2 - Retrieve customer B for 31-OCT-20, compare to 01-NOV-20 and 02-NOV-20, output 350 for the amount.
Hmmm . . .
select t.*,
(case when next_amount <> amount or next2_amount <> amount
then greatest(next_amount, next2_amount)
else next_amount
end) as imputed_next_2_days
from (select t.*,
lead(amount) over (partition by customer_name order by date) as next_amount,
lead(amount, 2) over (partition by customer_name order by date) as next2_amount
from t
) t
where date = last_day(date);
You can use the following query:
Select * from
(Select t.*,
row_number() over (partition by customer_name order by dt desc) as rn
From your_table t
Where extract(day from t.dt + 2) between 2 and 4)
Where rn = 1
Tip of the day: don't use oracle reserved keywords as the column name. (Date)

Specific Order by Clause Oracle

Let say I have grocery shop with 2 type of customer Regular - R and 'Corporate - C` with them i have an agreement of prices based on dates. Sample data would look like.
Type(C/R) CustID From Date Cost
C 1/11/2017 10
C 1 1/11/2017 12
1/11/2017 14
R 1/11/2017 9
C 1 10/11/2017 11
C 11/11/2017 15
From the table you can see Type,Custid are not mandatory. My rate picker matches max matching columns from input based on from date to give me cost to apply.
Sample Input:(Input will always have type,custid and from date)
Case 1: Type - c,Cust ID - 1, dealdate(fromdate) - 2/11/2017
Output: Row number 2 with price 12
Case 2: Type - C, Cust ID - 2,dealdate(fromdate) - 2/11/2017
Output: Row number 1 with price 10
Case 3: Type - C, Cust ID - 2,dealdate(fromdate) - 12/11/2017
Output: Row number 6 with price 15
My output will match max matching column first and then will check for valid date it means matching columns have high priority than from date(but yes it has to be valid period).
My Approach:
select * from (
select row_number() over(partition by partition_column order by
from_date desc,type,custid) rn,a.* from (
select *,'1' as partition_column from rate
where from_date <= :d_date and (type = :type or type is null) and
(custid = :custid or custid is null)) a) where rn=1;
I am not getting the desired result. Can anyone help please.
Ok, I think I understand what you want.
create table rate(
type varchar2(5)
,cust_id number
,from_date date not null
,cost number not null
);
insert into rate(type, cust_id, from_date, cost) values('C', null, date '2017-11-01', 10);
insert into rate(type, cust_id, from_date, cost) values('C', 1, date '2017-11-01', 12);
insert into rate(type, cust_id, from_date, cost) values(null, null, date '2017-11-01', 14);
insert into rate(type, cust_id, from_date, cost) values('R', null, date '2017-11-01', 9);
insert into rate(type, cust_id, from_date, cost) values('C', 1, date '2017-11-10', 11);
insert into rate(type, cust_id, from_date, cost) values('C', null, date '2017-11-11', 15);
This statement works by finding records that match either type or customer. The Date input must be satisfied. In the end, higher priority is given to customer ID than customer type, and in case multiple records come, the one with most recent from date is picked.
select type, cust_id, cost, from_date
from (select r.*
,case when cust_id = 2 then 1 end as cust_id_matches
,case when type = 'C' then 1 end as type_matches
from rate r
where (type = 'C' or cust_id = 2) -- Either attribute my match
and from_date <= date '2017-11-12' -- Mandatory, must be valid
order
by cust_id_matches asc nulls last -- Order customer ID matches first
,type_matches asc nulls last -- Then Matches for type
,from_date desc -- Pick most recent if multiple records
)
where rownum = 1;
Here is a SQL Fiddle

Get last transactions from Transaction table by date

I need to get Transactions from Transaction Table from 2 lats dates, where this Transactions completed. And check, if amount of transaction on last day more than 10% than amount of transaction for previous day.
My table Have columns AccountId, SubAccountId, Amount, Date and UserId.
For example:
CREATE TABLE Transactions
(`id` int, `AccountId` int, `SubAccountId` int, `Amount` decimal
,`Date` datetime, `User` int);
INSERT INTO Transactions
(`id`, `AccountId`, `SubAccountId`, `Amount`, `Date`, `User`)
VALUES
(1, 1, 2, 100, '06/15/2018', 1),
(2, 1, 2, 40, '06/15/2018', 1),
(3, 1, 2, 20, '06/14/2018', 1),
(4, 1, 2, 0, '06/10/2018', 1),
;
In this example I need to select only transactions for date 06/15/2018 and 06/14/2018, and display sum of amount of transactions for this days.
So far, I can select the last transactions, like this:
select distinct AccountId,
SubAccountId,
UserId,
Amount,
Date AS lastDate,
min(Date)
over (partition by PayerAccount order by Date
rows between 1 preceding and 1 preceding) as PrevDate
from Transactions
order by UserId
This checks the sum amount of the current day against the sum amount of the previous day (to confirm it's greater than 10%) and then does a top 2 to extract only the last two days...
WITH CTE AS(
select
Date,
sum(Amount) as SumAmount,
rownum = ROW_NUMBER() OVER(ORDER BY Date)
from Transactions
group by Date
)
select top 2 CTE.date, CTE.SumAmount, CTE.rownum, CASE WHEN prev.sumamount > CTE.sumamount * 0.10 THEN 1 else 0 END isgreaterthan10per
from CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
order by CTE.date desc
with CTE1 as
(
select accountID, Date, sum(Amount) as Amount
from Transactions
where Date between '2018-06-14' and '2018-06-16' -- Apply date restriction here
group by accountID, Date
)
, CTE2 as
(
select accountID, Amount, Date,
row_number() over (partition by accountID order by date desc) as rn
from Transactions
)
select a1.accountID, a1.Amount, a1.Date, a2.Date, a2.Amount
from CTE2 a1
left join CTE2 a2
on a1.accountID = a2.accountID
and a2.rn = a1.rn+1
This will get you the transactions for each day and those for the day previous by accountID on one line. From here you can compare values.
you wanna group by date and sum the amount
select Date,sum(Amount) from Transactions /*where contitions*/ group by Date
You can use this. I hope it will for you.
SELECT
*
FROM Transactions tb
INNER JOIN
(
SELECT MAX([Date]) AS [Date] FROM Transactions
UNION ALL
SELECT MAX([Date]) AS [Date] FROM Transactions WHERE [Date] < (SELECT MAX([Date]) AS [Date] FROM Transactions)
) tb1 ON tb1.[Date] = tb.[Date]
You can check out below query to get last two date and sum of amount on those two dates.
select distinct accountid,subaccountid,user,trandate,sum(amount) over(partition by date)
from transactions
where date>=(select max(date) from transactions where date < (select max(date) from transactions));

Add X number of Working days to a date

I have a table PostingPeriod that uses a company calendar to track all working days. Simplified, it looks like this:
Date Year Quarter Month Day IsWorkingDay
25.06.2015 2015 2 6 25 1
26.06.2015 2015 2 6 26 1
27.06.2015 2015 2 6 27 0
I have another table that contains all purchase lines with the Orderdate, confirmed delivery date from the vendor and the maximum allowed timeframe in working days between orderdate and deliverydate:
PurchID OrderDate ConfDelivery DeliveryDays
1234 14.04.2015 20.05.2015 30
1235 14.04.2015 24.05.2015 20
I want to create a new column that returns the maximum allowed Date (regardless of workday or not) for each order. The usual approach (Workingdays / 5 to get weeks, multiplied by 7 to get days) doesn't work, as all holidays etc need to be taken into consideration.
As this is for a DWH that will feed an OLAP database, performance is not an issue.
You could do this by assigning each working day an arbitrary index using ROW_NUMBER, e.g.
SELECT Date, WorkingDayIndex = ROW_NUMBER() OVER(ORDER BY Date)
FROM dbo.Calendar
Which will give you something like:
Date WorkingDayIndex
-----------------------------
2015-04-27 80
2015-04-28 81
2015-04-29 82
2015-04-30 83
2015-05-01 84
2015-05-05 85
2015-05-06 86
2015-05-07 87
Then if you want to know the date that is n working days from a given date, find the date with an index n higher, i.e. 2015-04-27 has an index of 80, therefore 5 working days later would have an index of 85 which yields 2015-05-05.
FULL WORKING EXAMPLE
/***************************************************************************************************************************/
-- CREATE TABLES AND POPULATE WITH TEST DATA
SET DATEFIRST 1;
DECLARE #Calendar TABLE (Date DATE, IsWorkingDay BIT);
INSERT #Calendar
SELECT TOP 365 DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY object_id), '20141231'), 1 FROM sys.all_objects;
UPDATE #Calendar
SET IsWorkingDay = 0
WHERE DATEPART(WEEKDAY, Date) IN (6, 7)
OR Date IN ('2015-01-01', '2015-04-03', '2015-04-06', '2015-05-04', '2015-05-25', '2015-08-31', '2015-12-25', '2015-12-28');
DECLARE #T TABLE (PurchID INT, OrderDate DATE, ConfDeliveryDate DATE, DeliveryDays INT);
INSERT #T VALUES (1234, '20150414', '20150520', 30), (1235, '20150414', '20150524', 20);
/***************************************************************************************************************************/
-- ACTUAL QUERY
WITH WorkingDayCalendar AS
( SELECT *, WorkingDayIndex = ROW_NUMBER() OVER(ORDER BY Date)
FROM #Calendar
WHERE IsWorkingDay = 1
)
SELECT *
FROM #T AS t
INNER JOIN WorkingDayCalendar AS c1
ON c1.Date = t.OrderDate
INNER JOIN WorkingDayCalendar AS c2
ON c2.WorkingDayIndex = c1.WorkingDayIndex + t.DeliveryDays;
If this is a common requirement, then you could just make WorkingDayIndex a fixed field on your calendar table so you don't need to calculate it each time it is required.
Starting from OrderDate, the Date if you advance N(DeliveryDays) WorkingDays.
If i understood correctly you want something like this:
select
PurchID,
OrderDate,
ConfDelivery,
DeliveryDay,
myDays.[Date] myWorkingDayDeliveryDate
from Purchases p
outer apply (
select
[Date]
from (
select
ROW_NUMBER() OVER (
ORDER BY
Date
) myDays,
[Date]
from PostingPeriod pp
where
IsWorkingDay = 1 and
pp.date >= p.OrderDate
) myDays
where
myDays = p.DeliveryDay
) myDays
You'd have to do something like
SELECT OrderDate.PurchId, OrderDate.OrderDate, OrderDate.DeliveryDays, Aux.Counter, Aux.Date
FROM OrderDate, (SELECT row_number() OVER (ORDER BY Date) AS Counter, Date FROM PostingPeriod WHERE IsWorkingDay = 1 ) Aux
WHERE Counter = DeliveryDays
ORDER BY 1
Basically, you'd need all the dates inserted in the table PostingPeriod (weekends and holidays would have a IsWorkingDay = 0, rest of the days = 1)
and this would provide you the minimal date by summing the OrderDate with the ammount of working days