connect by prior giving incorrect results - oracle

Assume I have 2 tables as shown below – PARENT and CHILD
create table parent
(
parent_id number,
parent_name varchar2(10),
cnt number
)
create table children
(
child_id number,
parent_id number,
name varchar2(10)
)
insert into parent values (1, 'A', 3);
insert into parent values (2, 'B', 2);
insert into children values (1, 1, 'AB');
insert into children values (1, 2, 'AC');
insert into children values (1, 3, 'AD');
insert into children values (2, 1, 'BA');
insert into children values (2, 2, 'BB');
The output has to be something like below:
Parent_ID Parent_Name Cnt Child_Names
1 A 3 AB, AC, AD
2 B 2 BA, BB
I have written the below query to achieve this. I don't know where it is going wrong, query seems to be fine but the output is not what is desired.
Please help me out as I am almost saturated debugging this.
select parent_id, parent_name, substr(max(sys_connect_by_path(child_name, ',')),2)
from
( select p.parent_id, p.parent_name, ch.child_name, row_number() over (partition by p.parent_id, p.parent_name order by ch.child_name) rn
from parent p, children ch
where p.parent_id = ch.parent_id
)
start with rn =1
connect by prior rn+1 = rn
group by parent_id, parent_name

You don't see hierartical data. It is simple master detail.
select p.parent_id, p.parent_name, p.cnt, c.children_names, c.real_count
from parent p left outer join
(select parent_id, listagg(name, ', ') within group (order by name) children_names,
count(*) real_count from children group by parent_id) c
on p.parent_id = c.parent_id
'AD' is not connected because it parent_id is 3.
Hm... listagg is a 11g feature.
The general solution before 11g was to use Tom Kyte's aproach:
http://www.sqlsnippets.com/en/topic-11591.html
And have a look at http://www.oracle-developer.net/display.php?id=515 for performance expectations
Ok, let's continue with "connect by" aproach.
First of all, lets reduce complexity by forgetting about parent table. We will be building right side of left outer join query above, but using connect by construction.
Second, I think you missed how parent_id and child_id are named. My guess is that your child_id is actually your parent_id in parent table. So name is wrong.
3 conditions to put all children in a row:
All children have the same child_id (parent.parent_id)
first one has parent_id = 1 (more like order_id)
every next one has parent_id +1 comparing to previous one
Let's put it all into query:
select c.*, level from children c
start with parent_id = 1 --2nd condition
connect by prior
c.parent_id = c.parent_id -1 --3rd condition
and prior child_id = child_id --1st condition
order by child_id, parent_id
Then you add your SYS_CONNECT_BY_PATH(name, ', '), get the last element and only then join it with parent table, but based on parent.parent_id = children.child_id

here is your query after correction, actually you did some mistake in column name of children table(wrote ch.child_name while it is ch.name or name)
select parent_id, parent_name,cnt,substr(max(sys_connect_by_path(child_name, ',')),2) child_name
from
( select p.parent_id, p.parent_name, p.cnt , ch.name child_name, row_number() over (partition by p.parent_id, p.parent_name order by ch.name) rn
from parent p, children ch
where p.parent_id = ch.parent_id
)
start with rn =1
connect by prior rn+1 = rn
group by parent_name,cnt,parent_id

Related

How can I know the last child of a level according to a path index in a list

I have a nested table with many levels of parent and child elements. It returns me the levels of each record and the path index as follow:
How can I make other field that says if the row is the last row child of the path_index parent? I've included here a manual example from sql here, with my current query: https://dbfiddle.uk/?rdbms=sqlite_3.27&fiddle=3963054e14723bd7dae146ab82ae936c
Basically is something like that: inside path_index 1.1 has 1.1.1, 1.1.2 and 1.1.3, the row with 1.1.3 will be the last row of parent 1.1, but inside 1.1.2 has 1.1.2.1, then 1.1.2.1 is true because is the last child of 1.1.2, then it receives true as last row parent, a complete example follows:
How can I do an algorithm for that? It can be done with SQL or with Java/Kotlin.
You can use the column rn from cte to check with window function MAX() if it is the last child of each parent:
WITH
levels AS (
SELECT *, 0 lvl FROM items
UNION ALL
SELECT i.*, l.lvl + 1
FROM items i INNER JOIN levels l
ON l.id = i.parentId
),
row_numbers AS (
SELECT id, item_name, parentId, MAX(lvl) lvl,
ROW_NUMBER() OVER (PARTITION BY parentId, lvl ORDER BY id) rn
FROM levels
GROUP BY id, item_name, parentId
),
cte AS (
SELECT id, item_name, parentId, lvl, rn, rn || '' path_index
FROM row_numbers
UNION ALL
SELECT r.id, r.item_name, r.parentId, r.lvl, r.rn,
c.path_index || '.' || r.rn
FROM row_numbers r INNER JOIN cte c
ON r.parentId = c.id
)
SELECT id, item_name, parentId, lvl, path_index,
rn = MAX(rn) OVER (PARTITION BY parentId) lastRowFromParent
FROM cte
GROUP BY id
HAVING MAX(LENGTH(path_index))
ORDER BY path_index + 0, path_index
See the demo.

ORDER BY BASED ON COLUMN

I have two tables,PRODUCTS AND LOOKUP TABLES.Now i want to order the KEY Column in products table based on KEY column value in LOOKUP TABLE.
CREATE TABLE PRODUCTS
(
ID INT,
KEY VARCHAR(50)
)
INSERT INTO PRODUCTS
VALUES (1, 'EGHS'), (2, 'PFE'), (3, 'EGHS'),
(4, 'PFE'), (5, 'ABC')
CREATE TABLE LOOKUP (F_KEY VARCHAR(50))
INSERT INTO LOOKUP VALUES('PFE,EGHS,ABC')
Now I want to order the records in PRODUCTS table based on KEY (PFE,EGHS,ABC) values in LOOKUP table.
Example output:
PRODUCTS
ID F_KEY
-----------
2 PFE
4 PFE
1 EGHS
3 EGHS
5 ABC
I use this query, but it is not working
SELECT *
FROM PRODUCTS
ORDER BY (SELECT F_KEY FROM LOOKUP)
You can split the string using XML. You first need to convert the string to XML and replace the comma with start and end XML tags.
Once done, you can assign an incrementing number using ROW_NUMBER() like following.
;WITH cte
AS (SELECT dt,
Row_number()
OVER(
ORDER BY (SELECT 1)) RN
FROM (SELECT Cast('<X>' + Replace(F.f_key, ',', '</X><X>')
+ '</X>' AS XML) AS xmlfilter
FROM [lookup] F)F1
CROSS apply (SELECT fdata.d.value('.', 'varchar(500)') AS DT
FROM f1.xmlfilter.nodes('X') AS fdata(d)) O)
SELECT P.*
FROM products P
LEFT JOIN cte C
ON C.dt = P.[key]
ORDER BY C.rn
Online Demo
Output:
ID F_KEY
-----------
2 PFE
4 PFE
1 EGHS
3 EGHS
5 ABC
You may do it like this:
SELECT ID, [KEY] FROM PRODUCTS
ORDER BY
CASE [KEY]
WHEN 'PFE' THEN 1
WHEN 'EGHS' THEN 2
WHEN 'ABC' THEN 3
END

Insert data into one table from another table avoiding duplicates

I've got a table as follows
Table1
ID Name Tag
-----------------
1 N1 2.1
2 N2 3.5
3 N1 3.5
4 N3 8.1
I create a new table Table2 with ID and Name (unique constraint) and I want to insert Table1's contents into Table2 avoiding duplicates, in the sense that I want only 1, 2 and 4 from Table1 in Table2.
I've tried this but it doesn't seem to work and I get the unique constraint error (ORACLE SQL)
INSERT INTO TABLE2 (ID, NAME)
SELECT ID, NAME
FROM TABLE1
WHERE NAME NOT IN (SELECT NAME FROM TABLE2);
Please can someone point me in the right direction?
Sorry for not making myself clear. Table2 is a brand new table. I want the first values inserted, the following duplicates should be ignored. So in my case, N1, N2 get inserted, N1 is dupe so it is ignored, N3 is inserted
OK - from your description, I understand table t2 is currently empty, and you want to copy the rows where id is in (1, 2, 4) from table t1 to table t2.
Why your code fails:
You seem to believe that the condition is applied to the first row in t1, it passes so it is inserted into t2, then the condition is applied to the second row in t1 (using what is already inserted in t2), etc. - and you don't understand why there is any attempt to insert ALL the rows from t1 into t2. Why doesn't the third row fail the WHERE clause?
Good question! The reason is that operations are done on a SET basis. The WHERE condition uses table t2 AS IT WAS before the INSERT operation began. So for ALL rows, the WHERE clause compares to an empty table t2.
How to fix this... Decide which id you want to add when there are duplicate names. For example, one way to get the result you said you wanted is to select MIN(id) for each name. Moreover, you still want to check if the name exists in t2 already (since you may do this again in the future, when t2 is already partially populated).
insert into t2 ( id, name )
select min(id), name
from t1
where name not in (select name from t2)
group by name
;
You can try it bother....!
Insert into tb2(Field1, Field2)
SELECT Field1, Field2
FROM tb1
WHERE NOT EXISTS (SELECT Field1 FROM tb1) ;
This is how I understood the question:
SQL> create table table2
2 (id number,
3 name varchar2(2),
4 tag number,
5 constraint pk_t2 primary key (id, name)
6 );
Table created.
SQL>
SQL> insert into table2 (id, name, tag)
2 with test (id, name, tag) as
3 (select 1, 'N1', 2.1 from dual union
4 select 2, 'N2', 3.5 from dual union
5 select 3, 'N1', 3.5 from dual union
6 select 4, 'N3', 8.1 from dual
7 )
8 select min(id), name, max(tag)
9 from test
10 group by name;
3 rows created.
SQL>
SQL> select * from table2 order by id;
ID NA TAG
---------- -- ----------
1 N1 3,5
2 N2 3,5
4 N3 8,1
SQL>
When we need to unique any two or more column we have to create unique index.
Run this query
ALTER TABLE TABLE2 ADD UNIQUE unique_index( id, name);
and then
INSERT INTO TABLE2 (id,name,tag) VALUES(1, "N1", 3.5 )
ON DUPLICATE KEY UPDATE tag=3.5
this will also help to update new tag
Try to check if the id and name from Table1 is doesn't exist in Table2, if then insert.
If the unique constraint on TABLE2 is a composite key then run this:
INSERT INTO TABLE2 (ID, NAME)
SELECT A.ID, A.NAME
FROM TABLE1 A
WHERE NOT EXISTS (SELECT NULL FROM TABLE2 B WHERE A.ID=B.ID AND A.NAME=B.NAME);
If there are two unique constraints; one on the id, and the other on the name then run this instead:
INSERT INTO TABLE2 (ID, NAME)
SELECT A.ID, A.NAME
FROM TABLE1 A
WHERE NOT EXISTS (SELECT NULL FROM TABLE2 B WHERE A.ID=B.ID OR A.NAME=B.NAME);
ORACLE, in case you need to get values from 2 different tables.
below example,i use an increment case.
INSERT INTO TABLE1
(INDEX, REMARKS, NAME, AGE)
(SELECT (SELECT colescs(MAX(INDEX),0) FROM TABLE1)+1,
'any remarks',
t2.NAME, t2,age from TABLE2 t2 where t2.name = 'apple')
explanation
match below numbers (1)-(1), (2)-(2) ...
INSERT INTO TABLE1
(INDEX, //index increment (1)
REMARKS, //hard code (2)
NAME, //from table2 (3)
AGE) //from table2 (4)
(SELECT // this part is to get values from another table
(SELECT colescs(MAX(INDEX),0) FROM TABLE1)+1, //increment (1)
'any remarks', //hard code value (2)
t2.NAME, //from table2 (3)
t2,age //from table2 (4)
from TABLE2 t2 where t2.name = 'apple') //condition for table2

How to reduce join operation to a single row in Oracle?

This example is invented for the purpose of the question.
SELECT
PR.PROVINCE_NAME
,CO.COUNTRY_NAME
FROM
PROVINCE PR
JOIN COUNTRY CO ON CO.COUNTRY_ID=PR.COUNTRY_ID
WHERE
PR.PROVINCE_ID IN (1,2)
Let's assume that COUNTRY_ID is not the Primary Key in the Country table and the above join on Country table returns potentially multiple rows. We don't know how many rows and we don't care why there are multiple ones. We only want to join on one of them, so we get one row per Province.
I tried subquery for the join but can't pass in PR.COUNTRY_ID for Oracle 11.2. Are there any other ways that this can be achieved?
A typical safe approach of handling tables without PK is to extend the duplicated column with a unique index (row_numer of the duplicated row)
In your case this would be:
with COUNTRY_UNIQUE as (
select COUNTRY_ID,
row_number() over (partition by COUNTRY_ID order by COUNTRY_NAME) rn,
COUNTRY_NAME
from country)
select * from COUNTRY_UNIQUE
order by COUNTRY_ID, rn;
leading to
COUNTRY_ID RN COUNTRY_NAME
---------- ---------- ------------
1 1 C1
2 1 C2
2 2 C3
The combination of COUNTRY_IDand RN is unique, so if you constraint only RN = 1 the COUNTRY_ID is unique.
You may define the order of the duplicated records and control with it the selection - in our case we choose the smalest COUNTRY_NAME.
The whole join used this subquery and constraints the countries on RN = 1
with COUNTRY_UNIQUE as (
select COUNTRY_ID,
row_number() over (partition by COUNTRY_ID order by COUNTRY_NAME) rn,
COUNTRY_NAME
from country)
SELECT
PR.PROVINCE_NAME
,CO.COUNTRY_NAME
FROM
PROVINCE PR
JOIN COUNTRY_UNIQUE CO ON CO.COUNTRY_ID=PR.COUNTRY_ID
WHERE
PR.PROVINCE_ID IN (1,2)
AND CO.RN = 1; /* consider only unique countries */
If you have Oracle 12c, you can use a LATERAL view in the join. Like this:
SELECT
PR.PROVINCE_NAME
,CO.COUNTRY_NAME
FROM
PROVINCE PR
CROSS JOIN LATERAL (
SELECT * FROM COUNTRY CO
WHERE CO.COUNTRY_ID=PR.COUNTRY_ID
FETCH FIRST 1 ROWS ONLY) CO
WHERE
PR.PROVINCE_ID IN (1,2)
Update for Oracle 11.2
In Oracle 11.2, you can use something along these lines. Depending on the size of COUNTRY and how many duplicates there are per COUNTRY_ID, it could perform as well or better than the 12c approach. (Fewer buffer gets but more memory required).
SELECT pr.province_name,
co.country_name
FROM province pr
INNER JOIN (SELECT *
FROM (SELECT co.*,
ROW_NUMBER () OVER (PARTITION BY co.country_id ORDER BY co.country_name) rn
FROM country co)
WHERE rn = 1) co
ON co.country_id = pr.country_id
WHERE pr.province_id IN (1, 2)

coalesce with subquery in a where clause in Oracle

Hy,
I have the next sql query in Oracle:
Imagine I have a table "items" with "id" and "name" fields and other table "prices_items" which have three fields named "id, itemId, category". The category may have three values: "1,2,3". So the query I need to do is get the price of an item from the table "prices_items" but the item can have until three prices because of the category field. So, in priotiry order I need to get the price of an item which has category 1, if the item doesnt have this category I have to find the price for category 2 and so on.
from items
left join prices_items on prices_items.itemId = items.itemId
where prices_items.id = coalesce(select id
from prices_items
where itemId= items.itemId and category=1,
select id
from prices_items
where itemId= items.itemId and category=2,
select id
from prices_items
where itemId= items.itemId and category=3)
The query I am using is like this but I dont know how its working because coalesce is being executed on each join?. How is this being executed?
Thanks
The coalesce() is going to keep the first prices_items.id found in order of the categories listed. Instead of individual subqueries you could write it this way and it will probably give a better plan.
select ...
from items inner join prices_items on prices_items.itemId = items.itemId
where prices_items.category = (
select min(pi2.category) from prices_items pi2
where pi2.itemId = items.itemId
);
If the priority of categories doesn't happen to follow an ascending sequence you could handle it with a case expression:
select ...
from items inner join prices_items on prices_items.itemId = items.itemId
where
case prices_items.category
when 2 then 1
when 3 then 2
when 1 then 3
end = (
select
min(case pi2.category
when 2 then 1
when 3 then 2
when 1 then 3
end)
from prices_items pi2
where pi2.itemId = items.itemId
);
As far as how your current query is actually running it may or may not be materializing all the subquery results. From an end results perspective all you really need to know is that only the first non-null value from the coalesce() arguments is the one kept. The reality is that it is probably more efficient to re-write the query so you don't need them.
There are other ways to write this. The one that's most common these days seems to be the row_number() approach:
with data as (
select *,
row_number() over (partition by pi.itemId order by pi.category) as rn
from items inner join prices_items pi on pi.itemId = items.itemId
)
select ...
from data
where rn = 1;
Here's another Oracle-specific solution:
select *
from
items inner join
(
select itemId, min(price) keep (dense_rank first order by category) as price
from prices_items
group by itemId
) pi on pi.itemId = items.itemId;
Oracle Setup:
CREATE TABLE items (
itemid NUMBER PRIMARY KEY,
name VARCHAR2(20)
);
CREATE TABLE prices_items (
itemId NUMBER REFERENCES items ( itemid ),
category INT,
price NUMBER,
CHECK ( category IN ( 1, 2, 3 ) ),
PRIMARY KEY ( itemid, category )
);
INSERT INTO items
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'B' FROM DUAL UNION ALL
SELECT 3, 'C' FROM DUAL;
INSERT INTO prices_items
SELECT 1, 1, 32.5 FROM DUAL UNION ALL
SELECT 1, 2, 23.9 FROM DUAL UNION ALL
SELECT 1, 3, 19.99 FROM DUAL UNION ALL
SELECT 2, 1, 42.42 FROM DUAL UNION ALL
SELECT 2, 3, 99.99 FROM DUAL UNION ALL
SELECT 3, 2, 0.02 FROM DUAL UNION ALL
SELECT 3, 3, 10 FROM DUAL;
Query:
SELECT i.itemid,
name,
category,
price
FROM items i
INNER JOIN
( SELECT itemid,
MIN( category ) AS category,
MAX( price ) KEEP ( DENSE_RANK FIRST ORDER BY category ) AS price
FROM prices_items
GROUP BY itemid
) p
ON ( i.itemid = p.itemid );
Output:
ID NAME CATEGORY PRICE
-- ---- -------- -----
1 A 1 32.50
2 B 1 42.42
3 C 2 0.02

Resources