SQL - first in first out - sql

I want to implement FIFO in my stock table
table looks like:
---+------------+-----------+--------+--------------+-----------
id | shift_type | item_type | amount | name | date
---+------------+-----------+--------+--------------+-----------
1 | in | apple | 50 | apple type 1 | 2017-12-01
2 | out | apple | 30 | apple type 1 | 2017-12-02
3 | in | apple | 40 | apple type 2 | 2017-12-04
4 | in | apple | 60 | apple type 3 | 2017-12-05
5 | out | apple | 20 | apple type 1 | 2017-12-07
6 | out | apple | 10 | apple type 1 | 2017-12-07
7 | in | apple | 20 | apple type 3 | 2017-12-09
and it keeps info about stock shifts.
If I want to take just the oldest incomes - I can take records with lowest id or oldest date.
But... there are also "out" shifts.
So if I want to take for example 50 apples, query should return me 2 records:
---+------------+-----------+--------+--------------+------------+-----
id | shift_type | item_type | amount | name | date | take
---+------------+-----------+--------+--------------+------------+-----
1 | in | apple | 50 | apple type 1 | 2017-12-01 | 20
3 | in | apple | 40 | apple type 2 | 2017-12-04 | 30
because - first out(id 2) takes 30 apples, so there is 20 left from income id 1, and rest of it should be taken from income id 3
How can I implement it with SQL?

I did something here. But need to test with different inputs.
Given a no, say 50 in the outer select query it will give you list of stacks that you need to take apples from.
WITH view_t
AS (
SELECT 1 id
,'in' shift_type
,'apple' item_type
,50 amount
,'apple type 1' NAME
,TO_DATE('2017-12-01', 'yyyy-mm-dd') date1
FROM dual
UNION ALL
SELECT 2
,'out'
,'apple'
,30
,'apple type 1'
,TO_DATE('2017-12-02', 'yyyy-mm-dd')
FROM dual
UNION ALL
SELECT 3
,'in'
,'apple'
,40
,'apple type 2'
,TO_DATE('2017-12-04', 'yyyy-mm-dd')
FROM dual
UNION ALL
SELECT 4
,'in'
,'apple'
,60
,'apple type 3'
,TO_DATE('2017-12-05', 'yyyy-mm-dd')
FROM dual
UNION ALL
SELECT 5
,'out'
,'apple'
,30
,'apple type 2'
,TO_DATE('2017-12-07', 'yyyy-mm-dd')
FROM dual
)
SELECT id
,date1
,shift_type
,NAME
,itemrem
,nvl((
50 - LAG(itemrem) OVER (
ORDER BY id
)
), itemrem) take
FROM (
SELECT id
,date1
,shift_type
,NAME
,SUM(amtretain) OVER (
ORDER BY id
) itemrem
FROM (
SELECT id
,date1
,shift_type
,amount
,signamt
,NAME
,CASE
WHEN LEAD(shift_type) OVER (
ORDER BY id
) = 'out'
THEN LEAD(signamt) OVER (
ORDER BY id
) + signamt
ELSE signamt
END amtretain
--date1,shift_type,sum(signamt) over(order by id)
FROM (
SELECT id
,shift_type
,NAME
,item_type
,date1
,DECODE(shift_type, 'in', amount, 'out', amount * - 1) signamt
,amount
FROM view_t
)
)
WHERE shift_type = 'in'
);
The idea behind the approach is to,
convert the signs of the amount based on the shift_type
Use lead to subtract the signamt based on its shift type
Use a running total to identify the remaining apples at each date
Finally using LAG with the input no of apples to calculate how much to take

Related

recursive moving average with sql

supose we have the next table:
table example
and what i need is:
frst iteration: calculate the moving average 5 days before the last day including the last day = (2+1+2+3+4)/5 = 2.4 and "save" this result, that result will be a prediction for the next day.
scnd iteration: calculate the moving average 5 days before the last, day where the last day basal cell is the value calculated in the previous iteration. (1+2+3+4+2.4)/5 = 2.48
..
and so on.. the recursion will stop for a concrete future day for example: 2022-12-9
deseable output for future day: 2022-12-9
| date_ | art_id | basal_sell |
| ------------| -----------|------------|
| 2022-12-01 | 1 | 2 |
| 2022-12-02 | 1 | 1 |
| 2022-12-03 | 1 | 2 |
| 2022-12-04 | 1 | 3 |
| 2022-12-05 | 1 | 4 |
| 2022-12-06 | 1 | 2.4 |
| 2022-12-07 | 1 | 2.48 |
| 2022-12-08 | 1 | 2.776 |
| 2022-12-09 | 1 | 2.9312 |
this is the partial problem, in the real problem will be a bunch of arts_ids but i think the idea for this parcial problem will be the solution for the big problem (with some little changes).
what i think:
I thought a recursive cte where in the recursive part of the cte i have a union that will be union the temporary table with the new row that i calculated.
Something like:
with MiCte as (
select *
from sells
union all
(
select * from MiCte
)
union
(
select dateadd(day, 1, date_), art_id, basal_sell
from(
select top 1 c.date_, c.art_id,
AVG(c.basal_sell) OVER (partition by c.art_id
ORDER BY c.date_
rows BETWEEN 4 PRECEDING AND current row) basal_sell
from MiCte c
order by c.date_ desc
) as tmp
)
) select * from MiCte
Obviously if I contemplate having more than one art_id I have to take this into account when making top 1 (which I still couldn't think of how to solve).
the example table:
CREATE TABLE sells
(date_ DATETIME,
art_id int,
basal_sell int)
;
INSERT INTO sells
(date_, art_id , basal_sell)
VALUES ('2022-12-1', 1, 2),
('2022-12-2', 1, 1),
('2022-12-3', 1, 2),
('2022-12-4', 1, 3),
('2022-12-5', 1, 4);

Set new field priority in SELECT SQL

I have a table of bills with the following structure:
id | store_name | sum | payment_date
1 | Amazon | 10 | 11.05.2022
2 | Amazon | 20 | 11.05.2022
3 | Ebay | 15 | 11.05.2022
4 | AppleStore | 13 | 11.05.2022
5 | Google Play| 6 | 11.05.2022
What I need is to select all data from table and set additional field "Priority" based on a sum of bill. First 2 rows get priority 1, next 2 rows get priority 2, others get 0:
id | store_name | sum | payment_date | priority
2 | Amazon | 20 | 11.05.2022 | 1
3 | Ebay | 15 | 11.05.2022 | 1
4 | AppleStore | 13 | 11.05.2022 | 2
1 | Amazon | 10 | 11.05.2022 | 2
5 | Google Play| 6 | 11.05.2022 | 0
In addition table contains data about bills from various days (field payment_date) and this priority should be set based on data inside each single day.
Order the rows for each day and then assign priority based on the row number:
SELECT t.*,
CASE ROW_NUMBER()
OVER (PARTITION BY TRUNC(payment_date) ORDER BY sum DESC)
WHEN 1 THEN 1
WHEN 2 THEN 1
WHEN 3 THEN 2
WHEN 4 THEN 2
ELSE 0
END AS priority
FROM table_name t
Which, for the sample data:
CREATE TABLE table_name (id, store_name, sum, payment_date) AS
SELECT 1, 'Amazon', 10, DATE '2022-05-11' FROM DUAL UNION ALL
SELECT 2, 'Amazon', 20, DATE '2022-05-11' FROM DUAL UNION ALL
SELECT 3, 'Ebay', 15, DATE '2022-05-11' FROM DUAL UNION ALL
SELECT 4, 'Apple Store', 13, DATE '2022-05-11' FROM DUAL UNION ALL
SELECT 5, 'Google Play', 6, DATE '2022-05-11' FROM DUAL;
Outputs:
ID
STORE_NAME
SUM
PAYMENT_DATE
PRIORITY
2
Amazon
20
2022-05-11 00:00:00
1
3
Ebay
15
2022-05-11 00:00:00
1
4
Apple Store
13
2022-05-11 00:00:00
2
1
Amazon
10
2022-05-11 00:00:00
2
5
Google Play
6
2022-05-11 00:00:00
0
db<>fiddle here

How do I summarize sales data in SQL by month for last 24months?

I have big number of rows with sales for different products on various days.
I want to retrieve the sum for each product and per month. For the last 24months.
How do I write a WHERE function showing the last 24 months (based on latest date in table not actual date)?
How is that summarized and shown by month instead of individual days like 2018-01-24?
**Sample Data Table**
| SalesDate | Product | SLSqty |
| 2018-01-24 | Product A | 25 |
| 2019-06-10 | Product B | 10 |
| 2019-10-07 | Product C | 4 |
| 2020-03-05 | Product A | 20 |
| 2021-09-01 | Product A | 50 |
| 2021-09-01 | Product B | 10 |
| 2021-09-02 | Product C | 3 |
| 2021-09-04 | Product A | 50 |
| 2021-09-07 | Product B | 10 |
**Expected Result**
| SalesMONTH | Product | SLSqty |
| 2019-10-31 | Product C | 4 |
| 2020-03-31 | Product A | 20 |
| 2021-09-30 | Product A | 100|
| 2021-09-30 | Product A | 20 |
| 2021-09-30 | Product B | 3 |
I would make a parameter that stores the value of the latest date in your table. Then you can impute the parameter in you WHERE clause.
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
CREATE TABLE #TEMP(
[SalesDate] DATE
,[product] NVARCHAR(20)
,[SLSqty] INT
)
INSERT INTO #TEMP([SalesDate],[product],[SLSqty])
VALUES('2018-01-24','Product A',25)
,('2019-06-10','Product B',10)
,('2019-10-07','Product C',4 )
,('2020-03-05','Product A',20)
,('2021-09-01','Product A',50)
,('2021-09-01','Product B',10)
,('2021-09-02','Product C',3 )
,('2021-09-04','Product A',50)
,('2021-09-07','Product B',10)
DECLARE #DATEVAR AS DATE = (SELECT MAX(#TEMP.SalesDate) FROM #TEMP)
The last line declares the variable. If you select #DATEVAR, you get the output of a single date defined by the select statement:
Then you impute it into a where clause. Since you want 24 months prior to the latest date, I would use a DATEDIFF(MONTH,,) function in your where clause. It outputs an integer of months and you simply constrain it to be 24 months or less.
SELECT #TEMP.SalesDate
,#TEMP.product
,#TEMP.SLSqty
,DATEDIFF(MONTH,#TEMP.SalesDate,#DATEVAR) [# of months Diff]
FROM #TEMP
WHERE DATEDIFF(MONTH,#TEMP.SalesDate,#DATEVAR) <= 24
OUTPUT:
Now you have to aggregate the sales grouped by the year-month and product.
I compute year-month by calculating an integer like 202109 (Sept. 2021)
SELECT --#TEMP.SalesDate --(YOU HAVE TO TAKE THIS OUT FOR THE GROUP BY)
YEAR(#TEMP.SalesDate)*100+MONTH(#TEMP.SalesDate) [year-month for GROUP BY]
,#TEMP.product
,SUM(#TEMP.SLSqty) SLSqty
-- ,DATEDIFF(MONTH,#TEMP.SalesDate,#DATEVAR) [# of months Diff] --(YOU HAVE TO TAKE THIS OUT FOR THE GROUP BY)
FROM #TEMP
WHERE DATEDIFF(MONTH,#TEMP.SalesDate,#DATEVAR) <= 24
GROUP BY YEAR(#TEMP.SalesDate)*100+MONTH(#TEMP.SalesDate)
,#TEMP.product
Output:
Here is some oracle sql:
With data ( SalesDate,Product,SLSqty)as(
Select to_date('2018-01-24'),'Product A',25 from dual union all
Select to_date('2019-06-10'),'Product B',10 from dual union all
Select to_date('2019-10-07'),'Product C',4 from dual union all
Select to_date('2020-03-05'),'Product A',20 from dual union all
Select to_date('2021-09-01'),'Product A',50 from dual union all
Select to_date('2021-09-01'),'Product B',10 from dual union all
Select to_date('2021-09-02'),'Product C',3 from dual union all
Select to_date('2021-09-04'),'Product A',50 from dual union all
Select to_date('2021-09-07'),'Product B',10 from dual),
theLatest(SalesDate) as(
select max(SalesDate) from data
)
select to_char(d.SalesDate,'YYYY-MM'),d.Product, sum(SLSqty)
from data d
Join theLatest on d.SalesDate >= add_months(theLatest.SalesDate,-24)
group by to_char(d.SalesDate,'YYYY-MM'),d.Product
order by to_char(d.SalesDate,'YYYY-MM')

Oracle SQL: How can I sum every x number of subsequent rows for each row

I have a data table that looks like this:
|Contract Date | Settlement_Prcie |
|--------------|------------------|
| 01/10/2020 | 50 |
|--------------|------------------|
| 01/11/2020 | 10 |
|--------------|------------------|
| 01/01/2021 | 20 |
|--------------|------------------|
| 01/02/2021 | 30 |
|--------------|------------------|
| 01/03/2021 | 50 |
|--------------|------------------|
I would like to write a query that sums every two rows beneath ... For example, On the first row with contract date 01/10/2020, the sum column would add 10 and 20 to give a result of 30. The next row, the sum column would add 20 and 30 to give 40 and so on. The resulting table of results would look like this:
|Contract Date | Settlement_Prcie | Sum Column |
|--------------|------------------|------------|
| 01/10/2020 | 50 | 30
|--------------|------------------|------------|
| 01/11/2020 | 10 | 50
|--------------|------------------|------------|
| 01/01/2021 | 20 | 80
|--------------|------------------|------------|
| 01/02/2021 | 30 |
|--------------|------------------|
| 01/03/2021 | 50 |
|--------------|------------------|
Could anyone please help me with the query to do this not just for 2 subsequent rows but for x subsequent rows.
So far I had tried using a SUM (Settlement_Price) Over (order by Contract_date Rows between 3 preceding and current row) - Current row of course was not ok, but that is as far as I had gone.
You can use the SUM analytic function:
SELECT contract_date,
settlement_price,
CASE COUNT(*) OVER (
ORDER BY contract_date ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING
)
WHEN 2
THEN SUM( settlement_price ) OVER (
ORDER BY contract_date ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING
)
END AS sum_column
FROM table_name;
Or you can use LEAD:
SELECT contract_date,
settlement_price,
LEAD( settlement_price, 1 , NULL ) OVER ( ORDER BY contract_date )
+ LEAD( settlement_price, 2 , NULL ) OVER ( ORDER BY contract_date )
AS sum_column
FROM table_name;
So, for the test data:
CREATE TABLE table_name ( contract_date, settlement_price ) AS
SELECT DATE '2020-10-01', 50 FROM DUAL UNION ALL
SELECT DATE '2020-11-01', 10 FROM DUAL UNION ALL
SELECT DATE '2020-12-01', 20 FROM DUAL UNION ALL
SELECT DATE '2021-01-01', 30 FROM DUAL UNION ALL
SELECT DATE '2021-02-01', 50 FROM DUAL;
Both queries output:
CONTRACT_DATE | SETTLEMENT_PRICE | SUM_COLUMN
:------------ | ---------------: | ---------:
01-OCT-20 | 50 | 30
01-NOV-20 | 10 | 50
01-DEC-20 | 20 | 80
01-JAN-21 | 30 | null
01-FEB-21 | 50 | null
db<>fiddle here
SUM (Settlement_Price) Over (order by Contract_date Rows between 1 following and 2 following)

Identify two rows with 1 year or more of difference

I have a table called finance that I store all payment of the customer. The main columns are: ID,COSTUMERID,DATEPAID,AMOUNTPAID.
What I need is a list of dates by COSTUMERID with dates of its first payment and any other payment that is grater than 1 year of the last one. Example:
+----+------------+------------+------------+
| ID | COSTUMERID | DATEPAID | AMOUNTPAID |
+----+------------+------------+------------+
| 1 | 1 | 2015-01-10 | 10 |
| 2 | 1 | 2016-01-05 | 30 |
| 2 | 1 | 2017-02-20 | 30 |
| 3 | 2 | 2016-03-15 | 100 |
| 4 | 2 | 2017-02-15 | 100 |
| 5 | 3 | 2017-05-01 | 25 |
+----+------------+------------+------------+
What I expect as result:
+------------+------------+
| COSTUMERID | DATEPAID |
+------------+------------+
| 1 | 2015-01-01 |
| 1 | 2017-02-20 |
| 2 | 2016-03-15 |
| 3 | 2017-05-01 |
+------------+------------+
Costumer 1 have 2 dates: the first one + one more that have more then 1 year after the last one.
I hope I make my self clear.
I think you just want lag():
select t.*
from (select t.*,
lag(datepaid) over (partition by customerid order by datepaid) as prev_datepaid
from t
) t
where prev_datepaid is null or
datepaid > dateadd(year, 1, prev_datepaid);
Gordon's solution is correct, as long as you are only looking at the previous row (previous payment) diff, but I wonder if Antonio is looking for payments greater than one year from the last 1 year payment, in which case this becomes a more complex problem to solve. Take the following example:
CREATE TABLE #Test (
CustomerID smallint
,DatePaid date
,AmountPaid smallint )
INSERT INTO #Test
SELECT 1, '2015-1-10', 10
INSERT INTO #Test
SELECT 1, '2016-1-05', 30
INSERT INTO #Test
SELECT 1, '2017-2-20', 30
INSERT INTO #Test
SELECT 1, '2017-6-30', 50
INSERT INTO #Test
SELECT 1, '2018-3-5', 50
INSERT INTO #Test
SELECT 1, '2018-5-15', 50
INSERT INTO #Test
SELECT 2, '2016-3-15', 100
INSERT INTO #Test
SELECT 2, '2017-6-15', 100
WITH CTE AS (
SELECT
CustomerID
,DatePaid
,LAG(DatePaid) OVER (PARTITION BY CustomerID ORDER BY DatePaid) AS PreviousPaidDate
,AmountPaid
FROM #Test )
SELECT
*
,-DATEDIFF(DAY, DatePaid, PreviousPaidDate) AS DayDiff
,CASE WHEN DATEDIFF(DAY, PreviousPaidDate, DatePaid) >= 365 THEN 1 ELSE 0 END AS Paid
FROM CTE
Row number 5 is > 1 year from the last 1 year payment, but subtracting from previous row doesn't address this. This may or may not matter but I wanted to point it out in case that is what he means.