Title. For example, I have below data:
Key1 Key2 Cost Qty_LIFO Date
Red A 2 19 1/4/2018
Red A 3 18 1/3/2018
Red C 4 7 1/2/2018
Red A 5 16 1/1/2018
Blu B 21 91 1/4/2018
Blu B 31 81 1/3/2018
Blu D 41 70 1/2/2018
Blu D 51 60 1/1/2018
The goal is to transform the data to look like below. Flip the quantity column, while also taking into account the Keys/categories
Key1 Key2 Cost Qty_FIFO Date
Red A 2 16 1/4/2018
Red A 3 18 1/3/2018
Red C 4 7 1/2/2018
Red A 5 19 1/1/2018
Blu B 21 81 1/4/2018
Blu B 31 91 1/3/2018
Blu D 41 60 1/2/2018
Blu D 51 70 1/1/2018
or like this (Qty_FIFO is flipped and added to the first example at the top):
Key1 Key2 Cost Qty_LIFO Qty_FIFO Date
Red A 2 19 16 1/4/2018
Red A 3 18 18 1/3/2018
Red C 4 7 7 1/2/2018
Red A 5 16 19 1/1/2018
Blu B 21 91 81 1/4/2018
Blu B 31 81 91 1/3/2018
Blu D 41 70 60 1/2/2018
Blu D 51 60 70 1/1/2018
The purpose of this is to calculate LIFO and FIFO costs.
I need to take Qty_LIFO column (which is sorted by date, descending), flip it vertically (so the data becomes Date ASC), and re-add it to the table without changing the sorting of the Costs column.
Basically, I need to pair the newest Cost data with the oldest Qty data and continue from there.
This is a hack, which only works if you have access to row_number()
CREATE TABLE existing_qry(
Key1 VARCHAR(3) NOT NULL
,Key2 VARCHAR(1) NOT NULL
,Cost INTEGER NOT NULL
,Qty_LIFO INTEGER NOT NULL
,Date DATE NOT NULL
);
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Red','A',2,19,'1/4/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Red','A',3,18,'1/3/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Red','C',4,7,'1/2/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Red','A',5,16,'1/1/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Blu','B',21,91,'1/4/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Blu','B',31,81,'1/3/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Blu','D',41,70,'1/2/2018');
INSERT INTO existing_qry(Key1,Key2,Cost,Qty_LIFO,Date) VALUES ('Blu','D',51,60,'1/1/2018');
with cte as (
select
*
, row_number() over(partition by key1 order by date ASC) rn_asc
, row_number() over(partition by key1 order by date DESC) rn_desc
from existing_qry
)
select
t.Key1, t.Key2, t.Cost, t.Qty_LIFO, flip.Qty_LIFO as Qty_FIFO, t.Date, t.rn_asc, t.rn_desc
from cte as t
inner join cte as flip on t.key1 = flip.key1 and t.rn_asc = flip.rn_desc
It calculates 2 numbers for each row in opposite date order, then aligns rows by requiring these to be equal through a self join. This has the impact of reversing the LIFO numbers (or "flipping" that column).
Key1 Key2 Cost Qty_LIFO Qty_FIFO Date rn_asc rn_desc
---- ------ ------ ------ ---------- ---------- --------------------- -------- ---------
1 Blu B 21 91 60 04.01.2018 00:00:00 4 1
2 Blu B 31 81 70 03.01.2018 00:00:00 3 2
3 Blu D 41 70 81 02.01.2018 00:00:00 2 3
4 Blu D 51 60 91 01.01.2018 00:00:00 1 4
5 Red A 2 19 16 04.01.2018 00:00:00 4 1
6 Red A 3 18 7 03.01.2018 00:00:00 3 2
7 Red C 4 7 18 02.01.2018 00:00:00 2 3
8 Red A 5 16 19 01.01.2018 00:00:00 1 4
https://rextester.com/LQBVD29253
Related
I have table A & B, Need to multiply from current row to last 4 days value, with corresponding 4 rows of another table.
Table A
date
values
seq
01-07-2022
40
4
01-07-2022
90
3
01-07-2022
20
2
01-07-2022
80
1
02-07-2022
30
4
02-07-2022
10
3
02-07-2022
60
2
02-07-2022
20
1
03-07-2022
70
4
03-07-2022
50
3
03-07-2022
10
2
03-07-2022
80
1
Table B
date
values
29-06-2022
20
30-06-2022
21
01-07-2022
22
02-07-2022
23
03-07-2022
24
How to sum the table A & B:
summation for 02-07-2022
table B
table A
date
value
date
values
(A.value * B.value)
sum(I)
--------
---------
----------
---------
---------------------
-------
29-06-2022
20
02-07-2022
30
600
30-06-2022
21
02-07-2022
10
210
01-07-2022
22
02-07-2022
60
1320
02-07-2022
23
02-07-2022
20
460
2590
summation for 03-07-2022
table B
table A
date
value
date
values
(A.value * B.value)
sum(I)
--------
---------
----------
---------
---------------------
-------
30-06-2022
21
03-07-2022
70
1470
01-07-2022
22
03-07-2022
50
1100
02-07-2022
23
03-07-2022
10
230
03-07-2022
24
03-07-2022
80
1920
4720
Expected Output:
date
values
sum
29-06-2022
20
null
30-06-2022
21
null
01-07-2022
22
null
02-07-2022
23
2590
03-07-2022
24
4720
So the logic was rather poorly described. Thank goodness you examples captured enough detail.
select
b.date
,b.value
,t.sum
from table_b as b
left join (
select
a.date
,sum(a.value * b.value) as sum
from table_a as a
join table_b as b
on a.date = dateadd(day, a.seq-1, b.date)
group by 1
having count(*) = 4
) as t
on b.date = t.date
order by 1;
gives:
DATE
VALUE
SUM
2022-06-29
20
null
2022-06-30
21
null
2022-07-01
22
null
2022-07-02
23
2,590
2022-07-03
24
4,720
TableA
ID
Counter
Value
1
1
10
1
2
28
1
3
34
1
4
22
1
5
80
2
1
15
2
2
50
2
3
39
2
4
33
2
5
99
TableB
StartDate
EndDate
2020-01-01
2020-01-11
2020-01-02
2020-01-12
2020-01-03
2020-01-13
2020-01-04
2020-01-14
2020-01-05
2020-01-15
2020-01-06
2020-01-16
TableC (output)
ID
Counter
StartDate
EndDate
Val
1
1
2020-01-01
2020-01-11
10
2
1
2020-01-01
2020-01-11
15
1
2
2020-01-02
2020-01-12
28
2
2
2020-01-02
2020-01-12
50
1
3
2020-01-03
2020-01-13
34
2
3
2020-01-03
2020-01-13
39
1
4
2020-01-04
2020-01-14
22
2
4
2020-01-04
2020-01-14
33
1
5
2020-01-05
2020-01-15
80
2
5
2020-01-05
2020-01-15
99
1
1
2020-01-06
2020-01-16
10
2
1
2020-01-06
2020-01-16
15
I am attempting to come up with some SQL to create TableC. What TableC is, it takes the data from TableB, in chronological order, and for each ID in tableA, it finds the next counter in the sequence, and assigns that to the Start/End date combination for that ID, and when it reaches the end of the counter, it will start back at 1.
Is something like this even possible with SQL?
Yes this is possible. Try to do the following:
Calculate maximal value for Counter in TableA using SELECT MAX(Counter) ... into max_counter.
Add identifier row_number to each row in TableB so it will be able to find matching Counter value using SELECT ROW_NUMBER() OVER() ....
Establish relation between row number in TableB and Counter in TableA like this ... FROM TableB JOIN TableA ON (COALESCE(NULLIF(TableB.row_number % max_counter = 0), max_counter)) = TableA.Counter.
Then gather all these queries using CTE (Common Table Expression) into one query as official documentation shows.
Consider below approach
select id, counter, StartDate, EndDate, value
from tableA
join (
select *, mod(row_number() over(order by StartDate) - 1, 5) + 1 as counter
from tableB
)
using (counter)
if applied to sample data in your question - output is
I have a data set as periodic. However, these periods are not consecutive. My data pattern is like that
Period Customer_No Date Product
1 111 01.01.2017 X
3 111 05.09.2017 Y
8 111 02.05.2018 Z
6 222 02.02.2017 X
9 222 06.04.2017 Z
12 222 05.09.2018 B
15 222 02.01.2019 A
End of the period should be 15 for all customers. I want to create consecutive periods based on customers and fill them with previous data like below:
Period Customer_No Date Product
1 111 01.01.2017 X
2 111 01.01.2017 X
3 111 05.09.2017 Y
4 111 05.09.2017 Y
5 111 05.09.2017 Y
6 111 05.09.2017 Y
7 111 05.09.2017 Y
8 111 02.05.2018 Z
9 111 02.05.2018 Z
10 111 02.05.2018 Z
11 111 02.05.2018 Z
12 111 02.05.2018 Z
13 111 02.05.2018 Z
14 111 02.05.2018 Z
15 111 02.05.2018 Z
6 222 02.02.2017 X
7 222 02.02.2017 X
8 222 02.02.2017 X
9 222 06.04.2017 Z
10 222 06.04.2017 Z
11 222 06.04.2017 Z
12 222 05.09.2018 B
13 222 05.09.2018 B
14 222 05.09.2018 B
15 222 02.01.2019 A
create table tbl_cust(period int,Customer_No int, Date date, Product varchar)
insert into tbl_cust values(1,111,'01.01.2017','X')
insert into tbl_cust values(3,111,'05.09.2017','Y')
insert into tbl_cust values(8,111,'02.05.2018','Z')
insert into tbl_cust values(6,222,'02.02.2017','X')
insert into tbl_cust values(9,222,'06.04.2017','Z')
insert into tbl_cust values(12,222,'05.09.2018','B')
insert into tbl_cust values(15,222,'02.01.2019','A')
You can use a recursive CTE to generate the rows that you want. Then you need to fill them in with the most recent data. What you really want is lag(ignore nulls), but SQL Server does not support that functionality.
There are only up to 15 rows per customer, so apply is a reasonable alternative:
with cte as (
select min(period) as period, customer_no
from tbl_cust
group by customer_no
union all
select period + 1, customer_no
from cte
where period < 15
)
select cte.period, cte.customer_no, c.date, c.product
from cte cross apply
(select top (1) c.*
from tbl_cust c
where c.customer_no = cte.customer_no and
c.period <= cte.period
order by c.period desc
) c
order by cte.customer_no, cte.period;
Here is a db<>fiddle.
You can try this.
select ID as period, Customer_No, [Date], Product from
(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) P(ID)
OUTER APPLY( SELECT *, ROW_NUMBER() OVER(PARTITION BY Customer_No ORDER BY period desc) RN
FROM tbl_cust C WHERE C.period <= P.ID ) X
WHERE X.RN = 1
ORDER BY Customer_No, ID
Result:
period Customer_No Date Product
----------- ----------- ---------- -------
1 111 2017-01-01 X
2 111 2017-01-01 X
3 111 2017-05-09 Y
4 111 2017-05-09 Y
5 111 2017-05-09 Y
6 111 2017-05-09 Y
7 111 2017-05-09 Y
8 111 2018-02-05 Z
9 111 2018-02-05 Z
10 111 2018-02-05 Z
11 111 2018-02-05 Z
12 111 2018-02-05 Z
13 111 2018-02-05 Z
14 111 2018-02-05 Z
15 111 2018-02-05 Z
6 222 2017-02-02 X
7 222 2017-02-02 X
8 222 2017-02-02 X
9 222 2017-06-04 Z
10 222 2017-06-04 Z
11 222 2017-06-04 Z
12 222 2018-05-09 B
13 222 2018-05-09 B
14 222 2018-05-09 B
15 222 2019-02-01 A
Good day community.
I'm having a hard time trying to figure out a way to achieve the results I try to get. As im not very skilled with SQL queries, I start to lose my mind. What I'm trying to do is to find the highest and lowest grade on a particular test, but I also wish to get the ID or the row number (they are matching) of the rows where the MAX() and MIN() were found.
The table "Results" looks like this:
ResultID|Test_UK|Test_US|TestUK_Scr|TestUS_Scr|TestTakenOn
1 1 3 85 14 2018-11-22 00:00:00.000
2 3 1 41 94 2018-11-23 00:00:00.000
3 2 4 71 54 2018-11-24 00:00:00.000
4 4 2 51 52 2018-12-25 00:00:00.000
5 6 3 74 69 2018-12-01 00:00:00.000
6 3 6 83 57 2018-12-02 00:00:00.000
7 7 4 91 98 2018-12-03 00:00:00.000
8 4 7 88 22 2018-12-04 00:00:00.000
9 5 8 41 76 2018-12-08 00:00:00.000
10 8 5 37 64 2018-12-09 00:00:00.000
The results I get when I run my query...
TestID|TopScore|LowScore|LastDateTestTaken
1 94 85 2018-11-23 00:00:00.000
2 71 52 2018-11-25 00:00:00.000
3 83 14 2018-12-02 00:00:00.000
4 98 51 2018-12-04 00:00:00.000
5 64 41 2018-12-09 00:00:00.000
6 74 57 2018-12-02 00:00:00.000
7 91 22 2018-12-04 00:00:00.000
8 76 37 2018-12-09 00:00:00.000
This is the queries I'm working on.
This query returns the results mentioned above
WITH
-- Combine the results of UK and US tests
Combined_Results_Both_Tests AS(
select ResultID as resultID, Test_UK as TestID, Test_UK_Scr as TestScore, TestTakenOn as TestDate from Results
union all
select ResultID as resultID, Test_US as TestID, Test_US_Scr as TestScore, TestTakenOn as TestDate from Results),
--Gets TOP and WORST results of the tests, LastDateTaken (Needs to add ResultID!)
Get_Best_and_Worst_Results_And_LastTestDate AS(
SELECT TestID ,max(TestScore) AS TopScore ,min(TestScore) AS LowScore ,max(TestDate) AS LastDateTestTaken
FROM Combined_Results_Both_Tests
GROUP BY TestID)
--Final query execution
SELECT * FROM Get_Best_and_Worst_Results_And_LastTestDate
I've tried to achieve my desired results with something like this, which doesn't work and is also very inefficient. What I mean that it doesn't work, it is filled with dublicates, whenever the match is found on US and UK tests.
--Gets ReslutID of Min and Max values
Get_ResultID_Of_Results AS(
SELECT * FROM Get_Best_and_Worst_Results_And_LastTestDate A
CROSS APPLY
(SELECT ResultID FROM Results res
WHERE (A.TestID = res.Test_UK AND A.TopScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_US AND A.TopScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_UK AND A.LowScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_US AND A.LowScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_UK AND A.TopScore = res.Test_US_Scr) OR
(A.TestID = res.Test_US AND A.TopScore = res.Test_US_Scr) OR
(A.TestID = res.Test_UK AND A.LowScore = res.Test_US_Scr) OR
(A.TestID = res.Test_US AND A.LowScore = res.Test_US_Scr)) D)
SELECT * FROM Get_ResultID_Of_Results
This is the results I'm trying to achieve (extra columns that would state where Max value and Min value was found) that would state the ResultID from Results table. Also, the row numbers match the ResultIDs in the table.
TestID|TopScore|LowScore|LastDateTestTaken |MaxValueLocID|MinValueLocID|
1 94 85 2018-11-23 00:00:00.000 2 1
2 71 52 2018-11-25 00:00:00.000 3 4
3 83 14 2018-12-02 00:00:00.000 6 1
4 98 51 2018-12-04 00:00:00.000 7 4
5 64 41 2018-12-09 00:00:00.000 10 9
6 74 57 2018-12-02 00:00:00.000 5 6
7 91 22 2018-12-04 00:00:00.000 7 8
8 76 37 2018-12-09 00:00:00.000 9 10
Asking for any help with the solution, theoretical or even practical. Thank you!
If I follow correctly, you want to unpivot the data and aggregate:
select v.testid, max(v.score), min(v.score) max(v.TestTakenOn)
from results r cross apply
(values (Test_UK, TestUK_Scr, TestTakenOn),
(Test_US, TestUS_Scr, TestTakenOn)
) v(testid, score, TestTakenOn)
group by v.testid;
Then you can modify this using window functions:
select v.testid, max(v.score), min(v.score) max(v.TestTakenOn),
max(case when seqnum_desc = 1 then resultid end) as resultid_max,
max(case when seqnum_asc = 1 then resultid end) as resultid_min
from (select r.resultid, v.*,
row_number() over (partition by v.testid order by v.score asc) as seqnum_asc,
row_number() over (partition by v.testid order by v.score desc) as seqnum_desc
from results r cross apply
(values (Test_UK, TestUK_Scr, TestTakenOn),
(Test_US, TestUS_Scr, TestTakenOn)
) v(testid, score, TestTakenOn)
) v
group by v.testid;
with allScores (TestId, Score, TestTakenOn, valueLoc) as
(
select [Test_UK], [TestUK_Scr],[TestTakenOn], ResultId from scores
union all
select [Test_US], [TestUS_Scr],[TestTakenOn], ResultId from scores
),
maxMin (TestId, MaxScore, MinScore, LastTestDate) as (
select TestId, Max(score), Min(score), Max(TestTakenOn)
from allScores
group by TestId
)
select mm.*, a1.valueLoc as MaxValueLoc, a2.ValueLoc as MinValueLoc
from maxMin mm
inner join allScores a1
on mm.TestId = a1.TestId and mm.MaxScore = a1.score
inner join allScores a2
on mm.TestId = a2.TestId and mm.MinScore = a2.score;
DBFiddle demo
For school I need to make a function on an auction website. For this I need to join a couple of tables in a VIEW. This worked just fine, until I needed to add a filter for price range. Seems easy enough but the query result needs to allow a NULL when no bid has been placed.
The Statement for the View:
SELECT I.itemID, I.title, I.startPrice, B.highestBid, Cfi.category, I.endDate
FROM dbo.Items AS I INNER JOIN dbo.category_for_item AS Cfi ON V.itemID = Vir.itemID
LEFT OUTER JOIN dbo.Bid AS B ON V.itemID = B.itemID
This would get the following Table:
itemID title startPrice highestBid category endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 1234 Alfa 25 NULL PC 2018-09-22
3 5678 Bravo 9 20 Console 2018-07-03
4 5678 Bravo 9 15 Console 2018-07-03
5 5678 Bravo 9 NULL Console 2018-07-03
6 9876 Charlie 84 100 Stamps 2018-06-14
7 9876 Charlie 84 90 Stamps 2018-06-14
8 9876 Charlie 84 85 Stamps 2018-06-14
9 9876 Charlie 84 NULL Stamps 2018-06-14
10 1470 Delta 98 100 Fashion 2018-06-15
11 1470 Delta 98 99 Fashion 2018-06-15
12 1470 Delta 98 NULL Fashion 2018-06-15
13 9631 Echo 56 65 Cars 2018-06-25
14 9631 Echo 56 NULL Cars 2018-06-25
15 7856 Foxtrot 98 NULL Dolls 2018-12-26
After looking around for answers I got a query for joining the VIEW on itself with only showing the highest bid instead of all bids:
SELECT VW.itemID, VW.title, VW.startPrice, VW.highestBid, VW.category, VW.endDate
FROM VW_SEARCH AS VW
INNER JOIN (SELECT itemID, MAX(highestBid) AS MaxBid
FROM VW_SEARCH
GROUP BY itemID) VJ
ON VW.itemID = VJ.itemID AND VW.highestBid = VJ.MaxBid
This gave the next results:
itemID title startPrice highestBid category endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 5678 Bravo 9 20 Console 2018-07-03
3 9876 Charlie 84 85 Stamps 2018-06-14
4 1470 Delta 98 100 Fashion 2018-06-15
5 9631 Echo 56 65 Cars 2018-06-25
As I expected the result only showed the items with at least one bid on them. I tried added one extra condition on the subQuery and Joining RIGHT OUTER to make sure I would not get doubles of an itemID.
SELECT VW.itemID, VW.title, VW.startPrice, VW.highestBid, VW.category, VW.endDate
FROM VW_SEARCH AS VW
RIGHT OUTER JOIN (SELECT itemID, MAX(highestBid) AS MaxBid
FROM VW_SEARCH
WHERE highestBid > 0 OR highestBid IS NULL
GROUP BY itemID) VJ
ON VW.itemID = VJ.itemID AND VW.highestBid = VJ.MaxBid
This gave the following results (did not add result 5 - 1199 because it is all the same as result 4, this would happen in the actual table not the example table from above):
itemID title startPrice highestBid category endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 5678 Bravo 9 20 Console 2018-07-03
3 9876 Charlie 84 85 Stamps 2018-06-14
4 NULL NULL NULL NULL NULL NULL
1200 1470 Delta 98 100 Fashion 2018-06-15
1201 9631 Echo 56 65 Cars 2018-06-25
While this is technicly allowing a NULL in the colums I need to get a result in the likes of :
itemID title startPrice highestBid catgory endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 5678 Bravo 9 20 Console 2018-07-03
3 9876 Charlie 84 85 Stamps 2018-06-14
4 1470 Delta 98 100 Fashion 2018-06-15
5 9631 Echo 56 65 Cars 2018-06-25
6 7856 Foxtrot 98 NULL Dolls 2018-12-26
How do I get the desired result, or is it just impossible?
Also if the query could be written better, please say so.
Thanks in advance.
Solve the problem using a left join:
SELECT VW.itemID, VW.title, VW.startPrice, VW.highestBid, VW.category, VW.endDate
FROM VW_SEARCH VW LEFT JOIN
(SELECT itemID, MAX(highestBid) AS MaxBid
FROM VW_SEARCH
GROUP BY itemID
) VJ
ON VW.itemID = VJ.itemID AND VW.highestBid = VJ.MaxBid;
Or, use the ANSI-standard ROW_NUMBER() function:
select vw.*
from (select vw.*,
row_number() over (partition by itemID
order by highestBid nulls last
) as seqnum
from vw_search vw
) vw
where seqnum = 1;
This guarantees one row per item.
Note: Not all databases support NULLS LAST. This may not even be necessary, but you can also implement it using a case expression.
Can you give the definition of the view at least? Maybe the table definition too.
I would go only with subquery as because identity column :
select vw.*
from vw_search vw
where id = (select vm1.id
from vw_search vm1
where vm.itemID = vw1.itemID and vm1.highestBid is not null
order by vm1.highestBid desc
limit 1
);
However, some DBMS has not support LIMIT clause such as SQL Server if so, then you can use TOP clause instead.