Using cursor for fetching multiple rows and setting its data in columns - sql

My scenario is I have to populate data into a table by performing join on two tables A and B on the basis of column quoteid and compid
Table A
------------------------------
quoteid compid ................
10004 1
10004 1
10004 1
10004 22
10004 22
10004 22
Table B
------------------------------------
quoteid compid quartercode cost
10004 1 1q10 14
10004 1 2q09 10
10004 1 3q10 12
10004 22 4q12 32
10004 22 3q11 30
10004 22 2q11 43
Now, the result of select query should be like
quoteid compid quarter1cost quarter2cost quarter3cost
10004 1 10 14 12
10004 22 43 30 32
The concept to select cost for quarter is with quartercode which is combination of quarter in year(1st,2nd...) and last two digits of year. So, oldest quarter will with quarter1 , second oldest will be quarter2 and most recent will be quarter3. Here, there cost will be available for only 3 recent quarters due to join conditions. For example, here for quoteid 10004 and compid 1, quarter1 will be 2q09, quarter2 will be 1q10 and quarter3 will be 3q10 and hence the cost.
I am trying to do it with cursor. But since I am new so unable to get the desired result.

Table A seems to have nothing to do with your result.
The basic idea is to use row_number() and conditional aggregation. This is complicated because you the quarter identifier is stored backwards, so it doesn't sort correctly. But you can still do it:
select quoteid, compid,
max(case when seqnum = 1 then cost end) as cost_q1,
max(case when seqnum = 2 then cost end) as cost_q2,
max(case when seqnum = 3 then cost end) as cost_q3
from (select b.*,
row_number() over (partition by quoteid, compid
order by substr(quartercode, 1, 1), substr(quartercode, 3, 2)
) as seqnum
from b
) b
group by quoteid, compid

Related

How to join two tables and count records SQL

table 1 is maintable_KQPPJ : contains GroupID, Year, Name, VendorID. This table contains multiple records with the same GroupID
table 2 is cb_vendorinformation: contains GroupID and CompanyName
I would like to join both tables on GroupID. The output should only have GroupID, CompanyName, and Count. The Count is the distinct count of GroupID in maintable_KQPPJ.
I have the following code but it doesn't really give me the output I'm looking for.
SELECT maintable_KQPPJ.GROUPID, cb_vendorinformation.CompanyName, count(distinct maintable_KQPPJ.GROUPID)
FROM maintable_KQPPJ
JOIN cb_vendorinformation ON maintable_KQPPJ.GROUPID=cb_vendorinformation.GROUPID
maintable_KQPPJ:
GroupID Year VendorID Name
26 2019 9999 John
26 2020 2345 Jane
6 2018 3244 Jack
36 2021 3245 Jill
cb_vendorinformation:
GroupID CompanyName
26 Walmart
6 Target
36 Kroger
The output should look like
GroupID CompanyName Count
26 Walmart 2
6 Target 1
36 Kroger 1
You need group by and count(*)
SELECT maintable_KQPPJ.GROUPID
, cb_vendorinformation.CompanyName
, count(*)
FROM maintable_KQPPJ
JOIN cb_vendorinformation ON maintable_KQPPJ.GROUPID=cb_vendorinformation.GROUPID
GROUP BY maintable_KQPPJ.GROUPID
, cb_vendorinformation.CompanyName

Getting latest price of different products from control table

I have a control table, where Prices with Item number are tracked date wise.
id ItemNo Price Date
---------------------------
1 a001 100 1/1/2003
2 a001 105 1/2/2003
3 a001 110 1/3/2003
4 b100 50 1/1/2003
5 b100 55 1/2/2003
6 b100 60 1/3/2003
7 c501 35 1/1/2003
8 c501 38 1/2/2003
9 c501 42 1/3/2003
10 a001 95 1/1/2004
This is the query I am running.
SELECT pr.*
FROM prices pr
INNER JOIN
(
SELECT ItemNo, max(date) max_date
FROM prices
GROUP BY ItemNo
) p ON pr.ItemNo = p.ItemNo AND
pr.date = p.max_date
order by ItemNo ASC
I am getting below values
id ItemNo Price Date
------------------------------
10 a001 95 2004-01-01
6 b100 60 2003-01-03
9 c501 42 2003-01-03
Question is, is my query right or wrong? though I am getting my desired result.
Your query does what you want, and is a valid approach to solve your problem.
An alternative option would be to use a correlated subquery for filtering:
select p.*
from prices p
where p.date = (select max(p1.date) from prices where p1.itemno = p.itemno)
The upside of this query is that it can take advantage of an index on (itemno, date).
You can also use window functions:
select *
from (
select p.*, rank() over(partition by itemno order by date desc) rn
from prices p
) p
where rn = 1
I would recommend benchmarking the three options against your real data to assess which one performs better.

SQL Server : expand range to include missing rows

I have a table (A) where the PK is an int:
Entry No. Date Product
-------------------------------
10001 1/1/19 Jeans
10002 1/1/19 Shoes
10003 2/1/19 Jeans...
I have another table (B) with sample data below (all values are int):
ID Begin End
-------------------
1 10001 10003
2 10004 10007
3 10008 10019...
I need to find the [ID] in table B for each [Entry No.] in table A.
I believe this requires unpivoting table B so [Begin] and [End] are in the same column then 'expanding' the rows so there's a row for every int:
ID Entry No.
-------------
1 10001
1 10002
1 10003
2 10004
2 10005
2 10006
2 10007
3 10008...
I could then join this to Table A on [Table A].[Entry No.] = [Table B].[Entry.No.]
My unpivot code:
SELECT
ID, [Entry No.]
FROM
(SELECT * FROM [Table B]) AS piv
UNPIVOT
([Entry No.] FOR values IN (Begin, End)) AS unpvt
Is there a better way of doing this? If not, can you please help me with how to 'expand' table B?
Use JOIN:
select a.*, b.*
from a join
b
on a.entry_no between b.begin_entry and b.end_entry

Oracle query stumped - derived table

It's been a long time since I've done more than the most basic sql queries. But I ran into this one today and have spent a few hours on it and am stuck with my derived table attempt (this is for an Oracle db). Looking for a few tips. Thx.
TABLE: dtree
DataID Name
-------------
10001 A.doc
10002 B.doc
10003 C.doc
10004 D.doc
TABLE: collections
CollectionID DataID
---------------------
201 10001
201 10002
202 10003
203 10004
TABLE: rimsNodeClassification
DataID RimsSubject RimsRSI Status
---------------------------------------
10001 blah IS-03 Active
10002 blah LE-01 Active
10003 blah AD-02 Active
10004 blah AD-03 Active
TABLE: rsiEventSched
RimsRSI RetStage DateToUse RetYears
--------------------------------------
IS-03 SEM-PHYS 95 1
IS-03 ACT NULL 2
LE-01 SEM-PHYS 94 1
LE-01 INA-PHYS 95 2
LE-01 ACT NULL NULL
LE-01 OFC NULL NULL
LE-02 SEM-PHYS 94 2
Trying to query on CollectionID=201
INTENDED RESULT:
DataID Name RimsRSI Status SEMPHYS_DateToUse INAPHYS_DateToUse SEMPHYS_RetYears INAPHYS_RetYears
-------------------------------------------------------------------------------------------------------
10001 A.doc IS-03 Active 95 null 1 null
10002 B.doc Le-01 Active 94 95 1 2
You don't need a Derived Table, just join the tables (the last using a Left join) and then apply a MAX(CASE) aggregation:
select c.DataID, t.Name, rnc.RimsRSI, rnc.Status,
max(case when res.RetStage = 'SEM-PHYS' then res.DateToUse end) SEMPHYS_DateToUse,
max(case when res.RetStage = 'INA-PHYS' then res.DateToUse end) INAPHYS_DateToUse,
max(case when res.RetStage = 'SEM-PHYS' then res.RetYears end) SEMPHYS_RetYears,
max(case when res.RetStage = 'INA-PHYS' then res.RetYears end) INAPHYS_RetYears
from collections c
join dtree t
on c.DataID = t.DataID
join rimsNodeClassification rnc
on c.DataID = rnc.DataID
left join rsiEventSched res
on rnc.RimsRSI = res.RimsRSI
where c.CollectionID= 201
group by c.DataID, t.Name, rnc.RimsRSI, rnc.Status

LISTAGG equivalent with windowing clause

In oracle, the LISTAGG function allows me to use it analytically with a OVER (PARTITION BY column..) clause. However, it does not support use of windowing with the ROWS or RANGE keywords.
I have a data set from a store register (simplified for the question). Note that the register table's quantity is always 1 - one item, one transaction line.
TranID TranLine ItemId OrderID Dollars Quantity
------ -------- ------ ------- ------- --------
1 101 23845 23 2.99 1
1 102 23845 23 2.99 1
1 103 23845 23 2.99 1
1 104 23845 23 2.99 1
1 105 23845 23 2.99 1
I have to "match" this data to a table in an special order system where items are grouped by quantity. Note that the system can have the same item ID on multiple lines (components ordered may be different even if the item is the same).
ItemId OrderID Order Line Dollars Quantity
------ ------- ---------- ------- --------
23845 23 1 8.97 3
23845 23 2 5.98 2
The only way I can match this data is by order id, item id and dollar amount.
Essentially I need to get to the following result.
ItemId OrderID Order Line Dollars Quantity Tran ID Tran Lines
------ ------- ---------- ------- -------- ------- ----------
23845 23 1 8.97 3 1 101;102;103
23845 23 2 5.98 2 1 104;105
I don't specifically care if the tran lines are ordered in any way, all I care is that the dollar amounts match and that I don't "re-use" a line from the register in computing the total on the special order. I don't need the tran lines broken out into a table - this is for reporting purposes and the granularity never goes back down to the register transaction line level.
My initial thinking was that I can do this with analytic functions to do a "best match" to identify the the first set of rows that match to the dollar amount and quantity in the ordering system, giving me a result set like:
TranID TranLine ItemId OrderID Dollars Quantity CumDollar CumQty
------ -------- ------ ------- ------- -------- -------- ------
1 101 23845 23 2.99 1 2.99 1
1 102 23845 23 2.99 1 5.98 2
1 103 23845 23 2.99 1 8.97 3
1 104 23845 23 2.99 1 11.96 4
1 105 23845 23 2.99 1 14.95 5
So far so good. But I then try to add LISTAGG to my query:
SELECT tranid, tranline, itemid, orderid, dollars, quantity,
SUM(dollars) OVER (partition by tranid, itemid, orderid order by tranline) cumdollar,
SUM(quantity) OVER (partition by tranid, itemid, orderid order by tranline) cumqty
LISTAGG (tranline) within group (order by tranid, itemid, orderid, tranline) OVER (partition by tranid, itemid, orderid)
FROM table
I discover that it always returns a full agg instead of a cumulative agg:
TranID TranLine ItemId OrderID Dollars Quantity CumDollar CumQty ListAgg
------ -------- ------ ------- ------- -------- -------- ------ -------
1 101 23845 23 2.99 1 2.99 1 101;102;103;104;105
1 102 23845 23 2.99 1 5.98 2 101;102;103;104;105
1 103 23845 23 2.99 1 8.97 3 101;102;103;104;105
1 104 23845 23 2.99 1 11.96 4 101;102;103;104;105
1 105 23845 23 2.99 1 14.95 5 101;102;103;104;105
So this isn't useful.
I would much prefer to do this in SQL if at all possible. I am aware that I can do this with cursors & procedural logic.
Is there any way to do windowing with the LISTAGG analytic function, or perhaps another analytic function which would support this?
I'm on 11gR2.
The only way I can think of to achieve this is with a correlated subquery:
WITH CTE AS
( SELECT TranID,
TranLine,
ItemID,
OrderID,
Dollars,
Quantity,
SUM(dollars) OVER (PARTITION BY TranID, ItemID, OrderID ORDER BY TranLine) AS CumDollar,
SUM(Quantity) OVER (PARTITION BY TranID, ItemID, OrderID ORDER BY TranLine) AS CumQuantity
FROM T
)
SELECT TranID,
TranLine,
ItemID,
OrderID,
Dollars,
Quantity,
CumDollar,
CumQuantity,
( SELECT LISTAGG(Tranline, ';') WITHIN GROUP(ORDER BY CumQuantity)
FROM CTE T2
WHERE T1.CumQuantity >= T2.CumQuantity
AND T1.ItemID = T2.ItemID
AND T1.OrderID = T2.OrderID
AND T1.TranID = T2.TranID
GROUP BY tranid, itemid, orderid
) AS ListAgg
FROM CTE T1;
I realise this doesn't give the exact output you were asking for, but hopefully it is enough to overcome the problem of the cumulative LISTAGG and get you on your way.
I've set up an SQL Fiddle to demonstrate the solution.
In your example, your store register table contains 5 rows and your special order system table contains 2 rows. Your expected result set contains the two rows from your special order system table and all "tranlines" of your store register table should be mentioned in the "Tran Line" column.
This means you need to aggregate those 5 rows to 2 rows. Meaning you don't need the LISTAGG analytic function, but the LISTAGG aggregate function.
Your challenge is to join the rows of the store register table to the right row in the special order system table. You were well on your way by calculating the running sum of dollars and quantities. The only step missing is to define ranges of dollars and quantities by which you can assign each store register row to each special order system row.
Here is an example. First define the tables:
SQL> create table store_register_table (tranid,tranline,itemid,orderid,dollars,quantity)
2 as
3 select 1, 101, 23845, 23, 2.99, 1 from dual union all
4 select 1, 102, 23845, 23, 2.99, 1 from dual union all
5 select 1, 103, 23845, 23, 2.99, 1 from dual union all
6 select 1, 104, 23845, 23, 2.99, 1 from dual union all
7 select 1, 105, 23845, 23, 2.99, 1 from dual
8 /
Table created.
SQL> create table special_order_system_table (itemid,orderid,order_line,dollars,quantity)
2 as
3 select 23845, 23, 1, 8.97, 3 from dual union all
4 select 23845, 23, 2, 5.98, 2 from dual
5 /
Table created.
And the query:
SQL> with t as
2 ( select tranid
3 , tranline
4 , itemid
5 , orderid
6 , sum(dollars) over (partition by itemid,orderid order by tranline) running_sum_dollars
7 , sum(quantity) over (partition by itemid,orderid order by tranline) running_sum_quantity
8 from store_register_table srt
9 )
10 , t2 as
11 ( select itemid
12 , orderid
13 , order_line
14 , dollars
15 , quantity
16 , sum(dollars) over (partition by itemid,orderid order by order_line) running_sum_dollars
17 , sum(quantity) over (partition by itemid,orderid order by order_line) running_sum_quantity
18 from special_order_system_table
19 )
20 , t3 as
21 ( select itemid
22 , orderid
23 , order_line
24 , dollars
25 , quantity
26 , 1 + lag(running_sum_dollars,1,0) over (partition by itemid,orderid order by order_line) begin_sum_dollars
27 , running_sum_dollars end_sum_dollars
28 , 1 + lag(running_sum_quantity,1,0) over (partition by itemid,orderid order by order_line) begin_sum_quantity
29 , running_sum_quantity end_sum_quantity
30 from t2
31 )
32 select t3.itemid "ItemID"
33 , t3.orderid "OrderID"
34 , t3.order_line "Order Line"
35 , t3.dollars "Dollars"
36 , t3.quantity "Quantity"
37 , t.tranid "Tran ID"
38 , listagg(t.tranline,';') within group (order by t3.itemid,t3.orderid) "Tran Lines"
39 from t3
40 inner join t
41 on ( t.itemid = t3.itemid
42 and t.orderid = t3.orderid
43 and t.running_sum_dollars between t3.begin_sum_dollars and t3.end_sum_dollars
44 and t.running_sum_quantity between t3.begin_sum_quantity and t3.end_sum_quantity
45 )
46 group by t3.itemid
47 , t3.orderid
48 , t3.order_line
49 , t3.dollars
50 , t3.quantity
51 , t.tranid
52 /
ItemID OrderID Order Line Dollars Quantity Tran ID Tran Lines
---------- ---------- ---------- ---------- ---------- ---------- --------------------
23845 23 1 8.97 3 1 101;102;103
23845 23 2 5.98 2 1 104;105
2 rows selected.
Regards,
Rob.