Full path traversed for each ID with cycle Oracle - sql

I have an input data which consists of ID,prev,current and next node (not sorted).
I have to find a path between the first and last page for each ID which covers all the nodes traversed.
for eg : if my input data is like :
first column is ID, second column is prev_node, third column is current node, fourth column is next node.
Prev_node will be empty for starting value and next node will be empty for last value
input
id prev current next
1 a b c
1 a e f
1 a b g
1 a b o
1 b c d
1 b g h
1 b o p
1 c d a
1 c b g
1 d a e
1 e f e
1 e f f
1 f e f
1 f f f
1 f f a
1 f a b
1 g h i
1 h i j
1 h j i
1 i j i
1 i i k
1 i k l
1 j i i
1 k l m
1 l m n
1 l n a
1 m n a
1 n a b
1 o p q
1 p q r
1 q r s
1 r s t
1 s t u
1 t u v
1 u v w
1 v w x
1 w x
1 a b
output should be the path of current node like -
ID current
1 a
1 b
1 c
1 d
1 a
1 e
1 f
1 e
1 f
1 f
1 f
1 a
1 b
1 b
1 g
1 h
1 i
1 j
1 j
1 i
1 i
1 k
1 l
1 m
1 n
1 n
1 a
1 b
1 o
1 p
1 q
1 r
1 s
1 t
1 u
1 v
1 w
1 x
There will be many IDs with similar data here i have shown only one ID(1). Also here i have used alphabets which will actually be 200-500 character long string. I tried the SQL approach with little modification, it works fine if an ID has 100 or below rows but gives string concatenation error for more rows (even after converting the long strings to number). Can anyone please suggest a robust procedure based approach to same. I tried some but it doesn't work for more than 300 rows for a ID.
The error that i sometimes encounter with below code is "result of string concatenation is too long"
my code
create or replace procedure pathing
as
type varr is table of varchar(4000);
visit varr;
t number;
--v varchar2(40);
fp varchar2(1000);
np varchar2(1000);
type stype is record(fp varchar2(1000),np varchar2(1000),t number);
type sinput is table of stype;
iarray sinput;
begin
select id
bulk collect into visit
from table_source
group by id
order by count(1) desc;
delete from table_final;
commit;
for k in visit.first .. visit.last loop
delete from table_temp;
commit;
insert into table_temp
select distinct prev_pg, page_id, next_pg, visit(k)
from table_source
where visit_id = visit(k)
order by prev_pg desc;
commit;
insert into table_final
WITH t_n AS (
SELECT prev_pg, page_id, next_pg, rownum n FROM table_temp
),
t_br AS (
SELECT
prev_pg,
page_id,
'<' || listagg(n, '|<') within GROUP(ORDER BY n) || '|' br,
COUNT(0) cnt
FROM
t_n
GROUP BY
prev_pg, page_id
),
t_mp AS (
SELECT
'|' || listagg(list) within GROUP(ORDER BY NULL) list
FROM (
SELECT REPLACE(br, '<') list FROM t_br WHERE cnt > 1
)
),
t_path(step, page_id, next_pg, used) AS (
SELECT 1, page_id, next_pg, ''
FROM t_n
WHERE prev_pg is null
UNION ALL
SELECT
step + 1,
t_br.page_id,
t_n.next_pg,
CASE
WHEN instr(list, '|' || n || '|') = 0
THEN used
ELSE used || n || '|'
END
FROM
t_mp,
t_path
JOIN t_br
ON next_pg = t_br.page_id AND t_path.page_id = prev_pg
JOIN t_n
ON n = regexp_substr(br, '^(<(' || used || '0)\|)*(<(\d+))?', 1, 1, '', 4)
) cycle step
SET is_cycle TO 'Y' DEFAULT 'N'
SELECT
page_id,
next_pg,
step,
visit(k)
FROM t_path
ORDER BY 1;
commit;
end loop;
end;
Explaining my example further more:- I want full path journey of each ID , in the example i have taken ID 1 as example. For ID 1 we have a set of current, previous and next value. So we need to find the path using these values . For example for id 1 the path starts with 'a' because prev column is empty. then we see the next value of a is b i.e current is a and next is b so we search in all the rows of id 1 for prev value as a and current value as b , at the point we find the same we take the next value of the row and repeat the process. For example here prev a ,current b and next is c so we again search for prev b and current c and so on until we get the full path until we encounter next as null as that would be the last

The solution through Hierarchical query clauses seems to be tricky, but there should be a solution, still as an alternative, use your existing PL/SQL code but change the VARCHAR field to CLOB to avoid "result of string concatenation is too long".

you may use the following query in your procedure to accomplish your goal
select id,current into v_your_output_collection from nodes
where id = :vId
start with prev_node is null
connect by
NOCYCLE
prior next_node= current and prior current = prev_node
that works in Oracle 10g and up

Related

Qlik Sense Sum of One field based on unique value of other fields

Sample Data:
P Q R
1 A 3
1 A 3
1 A 2
1 B 5
1 C 7
2 A 3
2 A 3
Expected Output:
P Q R
1 A 5
1 B 5
1 C 7
2 A 3
i Have tried this Sum (Distinct R) but it is not working. i need to group by P and Q column and add Unique Value of R for that. Please support
In chart, you have to add P and Q fields as dimensions. Then your expression should work just fine.
In script your code should look like this:
Load P, Q, Sum( Distinct R ) as sum_of_R
FROM sample_data
Group By P, Q;

Can you rearrange rows in sql tables accodring to a custom logic?

So I've been trying to change the order of rows according to my own logic.
Let's say I have a set of numbers from 1 to 4. If the rows have n entries of different values from 1 to 4 and I want to rearrange all the rows such that I have only rows with 4 at the top followed by a grouped combination of two 2's and a 1, followed by the 3's.
How can it be done using Sql server?
ID ROWS --> ID NEW ORDER
-- ---- -- ---------
A 1 G 4
B 1 G 4
C 2 G 4
C 2 G 4
D 2 C 2
D 2 C 2
E 2 A 1
E 2 E 2
F 3 E 2
F 3 B 1
F 3 D 2
G 4 D 2
G 4 F 3
G 4 F 3
G 4 F 3
Code till now:
SELECT * FROM table
ORDER BY
CASE
WHEN ROW = 4 THEN '1'
WHEN ROW = 1 THEN '2'
WHEN ROW = 2 THEN '3'
WHEN ROW = 3 THEN '4'
END ASC
Assuming ROWS is exactly the number of rows with a paticular ID,
order the rows first by a group according to ROWS interval (4),(1..2), (all the rest) then within the second group row_number() sequences of IDs having ROWS = 1 and 2 independently. Within the same sequence number order by ROWS reverse order.
select ID, ROWS
from (
select *
, case when ROWS = 4 then 1
when ROWS between 1 and 2 then 2
else 3 end grp1
, row_number() over(partition by ROWS order by ROWS) - row_number() over(partition by ROWS, ID order by ID) pos2
from yourtable) t
order by grp1, case ROWS when 1 then pos2 else pos2/2 end, -ROWS, ID
If you want you can try doing it using order by clause. You can use your logic to sort values accordingly. Default order by ASC. So i have tried below query with order by if this gives you some idea of doing it:
select id from test order by case when id%2 = 0 then id end desc
Will give;
ID
8
6
4
5
So you can try ordering with some logic inside.

To pull 1 record out of multiple records having same data in a field based on other fields

A | B | C | D | E
a y 6 12 21
b n 3 10 5
c n 4 12 12
c n 7 12 2
c y 1 12 22
d n 6 10 32
d n 7 10 32
OUTPUT TABLE:
A | B | C | F
a y 6 21
b n 3 12
c y 1 22
d n 6 10
I have a table that contains certain fields. From that table I want to remove duplicate records in A and produce the output table.
Now, the field F is calculated based on the field C when there are no duplicates for the records in A. So, if there is only one record of a in A then if C>5 then the F Column(Output table) pulls the record in E column. So, if record b has the value <5 in field C, then the F column (output table) will pull the record in D column for b. I have been able to achieve this using a case statement.
However, when there are duplicate records in column A, I want only one of the records based on the column B. Only that record should be pulled that has the value 'y' in column B and where the column F contains the value from column E. If none of the duplicate records in A have a value of 'n' in the B column, then pull any record with column D as column F in the output table. I am not able to figure out this part.
Please let me know if anything is not clear.
Code I am using:
SELECT A,B,C,
CASE
WHEN (SELECT COUNT(*) FROM MyTable t2 WHERE t1.A=t2.A)>1
THEN (SELECT TOP 1 CASE WHEN b='y' THEN E ELSE D END
FROM MyTable t3
WHERE t3.A=t1.A
ORDER BY CASE WHEN b='y' THEN 0 ELSE 1 END)
ELSE {
case when cast(C as float) >= 5.00 then (Case when E = '0.00' then D else E end)
when cast(C as float)< 5.00 then D end )
}
END AS F
FROM MyTable t1
You might want to encapsulate this logic in a Function to make it look cleaner, but the logic would go like this:
IF the record count of rows in the table with the same value for A as the current row is greater than 1, THEN SELECT the TOP 1 record with this value for A ORDER BY CASE WHEN b='y' THEN 0 ELSE 1 END
Use another CASE WHEN b='y' to determine if you will use column E or D for output column F.
And ELSE (the record count is not greater than 1), use your existing CASE expression.
EDIT: Here is a more psuedo-codey explanation:
WITH cte AS (SELECT A,B,C,
ROW_NUMBER() OVER (PARTITION BY A, ORDER BY CASE WHEN b='y' THEN 0 ELSE 1 END) rn
FROM MyTable
)
SELECT A,B,C,
CASE
WHEN (SELECT COUNT(*) FROM MyTable t2 WHERE t1.A=t2.A)>1
THEN CASE WHEN b='y' THEN E ELSE D END
ELSE {use your existing CASE Expression}
END AS F
FROM cte t1
WHERE rn=1

Query to fetch records only Oracle and Java

I have the below table :
Employee table :
Id skillset
1 O
2 J
2 O
3 J
4 J
4 O
5 O
5 J
5 U
I want a query to get only the employes those skill sets are O and J ( Oracle and java )
and that means only 2 and 4 and not even 5 bex it has skill set unix.
Do a GROUP BY. With HAVING make sure there are two different skillset values for an id, and that no other values that O and J are there:
select id
from tablename
group by id
having count(distinct skillset) = 2
and count(case when skillset not in ('O','J') then 1 end) = 0

Check succeeding records for special constraint

A mathematical rule must apply to all records with the same name identifier:
x(n) = x(n-1) + y(n-1) where n are the elements sorted by x.
A special case: if y=0, the following value of x does not need to stick to this rule. As an example:
Name X Y
a 0 1
a 1 2
a 2 5 <----- this is invalid because 1 + 2 != 2
b 0 1
b 1 0
b 14 3 <----- this is okay because the preceding element had y = 0
b 16 1 <----- this is invalid because 14 + 3 != 16
The task is to filter the invalid elements.
Without the special case of y=0 I came up with this:
SELECT * FROM TABLE EXCEPT
SELECT NAME, X, Y FROM (
SELECT * FROM TABLE JOIN SELECT NAME AS N, X AS XX, Y AS YY
ON NAME = N WHERE X = 0 OR XX+YY = X)
Does anyone have any suggestion how to handle Y=0?
I have found a solution to your problem. I made an assumption that your table is named 'a'
Here is the code:
SELECT name,
x,
y
FROM
(SELECT a1.name,
a1.x,
a1.y,
(SELECT a3.x + a3.y
FROM a AS a3
WHERE a3.name=a1.name
AND a3.y<>0
AND a3.x =
(SELECT max(a2.x)
FROM a AS a2
WHERE a2.name=a1.name
AND a2.x<a1.x)) AS s1
FROM a AS a1)
WHERE s1 IS NULL
OR x=s1
and the result is:
a 0 1
a 1 2
b 0 1
b 1 0
b 14 3