How to get this below output from DUAL in oracle? - sql

1 L R
1 1 1
1 1 2
1 1 3
1 2 1
1 2 2
1 2 3
1 3 1
1 3 2
1 3 3
Using this query but not able to get for L column
Select 1,level R
from DUAL
Connect by level <=3

You could do a Cartesian join in the row generator query you have to generate 3 rows. Thus, the Cartesian product would generate total 9 rows.
For example,
SQL> WITH DATA AS
2 ( SELECT LEVEL rn FROM dual CONNECT BY LEVEL <=3
3 )
4 SELECT 1, A.rn L, b.rn R FROM DATA A, DATA b
5 /
1 L R
---------- ---------- ----------
1 1 1
1 1 2
1 1 3
1 2 1
1 2 2
1 2 3
1 3 1
1 3 2
1 3 3
9 rows selected.
SQL>

select 1, L, R
from (Select level R
from DUAL
Connect by level <=3),
(Select level L
from DUAL
Connect by level <=3)

You might try something like this:
SELECT 1, CEIL(lvl/3) AS l
, ROW_NUMBER() OVER ( PARTITION BY CEIL(lvl/3) ORDER BY lvl ) AS r
FROM (
SELECT LEVEL AS lvl FROM dual
CONNECT BY LEVEL <= 9
);
The above avoids a cartesian join. See SQLFiddle here.

Related

SQL - how to combine sequential rows based on next row

There is a table I need to join back to itself to created a purchased parts report. But, I keep getting duplicated rows.
How to create a sql that will combine rows based on value of the next row?
Order No and Order Line fields uniquely identify a row in a sales order.
CP_COMP_SEQ is simply a list of all the components needed to manufacture Sales Order and Order Line.
MFG_PURCH_FLG is a flag 'M' means it's a Make part. 'P' means it's a part we need to Purchase.
If an M row follows another M row, then the first component has no purchased parts. If one or more P row(s) follow an M row, then all those sequential rows need to be purchased. Meaning we need to add them to a Purchase Parts report for a buyer to fill out a Purchase Order.
So, in the image below, Row 1 needs two purchased parts to complete. Row 4 doesn't need any purchased parts (because it's followed by another M row). Row 5 needs three purchased parts. And row 9 doesn't need any purchased parts.
Source table
order_no
order_line_no
cp_comp_seq
inv_item
mfg_purch_flg
qty
1
2
1
146FV
M
2
1
2
2
2085
P
4
1
2
3
2095
P
4
1
2
4
ZBAR007
M
1
1
2
5
1467V
M
1
1
2
6
2085
P
2
1
2
7
2095
P
2
1
2
8
3060
P
1
1
2
9
ZBAR007
M
1
2
1
1
xxx
x
x
2
1
2
xxx
x
x
I would like to have the results be one row per item that needs to be purchased based on the previous row that is a 'M' make item. Below is what I would like as the result:Result table
order_no
order_line_no
cp_comp_seq
inv_item
purchase_item
qty
1
2
1
146FV
2085
4
1
2
1
146FV
2095
4
1
2
5
147FV
2085
2
1
2
5
147FV
2095
2
1
2
5
147FV
3060
1
Any help would be greatly appreciated! Thanks in advance.
I tried joining based on rownumber - 1, but that doesn't stop when the Purchase flag changes. I tried looking at Run Groups but was unable to make that work as well.
Any help would be greatly appreciated! Thanks in advance.
You could use analytic function to get the sequence number of M flag of every P flag:
Select t.ORDER_NO, t.ORDER_LINE_NO, t.CP_COMP_SEQ, t.INV_ITEM, t.MFG_PURCH_FLG, t.QTY,
MAX(CASE WHEN t.MFG_PURCH_FLG = 'M' THEN t.CP_COMP_SEQ END) OVER(Order By t.CP_COMP_SEQ Rows Between Unbounded Preceding And Current Row) "M_SEQ"
From tbl t
ORDER_NO ORDER_LINE_NO CP_COMP_SEQ INV_ITEM MFG_PURCH_FLG QTY M_SEQ
---------- ------------- ----------- -------- ------------- --- ----------
1 2 1 146FV M 2 1
1 2 2 2085 P 4 1
1 2 3 2095 P 4 1
1 2 4 ZBAR007 M 1 4
1 2 5 1467V M 1 5
1 2 6 2085 P 2 5
1 2 7 2095 P 2 5
1 2 8 3060 P 1 5
1 2 9 ZBAR007 M 1 9
2 1 10 xxx x x 9
2 1 11 xxx x x 9
If you put it as a subquery in the FROM clause like here:
SELECT t.ORDER_NO, t.ORDER_LINE_NO,
(Select CP_COMP_SEQ From tbl Where CP_COMP_SEQ = t.M_SEQ) "CP_COMP_SEQ",
(Select INV_ITEM From tbl Where CP_COMP_SEQ = t.M_SEQ) "INV_ITEM",
t.INV_ITEM "PURCHASE_ITEM", t.QTY
FROM ( Select t.ORDER_NO, t.ORDER_LINE_NO, t.CP_COMP_SEQ, t.INV_ITEM, t.MFG_PURCH_FLG, t.QTY,
MAX(CASE WHEN t.MFG_PURCH_FLG = 'M' THEN t.CP_COMP_SEQ END) OVER(Order By t.CP_COMP_SEQ Rows Between Unbounded Preceding And Current Row) "M_SEQ"
From tbl t
) t
WHERE t.MFG_PURCH_FLG = 'P'
ORDER BY t.CP_COMP_SEQ
... then, with your sample data ...
WITH
tbl (ORDER_NO, ORDER_LINE_NO, CP_COMP_SEQ, INV_ITEM, MFG_PURCH_FLG, QTY) AS
(
Select 1, 2, 1, '146FV', 'M', '2' From Dual Union All
Select 1, 2, 2, '2085', 'P', '4' From Dual Union All
Select 1, 2, 3, '2095', 'P', '4' From Dual Union All
Select 1, 2, 4, 'ZBAR007', 'M', '1' From Dual Union All
Select 1, 2, 5, '1467V', 'M', '1' From Dual Union All
Select 1, 2, 6, '2085', 'P', '2' From Dual Union All
Select 1, 2, 7, '2095', 'P', '2' From Dual Union All
Select 1, 2, 8, '3060', 'P', '1' From Dual Union All
Select 1, 2, 9, 'ZBAR007', 'M', '1' From Dual Union All
Select 2, 1, 10, 'xxx', 'x', 'x' From Dual Union All
Select 2, 1, 11, 'xxx', 'x', 'x' From Dual
)
... the result would be:
ORDER_NO ORDER_LINE_NO CP_COMP_SEQ INV_ITEM PURCHASE_ITEM QTY
---------- ------------- ----------- -------- ------------- ---
1 2 1 146FV 2085 4
1 2 1 146FV 2095 4
1 2 5 1467V 2085 2
1 2 5 1467V 2095 2
1 2 5 1467V 3060 1
Take a look at the cross apply syntax. This syntax allows you to drive the inner query off each row in the other query.
select *
from AnyTableYouWant T1
cross apply
(
select top 1 ID from AnyTableYouWant T2
where T1.ID = T2.ID
order by T2.ID desc
) T3
T1 or T2 can be the same or different tables. As long as you come up with join criteria. A cross apply is different from a typical left, right or inner join. There's plenty of literature on these queries. This answer is not meant to be a diatribe on how it works.
After reading this question more closely, it would benefit from just walking through a result set sequentially and not trying to do it all in SQL. If you want to do it all in SQL, lookup up CURSORs. For maintenance reasons using python or C# or any other procedural language. Query all the records from the parts table make sure it's sorted the way you have it listed, then walk through the results one record at a time and apply the business logic you described.

Oracle Hierarchical Query at depth level

I have a requirement to build a table from a hierarchical table. Table structure as below:
emp_hier table:
emp_id
supervisorId
100
null
1
100
2
1
3
2
New table:
I have to write a select query on the emp_heir table and the selected data should look like this:
sel_emp_id
rel_emp_id
relation
depth_lvl
100
100
self
0
100
1
My Repotee
-1
100
2
My Repotee
-2
100
3
My Repotee
-3
1
100
My Mgr
1
1
1
self
0
1
2
My Repotee
-1
1
3
My Repotee
-2
2
1
My Mgr
1
2
2
self
0
2
3
My Repotee
-1
3
100
My Mgr
3
3
1
My Mgr
2
3
2
My Mgr
1
3
3
self
0
You can use UNION ALL to combine a hierarchical query to get each row and its children to another hierarchical query to get all the ancestors:
SELECT CONNECT_BY_ROOT emp_id AS sel_emp_id,
emp_id AS rel_emp_id,
CASE LEVEL WHEN 1 THEN 'Self' ELSE 'My Reportee' END AS relation,
1 - LEVEL AS depth_lvl
FROM emp_hier
CONNECT BY PRIOR emp_id = supervisorid
UNION ALL
SELECT CONNECT_BY_ROOT emp_id,
emp_id,
'My Mgr',
LEVEL - 1
FROM emp_hier
WHERE LEVEL > 1
CONNECT BY PRIOR supervisorid = emp_id
ORDER BY sel_emp_id, depth_lvl DESC
Which, for your sample data:
CREATE TABLE emp_hier (emp_id, supervisorId) AS
SELECT 100, null FROM DUAL UNION ALL
SELECT 1, 100 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL;
Outputs:
SEL_EMP_ID
REL_EMP_ID
RELATION
DEPTH_LVL
1
100
My Mgr
1
1
1
Self
0
1
2
My Reportee
-1
1
3
My Reportee
-2
2
100
My Mgr
2
2
1
My Mgr
1
2
2
Self
0
2
3
My Reportee
-1
3
100
My Mgr
3
3
1
My Mgr
2
3
2
My Mgr
1
3
3
Self
0
100
100
Self
0
100
1
My Reportee
-1
100
2
My Reportee
-2
100
3
My Reportee
-3
db<>fiddle here
Using CONNECT BY, you can connect all the employees and their relationships to each other. Then by joining that information together, you can print out the information in the format you desire.
WITH
hier
AS
( SELECT e.*, LEVEL AS lvl
FROM emp_hier e
CONNECT BY PRIOR emp_id = supervisorid
START WITH supervisorid IS NULL)
SELECT h1.emp_id AS sel_emp_id,
h2.emp_id AS rel_emp_id,
CASE
WHEN h1.lvl - h2.lvl = 0 THEN 'self'
WHEN h1.lvl - h2.lvl > 0 THEN 'My Mgr'
ELSE 'My Reportee'
END AS relation,
h1.lvl - h2.lvl AS depth_level
FROM hier h1, hier h2
ORDER BY CASE WHEN h1.supervisorid IS NULL THEN 0 ELSE 1 END, h1.emp_id, h1.lvl - h2.lvl DESC;
SEL_EMP_ID REL_EMP_ID RELATION DEPTH_LEVEL
_____________ _____________ ______________ ______________
100 100 self 0
100 1 My Reportee -1
100 2 My Reportee -2
100 3 My Reportee -3
1 100 My Mgr 1
1 1 self 0
1 2 My Reportee -1
1 3 My Reportee -2
2 100 My Mgr 2
2 1 My Mgr 1
2 2 self 0
2 3 My Reportee -1
3 100 My Mgr 3
3 1 My Mgr 2
3 2 My Mgr 1
3 3 self 0
You can get the entire desired result with a single pass through the hierarchy (a single CONNECT BY query), with no self-join and no union all.
Instead, I use a helper inline view (with just one row and two numeric columns, with the values -1 and 1); since each "relationship" appears exactly twice in the output, with the exception of "Self", I use this to do an ad-hoc duplication of the rows from the hierarchical query.
I used the table in MT0's post for testing. I don't show the result - it's the same (just ordered differently).
with
h (x) as (select 1 from dual union all select -1 from dual)
, p (ancestor, emp, depth) as (
select connect_by_root(emp_id), emp_id, level - 1
from emp_hier
connect by supervisorid = prior emp_id
)
select case h.x when 1 then emp else ancestor end as emp_self,
case h.x when 1 then ancestor else emp end as emp_related,
case when h.x = 1 then 'Mgr'
when p.depth != 0 then 'Reportee'
else 'Self' end as rel,
h.x * p.depth as depth_level
from p join h on h.x = -1 or p.depth != 0 -- do not duplicate "Self"
order by emp_self, depth_level desc, emp_related -- or whatever (if/as needed)
;

Oracle hierarchical queries data

The link gives a good example for overview on how to use Oracle hierarchical queries. I was trying to generate the below combination of data with the example table tab1 given in the link but struck for a while.Using Oracle version 12.2
PARENT_ID CHILD_ID
-------- ---------
1 2 # the root node and id combination (This is evident by using ROOT_ID,ID)
1 3
1 4
. .
. .
2 5 # How to generate below combination in a select statement ont TAB1 table.
2 6
. .
. .
9 11
SELECT id,
parent_id,
RPAD('.', (level-1)*2, '.') || id AS tree,
level,
CONNECT_BY_ROOT id AS root_id,
LTRIM(SYS_CONNECT_BY_PATH(id, '-'), '-') AS path,
CONNECT_BY_ISLEAF AS leaf
FROM tab1
START WITH parent_id IS NULL
CONNECT BY parent_id = PRIOR id
ORDER SIBLINGS BY id;
Output of the above select statement
ID PARENT_ID TREE LEVEL ROOT_ID PATH LEAF
---------- ---------- -------------------- ---------- ---------- -------------------- ----------
1 1 1 1 1 0
2 1 ..2 2 1 1-2 0
3 2 ....3 3 1 1-2-3 1
4 2 ....4 3 1 1-2-4 0
5 4 ......5 4 1 1-2-4-5 1
6 4 ......6 4 1 1-2-4-6 1
7 1 ..7 2 1 1-7 0
8 7 ....8 3 1 1-7-8 1
9 1 ..9 2 1 1-9 0
10 9 ....10 3 1 1-9-10 0
11 10 ......11 4 1 1-9-10-11 1
12 9 ....12 3 1 1-9-12 1
Diagraph:
The sql query to get the desired output are below:
Solution 1: using CTE
with h ( id, parent_id ) as (
select id ,parent_id from tab1
),
r ( id , parent_id, steps ) as (
select id , id , 0
from h
union all
select r.id, h.parent_id, steps + 1
from h join r
on h.id = r.parent_id
)
select parent_id, id
from r
where parent_id != id
order by parent_id asc;
Solution 2: Using Oracle only connect by query. The CONNECT BY NOCYCLE clause can be used to not traverse cyclical hierarchies if any.
with hier_data as
(
select
connect_by_root id as parent_id
,id
from tab1
connect by parent_id = prior id
order by parent_id,id
)
select * from hier_data
where
parent_id != id;

SQL Server - group and number matching contiguous values

I have a list of stock transactions and I am using Over(Partition By) to calculate the running totals (positions) by security. Over time a holding in a particular security can be long, short or flat. I am trying to find an efficient way to extract only the transactions relating to the current position for each security.
I have created a simplified sqlfiddle to show what I have so far. The cte query generates the running total for each security (code_id) and identifies when the holdings are long (L), short (s) or flat (f). What I need is to group and number matching contiguous values of L, S or F for each code_id.
What I have so far is this:
; WITH RunningTotals as
(
SELECT
*,
RunningTotal = sum(qty) OVER (Partition By code_id Order By id)
FROM
TradeData
), LongShortFlat as
(
SELECT
*,
LSF = CASE
WHEN RunningTotal > 0 THEN 'L'
WHEN RunningTotal < 0 THEN 'S'
ELSE 'F'
END
FROM
RunningTotals
)
SELECT
*
FROM
LongShortFlat r
I think what I need to do is create a GroupNum column by applying a row_number for each group of L, S and F within each code_id so the results look like this:
id code_id qty RunningTotal LSF GroupNum
1 1 5 5 L 1
2 1 2 7 L 1
3 1 7 14 L 1
4 1 -3 11 L 1
5 1 -5 6 L 1
6 1 -6 0 F 2
7 1 5 5 L 3
8 1 5 10 L 3
9 1 -2 8 L 3
10 1 -4 4 L 3
11 2 5 5 L 1
12 2 3 8 L 1
13 2 -4 4 L 1
14 2 -2 2 L 1
15 2 -2 0 F 2
16 2 6 6 L 3
17 2 -5 1 L 3
18 2 -5 -4 S 4
19 2 2 -2 S 4
20 2 4 2 L 5
21 2 -5 -3 S 6
22 2 -2 -5 S 6
23 3 5 5 L 1
24 3 2 7 L 1
25 3 1 8 L 1
I am struggling to generate the GroupNum column.
Thanks in advance for your help.
[Revised]
Sorry about that, I read your question too quickly. I came up with a solution using a recursive common table expression (below), then saw that you've worked out a solution using LAG. I'll post my revised query anyway, for posterity. Either way, the resulting query is (imho) pretty ugly.
;WITH cteBaseAgg
as (
-- Build the "sum increases over time" data
SELECT
row_number() over (partition by td.code_id order by td.code_id, td.Id) RecurseKey
,td.code_id
,td.id
,td.qty
,sum(tdPrior.qty) RunningTotal
,case
when sum(tdPrior.qty) > 0 then 'L'
when sum(tdPrior.qty) < 0 then 'S'
else 'F'
end LSF
from dbo.TradeData td
inner join dbo.TradeData tdPrior
on tdPrior.code_id = td.code_id -- All for this code_id
and tdPrior.id <= td.Id -- For this and any prior Ids
group by
td.code_id
,td.id
,td.qty
)
,cteRecurse
as (
-- "Set" the first row for each code_id
SELECT
RecurseKey
,code_id
,id
,qty
,RunningTotal
,LSF
,1 GroupNum
from cteBaseAgg
where RecurseKey = 1
-- For each succesive row in each set, check if need to increment GroupNum
UNION ALL SELECT
agg.RecurseKey
,agg.code_id
,agg.id
,agg.qty
,agg.RunningTotal
,agg.LSF
,rec.GroupNum + case when rec.LSF = agg.LSF then 0 else 1 end
from cteBaseAgg agg
inner join cteRecurse rec
on rec.code_id = agg.code_id
and agg.RecurseKey - 1 = rec.RecurseKey
)
-- Show results
SELECT
id
,code_id
,qty
,RunningTotal
,LSF
,GroupNum
from cteRecurse
order by
code_id
,id
Sorry for making this question a bit more complicated than it needed to be but for the sake of closure I have found a solution using the lag function.
In order to achieve what I wanted I continued my cte above with the following:
, a as
(
SELECT
*,
Lag(LSF, 1, LSF) OVER(Partition By code_id ORDER BY id) AS prev_LSF,
Lag(code_id, 1, code_id) OVER(Partition By code_id ORDER BY id) AS prev_code
FROM
LongShortFlat
), b as
(
SELECT
id,
LSF,
code_id,
Sum(CASE
WHEN LSF <> prev_LSF AND code_id = prev_code
THEN 1
ELSE 0
END) OVER(Partition By code_id ORDER BY id) AS grp
FROM
a
)
select * from b order by id
Here is the updated sqlfiddle.

increasing value with condition on oracle

How to add an increment value (not summarize) with some condition on another column?
I'm using Oracle-like DBMS, named Tibero, for simple example i want to produce this data
ROWNUM GRP_STRT GRP_NO SLBY
1 1 1 1
2 1 1 1
3 1 1 1
4 1 1 1
5 1 1 1
6 1 2 0
7 1 2 0
8 1 3 1
9 1 3 1
10 1 3 1
11 1 4 0
12 1 5 1
Column SLBY is for Buy/Sell code (0=Buy, 1=Sell) then every changing type of transaction, column GRP_NO increasing (but it's not grouping by SLBY column)
SELECT CASE
WHEN ROWNUM = 1 THEN GRP_NO
WHEN ROWNUM <> 1 AND SLBY = LAG(SLBY,1) over (ORDER BY ROWNUM) THEN LAG(GRP_STRT,1) over (ORDER BY ROWNUM) - 1
WHEN ROWNUM <> 1 AND SLBY_DSTN_CD <> LAG(SLBY_DSTN_CD,1) over (ORDER BY ROWNUM) THEN LAG(GRP_STRT,1) over (ORDER BY ROWNUM) + 1
END TARGET_GROUPING
, A.*
FROM SOME_TABLE
I tried with that query but instead of getting what i want like in the picture above, I produced a GRP_NO like 1 1 1 1 1 2 1 1 1 1 2 1 1 1 (first change SLBY only)
Apologies for my bad english and bad explanation, I'll explain more if need further information, thanks for your help!
As far as I understood your problem,
You are trying to calculate GRP_NO from ROWNUM, GRP_STRT, GRP_NO, and SLBY.
I have created the following query for you.
You can check the logic and apply it in your code accordingly:
SELECT
RN,
GRP_STRT,
SUM(CASE
WHEN PREV_SLBY_DSTN_CD IS NULL
OR PREV_SLBY_DSTN_CD <> SLBY_DSTN_CD THEN 1
END) OVER(
ORDER BY
RN
) AS GRP_NO,
SLBY_DSTN_CD AS SLBY
FROM
(
SELECT
RN,
LAG(SLBY_DSTN_CD) OVER(
ORDER BY
RN
) AS PREV_SLBY_DSTN_CD,
SLBY_DSTN_CD,
GRP_STRT
FROM
(SELECT ROWNUM RN, .... FROM SOME_TABLE) A
)
This code is to generate the output as shown in question:
ROWNUM GRP_STRT GRP_NO SLBY
1 1 1 1
2 1 1 1
3 1 1 1
4 1 1 1
5 1 1 1
6 1 2 0
7 1 2 0
8 1 3 1
9 1 3 1
10 1 3 1
11 1 4 0
12 1 5 1
Cheers!!