How to do this in SQL (PostgreSQL Window Function?) - sql

I have a situation in SQL (PostgreSQL specifically) that I'm struggling with. The schema/model that I'm working with is not under my control and not something I'm able to alter, so I am trying to figure out the best way to deal with the cards I've been dealt.
First, the schema, simplified for this question, but essentially it's invoice (Type = T) and transaction (Type <> T) lines combined into the same table. There can and will be n-number of tranaction lines per invoice and n-number of invoices per client.
Id
Type
InvoiceNo
ClientId
100
I
100
1
99
X
0
1
98
S
0
1
97
T
0
1
96
I
99
1
95
X
0
1
94
S
0
1
What I ultimately would like to end up with is something like the below, with the Invoice (Type = I) records removed and the Transaction (Type <> T) records that fall after each Invoice record populated with it's corresponding InvoiceId value.
Id
Type
InvoiceNo
ClientId
99
X
100
1
98
S
100
1
97
T
100
1
95
X
99
1
94
S
99
1
So far, the closest I've been able to get, which isn't very close, is using the below SQL:
select
t1.Id,
t1.Type,
t2.InvoiceNo,
t1.ClientId
from table AS t1
join (select
Id,
InvoiceNo,
ClientId
from table
where type = 'I') as t2
on t1.ClientId = t2.ClientId
where t1.ClientId = t2.ClientId and t1.Id <= t2.Id and t1.Type <> 'I'
The result of that looks something like the below, which works fine for the first invoice per client and then creates extra transaction records for each invoice
Id
Type
InvoiceNo
ClientId
99
X
100
1
98
S
100
1
97
T
100
1
95
X
100
1
95
X
99
1
94
S
100
1
94
S
99
1
Any help or guidance is much appreciated!
** Updated with more complex example **
Source:
Id
Type
InvoiceNo
ClientId
1
X
0
1
2
I
97
1
3
S
0
2
4
X
0
2
5
S
0
1
6
I
98
2
7
S
0
1
8
X
0
1
9
I
99
1
10
T
0
1
11
S
0
1
12
X
0
1
13
I
100
1
Playing with the answer below, I came up with:
select * from (select t.*,
max(InvoiceNo) filter (where type = 'I') over (partition by clientid order by id DESC) as imputed_invoiceno
from t) as x
where Type <> 'I';
Which gets me close:
Id
Type
InvoiceNo
ClientId
imputed_invoiceno
12
X
0
1
100
11
S
0
1
100
10
T
0
1
100
8
X
0
1
99
7
S
0
1
99
5
S
0
1
99
1
X
0
1
99
4
X
0
2
98
3
S
0
2
98
Best case result:
Id
Type
InvoiceNo
ClientId
12
X
100
1
11
S
100
1
10
T
100
1
8
X
99
1
7
S
99
1
5
S
99
1
1
X
97
1
4
X
98
2
3
S
98
2

Based on your sample data, you can use a cumulative window function:
select t.*,
min(invoiceno) filter (where type = 'I') over (order by id desc) as imputed_invoiceno
from t;

Related

SQL merge 3 tables

I have an sql query involving 2 tables and try to add a third one.
These are the tables
FreeBookPos
FreeBooK_ID
ArticleNr
Amount
FreeBook
ID
BookNr
Date
FreeFields
FreeFieldType
Value
SQLPrimeKey
The first two are linked this way
select FreeBookPos.ArticleNr, Format(FreeBooking.Date, 'yyyy_MM') as dt,
SUM(CASE WHEN FreeBook.BookNr = 0 THEN FreeBookPos.Amount ELSE 0 END) as TotalEntryAmount,
SUM(CASE WHEN FreeBook.BookNr = 1 THEN FreeBookPos.Amount ELSE 0 END) as TotalLeftAmount
From FreeBookPos
INNER JOIN FreeBook on FreeBookPos.FreeBook_ID = FreeBook.ID
group by FORMAT ( FreeBook.Date, 'yyyy_MM'), FreeBookPos.ArticleNr
order by dt, ArticleNr
Now I need to add the table 3. This table is linked via SQLPrimeKey to FeeBook table ID. I then need to have only the fields where FreeFields.Value 2 or 4 and FreeFields.FreeFieldType = 54.
I tried various options with join but never get the result. Would I need to first join table 2 and 3 and then with 1 in a separate step?
Table 1: FreeBookPos
FreeBook_ID ArticleNr Amount
1 145 12
2 145 6
3 143 4
4 145 1
5 145 42
Table 2: FreeBook
ID BookNr Date
1 1 2012-05-19
2 -1 2012-05-21
3 1 2012-05-22
4 -1 2012-05-24
5 -1 2012-06-25
Table 3: FreeFields
SQLPrimareyKey FreeFieldType Value
1 54 1
2 52 2
3 54 4
4 54 2
5 54 2
Result should be:
ArticleNr Dt TotalEntryAmount TotalLeftAmount
143 2012-05 4 0
145 2012-05 0 -1
145 2012-06 0 -42
Try the below -
select FreeBookPos.ArticleNr, Format(FreeBooking.Date, 'yyyy_MM') as dt,
SUM(CASE WHEN FreeBook.BookNr = 0 THEN FreeBookPos.Amount ELSE 0 END) as TotalEntryAmount,
SUM(CASE WHEN FreeBook.BookNr = 1 THEN FreeBookPos.Amount ELSE 0 END) as TotalLeftAmount
From FreeBookPos
INNER JOIN FreeBook on FreeBookPos.FreeBook_ID = FreeBook.ID
inner join FreeFields on FreeBook.ID=SQLPrimareyKey
where value in (2,4) and FreeFieldType = 54
group by FORMAT ( FreeBook.Date, 'yyyy_MM'), FreeBookPos.ArticleNr
order by dt, ArticleNr

sql for Access Database

I am dealing with a huge volume of traffic data. I want to identify the vehicles which have changed their lanes in MS Access database. I want to identify those records only which has changed the lane (immediate two records: before lane change and after lane change)
Traffic Data:
Vehicle_ID Lane_ID Frame_ID Distance
1 2 12 100
1 2 13 103
1 2 14 105
2 1 15 107
***2 1 16 130
2 2 17 135***
2 2 18 136
***3 1 19 140
3 2 20 141***
3 2 21 147
4 2 22 149
***4 2 23 151
4 1 24 154***
4 1 25 159
With assistance from here i have sorted out those Vehicle_ID which have changed their lanes:
SELECT t.Vehicle_ID, COUNT(t.Lane_ID) AS [Lane Count]
FROM (
SELECT DISTINCT Vehicle_ID, Lane_ID FROM Table1
) AS t
GROUP BY t.Vehicle_ID
HAVING COUNT(t.Lane_ID) > 1
Shown Result:
Vehicle_ID Lane Count
2 2
3 2
4 2
Now i want to do further analysis withe records of lane changing by segregating immediate two records: before and after lane change. My desired output would be:
Desired Result:
Vehicle_ID Lane_ID Frame_ID Distance
***2 1 16 130
2 2 17 135***
***3 1 19 140
3 2 20 141***
***4 2 23 151
4 1 24 154***
Assuming the frame ids have no gaps, you can do this using joins:
select t1.*
from (table1 as t1 inner join
table1 as t1prev
on t1prev.Vehicle_ID = t1.Vehicle_ID and
t1prev.frame_id = t1.frame_id - 1
) inner join
table1 as t1next
on t1next.Vehicle_ID = t1.Vehicle_ID and
t1next.frame_id = t1.frame_id + 1
where t1prev.lane_id <> t1.lane_id or
t1next.lane_id <> t1.lane_id;
Otherwise, this will be a very expensive query.
You can do it with EXISTS:
select t.* from Table1 t
where
exists (
select 1 from Table1
where
vehicle_id = t.vehicle_id
and
frame_id in (t.frame_id - 1, t.frame_id + 1)
and
lane_id <> t.lane_id
)

Select every 2nd, 4th row and so on from same column based on specific material

In 1 packing got 2 material and item sequence is 00010 and 00020. What I need is if I input material from item sequence 00010 in where statement which is 'CB016' , I can list out all the item sequence 00020.
Table Data
Packing ItemSeq ItemCate Material TargetQty MinQty
1000009654 10 P CB016 1 0
1000009654 20 I 10000015991 48 0
1000012548 10 P CB016 1 0
1000012548 20 I 10000009495 48 0
1000012564 10 P CB016 1 0
1000012564 20 I 10000009517 48 0
1000007961 10 P CB017 1 0
1000007961 20 I 10000003423 10000 0
1000007962 10 P CB017 1 0
1000007962 20 I 10000003424 10000 0
Expected Output
Packing ItemSeq ItemCate Material TargetQty MinQty
1000009654 20 I 10000015991 48 0
1000012548 20 I 10000009495 48 0
1000012564 20 I 10000009517 48 0
Window functions are not required here. You need the 20 rows for with there is a 10 row in the same group.
SELECT *
FROM yourdata item20
WHERE ItemSeq = 20
AND EXISTS (
SELECT 1
FROM yourdata item10
WHERE item10.Packing = item20.packing
AND ItemSeq = 10
AND Material = 'CB016' -- insert material name here
)
DB Fiddle
You can try with row_number() function:
select * from
(select *, row_number() over (partition by packing order by itemseq desc) as rn)a
where rn=1

finding rows against summed value of specific id's in sql

I have a table like below--
Id| Amount|DateAdded |
--|-------|-----------|
1 20 20-Jun-2018
1 10 05-Jun-2018
1 4 21-May-2018
1 5 15-May-2018
1 15 05-May-2018
2 25 15-Jun-2018
2 25 12-Jun-2018
2 65 05-Jun-2018
2 65 20-May-2018
Here If I sum up the Amount of Id = 1 then I will get 54 as the sum result. I want to find those rows of Id = 1 whose sum is not greater then exact 35 or any given value
In case of given value 35 the expected Output for id = 1 should be--
Id| Amount|DateAdded |
--|-------|-----------|
1 20 20-Jun-2018
1 10 05-Jun-2018
1 4 21-May-2018
1 5 15-May-2018
In case of given value 50 the expected Output for Id = 2 should be--
Id| Amount|DateAdded |
--|-------|-----------|
2 25 15-Jun-2018
2 25 12-Jun-2018
You would use a cumulative sum. To get all the rows:
select t.*
from (select t.*,
sum(amount) over (partition by id order by dateadded) as running_amount
from t
) t
where t.running_amount - amount < 35;
To get just the row that passes the mark:
where t.running_amount - amount < 35 and
t.running_amount >= 35

How to check id-s of parents and then set value

I have table like this :
ID object_id parent_id allowed
1 1 0 0
2 23 25 1
3 25 44 0
4 44 38 0
5 38 1 0
6 52 55 1
7 55 58 0
8 58 60 0
9 60 1 0
Now want select row-s where allowed = 1 and then set allowed = 1 for parents of the row which i select. For example it will be like :
step 1. select object_id , parent_id from myTbl where allowed = 1 Displays:
ID object_id parent_id allowed
2 23 25 1
6 52 55 1
step 2: It checks if the object_id is IN the parent_id from the above result and sets allowed = 1 when the object_id is equal to any of the parent_id's.
The exact same step2 repeats until it reaches a point where there is no match between object_id and parent_id
ID object_id parent_id allowed
2 23 25 1
6 52 55 1
3 25 44 0 --update to 1
7 55 58 0 -- update to 1
The exact same principle is being applied to the folling records, too:
for 25,44,1 - 44,38,0 (allowed is 0 want set 1) when set allowed = 1 it will be
44,38,1
for 55,58,1 - 58,60,0 (allowed is 0 want set 1) when set allowed = 1 it will be
58,60,1
How to do it ? In table My table contains multiple records with status allowed=1 and only 2 of them are used in this particular example.
Try:
UPDATE tbl
SET allowed = 1
FROM (SELECT *
FROM tbl
WHERE allowed = 0) A
INNER JOIN
(SELECT *
FROM tbl
WHERE allowed = 1) B
ON A.objectid = B.parentid