PRIOR in SELECT list - sql

I can't understand what it adds to the result of the query. From the book that I'm learning:
If you prefix a column name with PRIOR in the
select list (SELECT PRIOR EMPLOYEE_ID, ...), you specify the “prior” row’s value.
SELECT PRIOR EMPLOYEE_ID, MANAGER_ID, LPAD(' ', LEVEL * 2) || EMPLOYEES.JOB_ID
FROM EMPLOYEES
START WITH EMPLOYEE_ID = 100
CONNECT BY PRIOR EMPLOYEE_ID = MANAGER_ID;
The only difference I see, is that it adds a NULL value in the first row and increments IDs of employees by 1.

PRIOR just takes a record from a previous record in the traversed hierarchy.
I think the best way to undestand how it works is to play with a simple hierarchy:
create table qwerty(
id int,
name varchar2(100),
parent_id int
);
insert all
into qwerty values( 1, 'Grandfather', null )
into qwerty values( 2, 'Father', 1 )
into qwerty values( 3, 'Son', 2 )
into qwerty values( 4, 'Grandson', 3 )
select 1234 from dual;
The below query traverses the above hierarchy:
select level, t.*
from qwerty t
start with name = 'Grandfather'
connect by prior id = parent_id
LEVEL ID NAME PARENT_ID
---------- ---------- -------------------- ----------
1 1 Grandfather
2 2 Father 1
3 3 Son 2
4 4 Grandson 3
If we add "PRIOR name" to the above query, then the name of "parent" is displayed. This vaue is taken from prevoius record in the hierarchy (from LEVEL-1)
select level, prior name as parent_name, t.*
from qwerty t
start with name = 'Grandfather'
connect by prior id = parent_id;
LEVEL PARENT_NAME ID NAME PARENT_ID
---------- -------------------- ---------- -------------------- ----------
1 1 Grandfather
2 Grandfather 2 Father 1
3 Father 3 Son 2
4 Son 4 Grandson 3

PRIOR operator returns previous value in a hierarchy build using CONNECT BY clause.
WITH hierarchy(id, parent_id, value) AS (
SELECT 1, NULL, 'root' FROM dual UNION ALL
SELECT 2, 1, 'child 1' FROM dual UNION ALL
SELECT 3, 1, 'child 2' FROM dual UNION ALL
SELECT 4, 3, 'grand child 1' FROM dual
)
SELECT
hierarchy.*, LEVEL depth, PRIOR value
FROM
hierarchy
START WITH
parent_id IS NULL
CONNECT BY
PRIOR id = parent_id
This simple query connects the rows from root to leafs. The PRIORVALUE column returns value of VALUE column of row's parent row (predecessor within the hierarchy), so 'grand child 1' parent is 'child 2' or 'child 1' parent is 'root'. 'root', the first row within the hierarchy (LEVEL = 1) doesn't have any parent therefore PRIOR returns NULL.
If you connect the hierarchy in opposite direction, from a leaf to the root, the PRIOR operator will return child row that was used to connect the row you're looking at.
The LEVEL column shows the depth of specific row within the hierarchy.

Related

Convert the below logic in oracle tree

Recursively the parent is inserted first and then the children. How can I migrate the logic in Oracle using parent-child.The logic for the SQL should be first the parent is evaluated and then inserted or added and then the child is checked and then it is prepared.
Thank you for the response , the table structure as follow Name
REF_ID NOT NULL NUMBER
REF_TYPE_ID NOT NULL NUMBER
PARENT_REF_ID NUMBER
REF_VALUE NOT NULL VARCHAR2(255)
Now the expected result conditions: we need to start finding the query with ref_id, so if ref_id is supposed 14, then we need to check the corresponding should have parent_ref_id is null or not, if parent_ref_id is present suppose 4, then we need to again get the value for 4 as ref_id and check is parent_id is null or not If null then we need to check the value ref_type_id whether it is 1, 2,3,4,..; etc and based on the condition we need to append the result 4(parent_ref_id) with some text let it be 'article 4'. Now resuming back to the loop, the user had enter ref_id 14, then we need to check the value for ref_type_id for the corresponding and then we need to append with the result suppose 'par 14' and this result had to come after parent result: article 4 par 14 (article 4 - parent, par 14 children)
I believe that using a CONNECT BY statement with SYS_CONNECT_BY_PATH should be able to help you achieve your goal.
WITH
refs (ref_id,
ref_type_id,
parent_ref_id,
ref_value)
AS
(SELECT 501, 1, NULL, 207 FROM DUAL
UNION ALL
SELECT 502, 2, 501, 4 FROM DUAL
UNION ALL
SELECT 503, 3, 502, 1 FROM DUAL)
SELECT CONNECT_BY_ROOT r.ref_id as starting_ref_id,
TRIM (
',' FROM
SYS_CONNECT_BY_PATH (
CASE r.ref_type_id
WHEN 1 THEN 'article '
WHEN 2 THEN 'par '
WHEN 3 THEN '('
WHEN 4 THEN 'point '
WHEN 5 THEN 'sous '
WHEN 6 THEN NULL
WHEN 8 THEN NULL
ELSE '/'
END
|| r.ref_id,
',')) AS ref_label
FROM refs r
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR r.parent_ref_id = r.ref_id;
STARTING_REF_ID REF_LABEL
__________________ ___________________________
501 article 501
502 par 502,article 501
503 (503,par 502,article 501

Sort strings/words alphabetically separated by comma within a column in SQL (entire column)

Lets say that I have a table the following data:
(there are a 1000+ more rows like this)
Bird
----------------------------
Sparrow, Eagle, Crow
Woodpecker, Sparrow
Crow, Eagle
etc. etc.
I want the final column to be sorted out alphabetically. Something like this:
Bird
--------------------
Crow, Eagle, Sparrow
Sparrow, Woodpecker
Crow, Eagle
etc. etc.
Need to know a SQL query that can do that. Possibly SQL Developer.
Here is an Oracle solution using Common Table Expressions (CTEs) to break the problem down. Not sure if this will help, but maybe it will give you an idea or a starting point that you can apply to your environment.
SQL> -- Set up original data set
SQL> with bird_tbl(id, unsorted_list) as (
select 1, 'Sparrow, Eagle, Crow' from dual union all
select 2, 'Woodpecker, Sparrow' from dual union all
select 3, 'Crow, Eagle' from dual
),
-- Split the list into a row for each element
split_tbl(id, bird) as (
select id, regexp_substr(unsorted_list, '(.*?)(, |$)', 1, level, null, 1)
from bird_tbl
connect by level <= regexp_count(unsorted_list, ', ')+1
and prior id = id
and prior sys_guid() is not null
)
-- select * from split_tbl;
-- Rebuild the sorted row
select id, listagg(bird, ', ')
within group (order by bird) sorted_list
from split_tbl
group by id;
ID SORTED_LIST
---------- --------------------
1 Crow, Eagle, Sparrow
2 Sparrow, Woodpecker
3 Crow, Eagle
EDIT: Here's how to apply to your situation. Just replace <your_primary_key> with the primary key column name, <your_column_name> with the name of the column that contains the unsorted list and <your_table_name> with the name of the table.
with split_tbl(<your_primary_key>, <your_column_name>) as (
select <your_primary_key>, regexp_substr(<your_column_name>, '(.*?)(, |$)', 1, level, null, 1)
from <your_table_name>
connect by level <= regexp_count(<your_column_name>, ', ')+1
and prior <your_primary_key> = <your_primary_key>
and prior sys_guid() is not null
)
-- select * from split_tbl;
-- Rebuild the sorted row
select <your_primary_key>, listagg(<your_column_name>, ', ')
within group (order by <your_column_name>) sorted_list
from split_tbl
group by <your_primary_key>;

How do I separate and parse out data from multiple columns into separate rows (Oracle)

I have columns with multiple values delimited by a comma in each column and row. I am trying to separate them out into separate rows. If i have a null value for one of them (as shown below) I will still include the null value as long as one of the other values are still present for that particular row.
What I'm given
First_Name (John, ,Phil)
Last_Name (Smith,No, )
Location (CA,GA,NY)
What I want
(John, Smith, CA)
( , No, GA)
(Phil, ,NY)
I've tried using the regexp_substr method but it's not returning any rows that have a null in any one of the 3 columns listed above.
with
inputs ( id, first_name, last_name, location ) as (
select 101, 'John,,Phil' , 'Smith,No,' , 'CA,GA,NY' from dual union all
select 102, 'Jo,Al,Ed,Li', 'Ng,Tso,,Roth', ',ZZ,,BB' from dual
)
-- End of simulated inputs (for testing only, not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select id,
regexp_substr(first_name, '([^,]*)(,|$)', 1, level, null, 1) as first_name,
regexp_substr(last_name , '([^,]*)(,|$)', 1, level, null, 1) as last_name,
regexp_substr(location , '([^,]*)(,|$)', 1, level, null, 1) as location
from inputs
connect by level <= regexp_count(first_name, ',') + 1
and prior id = id
and prior sys_guid() is not null
;
ID FIRST_NAME LAST_NAME LOCATION
---- ----------- ------------ --------
101 John Smith CA
101 No GA
101 Phil NY
102 Jo Ng
102 Al Tso ZZ
102 Ed
102 Li Roth BB
You can try something like this.
SET SERVEROUTPUT ON;
DECLARE
TYPE etype IS TABLE OF VARCHAR2(100);
erec etype;
BEGIN
for rec IN ( SELECT first_name,last_name,location FROM Table1 )
LOOP
WITH fname
AS (SELECT LEVEL lvl,
REGEXP_SUBSTR(rec.first_name, '[^,]+', 1, LEVEL)First_name
FROM DUAL
CONNECT BY REGEXP_SUBSTR(rec.first_name, '[^,]+', 1, LEVEL) IS NOT NULL),
lname
AS (SELECT LEVEL lvl,
REGEXP_SUBSTR(rec.last_name, '[^,]+', 1, LEVEL)Last_Name
FROM DUAL
CONNECT BY REGEXP_SUBSTR(rec.last_name, '[^,]+', 1, LEVEL) IS NOT NULL),
loc
AS (SELECT LEVEL lvl,
REGEXP_SUBSTR(rec.location, '[^,]+', 1, LEVEL)Location
FROM DUAL
CONNECT BY REGEXP_SUBSTR(rec.location, '[^,]+', 1, LEVEL) IS NOT NULL)
SELECT first_name
||','
|| last_name
||','
|| location BULK COLLECT INTO erec
FROM fname fn
FULL OUTER join lname ln
ON fn.lvl = ln.lvl
FULL OUTER join loc lo
ON ln.lvl = lo.lvl;
FOR i IN 1..erec.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(erec(i));
END LOOP;
END LOOP;
END;
/

SQL - Splitting a single column into multiple rows

I have a record that looks like this in the database (As an example).
ID, Name, Brand
1, 'Bike', 'Schwinn'
2, 'Car', 'Ford, Honda, Chevy'
3, 'Bike', 'Schwinn, Trex'
4, 'Car', 'Honda'
I need to export the data out and create multiple records where Brand has multiple entries. I also need to increase the ID on output so I don't have duplicates. (I can use a sequence for this and would set it higher to my max value in db).
My output would look like
ID, Name, Brand
1, Bike, Schwinn
2, Car, Ford
Sequence.nextval, Car, Honda
Sequence.nextval, Car, Chevy
3, Bike, Schwinn
Sequence.nextval, Bike, Trex
4, Car, Honda
I would like to try and to this with a SQL statement. Basically I'm dumping this data as a csv file via straight SQL.
My difficulty is trying to loop/split through the Brand column.
You can use following select statement:
with test_tab (ID, Name, Brand) as (
select 1, 'Bike', 'Schwinn' from dual union all
select 2, 'Car', 'Ford, Honda, Chevy' from dual union all
select 3, 'Bike', 'Schwinn, Trex' from dual union all
select 4, 'Car', 'Honda' from dual)
--------------------
-- End of Data Preparation
--------------------
select case when level <> 1 then <your_sequece>.nextval else id end as id,
name,
trim(regexp_substr(Brand, '[^,]+', 1, level)) BRAND
from test_tab
connect by regexp_substr(Brand, '[^,]+', 1, level) is not null
and prior Brand = Brand
and prior sys_guid() is not null;
output would be:
ID NAME BRAND
---------------------
2 Car Ford
5 Car Honda
6 Car Chevy
4 Car Honda
1 Bike Schwinn
3 Bike Schwinn
7 Bike Trex
You can write Insert statement as
Insert into <destination_table>
select case when level <> 1 then <your_sequece>.nextval else id end as id,
name,
trim(regexp_substr(Brand, '[^,]+', 1, level)) BRAND
from <source_table>
connect by regexp_substr(Brand, '[^,]+', 1, level) is not null
and prior Brand = Brand
and prior sys_guid() is not null;
PS: If ID is unique, you can try replacing and prior Brand = Brand with and prior ID = ID to remove duplicate loop values.
select case when level <> 1 then <your_sequece>.nextval else id end as id,
name,
trim(regexp_substr(Brand, '[^,]+', 1, level)) BRAND
from <source_table>
connect BY regexp_substr(Brand, '[^,]+', 1, level) is not null
and prior ID = ID
and prior sys_guid() is not null;

sql nested query - group results by parent

I have the need to return tree like results from a single table. At the moment im using connect by and start with to ensure that all the correct results are returned.
select id,parent_id
from myTable
connect by prior id = parent_id start with name = 'manager'
group by id, parent_id
order by parent_id asc
However i want the results to return in tree structure. So each time a parent row is found its children rows will be displayed directly underneath it. Then move onto next parent and do the same
Expected results
- Parent A
- child a
- child b
- Parent B
- child c
- child d
Actual results
- Parent A
- Parent B
- child a
- child b
- child c
- child d
Is it possible to do this in oracle? My table uses a parent_id field to identify when a row has a parent. Every row also has a sort order, which is the order it should be sorted under its parent and a unique Id.
I'm using an Oracle DB
What you want is to use ORDER SIBLINGS BY. The query you have is ordering by the parent_id column which is overriding any hierarchical ordering.
The query below should do what you need it to do:
with my_hierarchy_data as (
select 1 as id, null as parent_id, 'Manager' as name from dual union all
select 2 as id, 1 as parent_id, 'parent 1' as name from dual union all
select 3 as id, 1 as parent_id, 'parent 2' as name from dual union all
select 4 as id, 2 as parent_id, 'child 1' as name from dual union all
select 5 as id, 2 as parent_id, 'child 2' as name from dual union all
select 6 as id, 3 as parent_id, 'child 3' as name from dual union all
select 7 as id, 3 as parent_id, 'child 4' as name from dual
)
select id, parent_id, lpad('- ', level * 2, ' ') || name as name
from my_hierarchy_data
connect by prior id = parent_id
start with name= 'Manager'
order siblings by parent_id asc
There is a special value level that can be used in Oracle hierarchical queries. It returns 1 for rows at the top level of the hierarchy, 2 for their children, and so on. You can use this to create indentation like this:
select lpad('- ',level*2,' ') || name
from myTable
connect by prior id = parent_id start with name = 'manager'
group by id, parent_id