Oracle while exists(select ...) insert into - sql

I am trying to insert id's into a temporary table with while loop.
TEMP_TABLE contains ID-s which = ID of entry in ID_TABLE.
ID_TABLE has two fields CON_1 and CON_2, which refer to another entry in ID_TABLE.
I want to add all CON_1 and CON_2 values from ID_TABLE into TEMP_TABLE where ID_TABLE.ID = TEMP_TABLE.ID and CON_1 or CON_2 are not already in TEMP_TABLE and then repeat the process until there are no IDs left to insert (after adding CON_1 or CON_2 to TEMP_TABLE, those ID-s might refer to ID_TABLE.ID where CON_1 or CON_2 are not already present in TEMP_TABLE).
Basically, an ID might have connections as another ID and I want to add ID, it's connections and the connections for the connections ... into TEMP_TABLE.
The query I made this far:
begin
while exists(select extId
from (
select distinct case
when con.CON_1 = idTable.ID
then con.CON_2
else con.CON_1
end
as extId
from ID_TABLE idTable
inner join TEMP_TABLE temp on idTable.ID = temp.ID
inner join CONNECTIONS_TABLE con on con.CON_2 = idTable.ID
or con.CON_1 = idTable.ID)
where not exists(select ID from TEMP_TABLE where ID = extId))
loop
insert into TEMP_TABLE
select extId
from (
select distinct case
when con.CON_1 = idTable.ID
then con.CON_2
else con.CON_1
end
as extId
from ID_TABLE idTable
inner join TEMP_TABLE temp on idTable.id = temp.ID
inner join CONNECTIONS_TABLE con on con.CON_2 = idTable.id
or con.CON_1 = idTable.ID)
where not exists(select ID from TEMP_TABLE where ID = extId);
end loop;
end;
When I run the query, I get this error:
PLS-00103: Encountered the symbol "INNER" when expecting one of the following:
) , with group having intersect minus order start union where
connect
Running on Oracle 12c

You appear to be trying to recursively add CON_1 or CON_2 values from the CONNECTIONS_TABLE that were connected to a prior ID value in both the TEMP_TABLE and the ID_TABLE.
You don't appear to need the WHILE loop (or even PL/SQL) and can use a single MERGE statement and a hierarchical query:
MERGE INTO temp_table DST
USING (
SELECT DISTINCT
extid
FROM (
select con_1, con_2
from connections_table c
START WITH EXISTS(
SELECT 1
FROM ID_TABLE i
inner join TEMP_TABLE t
on ( i.id = t.ID )
WHERE i.id IN ( c.con_1, c.con_2 )
)
CONNECT BY NOCYCLE
PRIOR con_1 IN ( con_1, con_2 )
OR PRIOR con_2 IN ( con_1, con_2 )
)
UNPIVOT (
extid FOR con IN ( con_1 AS 1, con_2 AS 2 )
)
) src
ON ( src.extID = dst.id )
WHEN NOT MATCHED THEN
INSERT ( id ) VALUES ( src.extid );
Which for the initial setup:
CREATE TABLE temp_table( id ) AS
SELECT 1 FROM DUAL;
CREATE TABLE connections_table( con_1, con_2 ) AS
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 3, 4 FROM DUAL UNION ALL
SELECT 5, 10 FROM DUAL;
CREATE TABLE id_table ( id ) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 10;
Would insert 3 rows and then:
SELECT * FROM temp_table;
Outputs:
ID
1
2
4
3
db<>fiddle here

Related

oracle subquery stored into a variable

I have 2 tables table1 and table2.
create table table1
(
ID integer,
reg_number varchar(9),
primary_number varchar(9),
act varchar(1)
);
create table table2
(
SECONDARY_NUMBER varchar(9),
table1_id integer
);
I need to be able to grab all the rows from table1 that is associated with table2 so after an insert or update on column act from table1 to insert those rows into table3 table.
I have the query but I'm unsure how to store that query into a variable so I can call it into from a trigger.
The query
select * from table1 where id in (
with children as (
select id pid, primary_number pan from table1
where id = 20407 -- current id
and primary_number is not null -- child ids only
),
prime as (
select id pid from table1 p
inner join children c on c.pan = p.reg_number),
sibs as (
select secondary_number sec from table2 c
inner join prime on prime.pid = c.person_id ),
sibids as (
select id pid from table1 p
inner join sibs s on s.sec = p.reg_number)
select pid from children
union
select pid from prime
union
select pid from sibids
);
Thank you

SQL Pivoting in Teradata

I need to convert the rows into multiple output columns.
I've 2 tables that I need to combine and produce the third table which is kept here.
CREATE MULTISET TABLE TAG
( TAG VARCHAR(100),
ID VARCHAR(100)
) PRIMARY INDEX (TAG,ID);
INSERT INTO TAG VALUES('L2250','I14299');
INSERT INTO TAG VALUES('L2250','I14300');
INSERT INTO TAG VALUES('L2250','I14301');
CREATE MULTISET TABLE IDS
( ID VARCHAR(100),
TYPE VARCHAR(100),
DESCR VARCHAR(100)
) ;
INSERT INTO IDS VALUES ('I14299','Exposure','Bills');
INSERT INTO IDS VALUES ('I14299','Exposure','Certificates');
INSERT INTO IDS VALUES ('I14299','Exposure','NCDS');
INSERT INTO IDS VALUES ('I14300','Currency','GB');
INSERT INTO IDS VALUES ('I14300','Currency','AU');
INSERT INTO IDS VALUES ('I14301','Rate','NOT FIXED');
INSERT INTO IDS VALUES ('I14301','Rate','FIXED');
That's neither Pivot nor Unpivot, you want to create all possible combinations of the different types:
SELECT *
FROM
( SELECT t.tag, i.descr AS I14299
FROM tag AS t JOIN ids AS i
ON t.id = i.id
WHERE i.id = 'I14299'
) AS e
CROSS JOIN
( SELECT descr AS I14300
FROM ids
WHERE id = 'I14300'
) AS c
CROSS JOIN
( SELECT descr AS I14301
FROM ids
WHERE id = 'I14301'
) AS r
If you need to do this for different tags you can switch to:
SELECT e.*, c.descr, r.descr
FROM
( SELECT t.tag, i.descr AS I14299
FROM tag AS t JOIN ids AS i
ON t.id = i.id
WHERE t.id = 'I14299'
) AS e
JOIN
( SELECT t.tag, descr AS I14300
FROM tag AS t JOIN ids AS i
ON t.id = i.id
WHERE t.id = 'I14300'
) AS c
ON e.tag = c.tag
JOIN
( SELECT t.tag, descr AS I14301
FROM tag AS t JOIN ids AS i
ON t.id = i.id
WHERE t.id = 'I14301'
) AS r
ON e.tag = r.tag
Of course this hard-codes the ids, if you want them dynamically created you need Dynamic SQL in a Stored Procedure.

SQL Sever parent, child, child relationships within table

I have the following table of items with contain a list of name with map back to a parent field within the table -
id nameVal parentId
1 A NULL
2 B NULL
3 C NULL
4 D NULL
5 E NULL
6 A1 1
7 A2 6
8 A3 1
9 A4 7
10 B1 2
11 B2 2
it can be more that one step away from the parent record - A, A1, A4 are all related etc. as below...
A1 => A
A2 => A1 => A
A3 => A
A4 => A2 => A1 => A
So what I'm trying to do is pull through all records where a relationship exists, ie, A4 would bring back all the A's as there is a link to the original A records.
Is this possible?
You can do this with a recursive CTE. The key is to get the parent. Here is one method:
with cte as (
select id, nameval, id as orig
from t
where parentid is null
union all
select t.nameval, cte.orig
from cte join
t
on t.parentid = cte.id
)
select cte.*
from cte
where cte.orig = (select cte2.orig from cte cte2 where ct2.nameval = 'A4');
Perhaps a little more than necessary, but consider the following:
You can set the top node (null will default to the entire hierachy)
You also can set a filter. This can be empty for no filter, a single ID, or a delimited string of IDs.
Declare #T table (id int,nameVal varchar(50),parentId int)
Insert into #T values
(1 ,'A', NULL),
(2 ,'B', NULL),
(3 ,'C', NULL),
(4 ,'D', NULL),
(5 ,'E', NULL),
(6 ,'A1', 1),
(7 ,'A2', 6),
(8 ,'A3', 1),
(9 ,'A4', 7),
(10 ,'B1', 2),
(11 ,'B2', 2)
Declare #Top int = null --<< Sets top of Hier Try 6
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
Declare #Filter varchar(25) = '7' --<< Empty for All or try '7,10'
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by nameVal) as varchar(500))
,ID
,parentId
,Lvl=1
,nameVal
From #T
Where IsNull(#Top,-1) = case when #Top is null then isnull(parentId,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.nameVal)) as varchar(500))
,r.ID
,r.parentId
,p.Lvl+1
,r.nameVal
From #T r
Join cteP p on r.parentId = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
,cte as (
Select A.R1
,B.R2
,A.ID
,A.parentId
,A.Lvl
,nameVal = Replicate(#Nest,A.Lvl-1) + A.nameVal
From cteR1 A
Join cteR2 B on A.ID=B.ID
)
Select Distinct A.*
From cte A
Join (
Select A.R1,A.R2
From cte A
Join (Select R1 from cte Where IIF(#Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',#Filter+','))>0) B
on B.R1 between A.R1 and A.R2
) B on A.R1 between B.R1 and B.R2
Order By A.R1
Returns
Now, if you set #Filter = '7,10', you'll get
If you set #Filter = '', you'll get
You can use a recursive query to fetch all of the related rows. Whether the data will be in the form you want, I don't know, as the question seems a bit unclear about that. But for example
Fetch a record and all descendants:
with r as (
select *
from my_table t
where id = 7
union all
select t1.*
from my_table t1
inner join r
on t1.parent_id = r.id
)
select * from r;
Fetch a record and all ancestors:
with r as (
select *
from my_table t
where id = 7
union all
select t1.*
from my_table t1
inner join r
on t1.id = r.parent_id
)
select * from r;
Now maybe you want both children and ancestors. This can get a little trickier; recursion works best in a straight line so there can be no infinite loops. One way is to union together the two above queries. If your real query has complex logic that you don't want to write twice, then you could use this to get a list of ID's and then run the real query over a select ... where id in (my_list) type query.
Another consideration is whether a record can have multiple children. If we have
A
A1 => A
A10 => A1
A11 => A1
A2 => A
A20 => A2
A21 => A2
you could say these are all related (through A; some are "cousins"). So if you search from A1 and union the first two example queries, you'd get A, A1, A10, A11... but would you also want the other children of A? If so you could take a slightly different approach:
First, find the eldest ancestor:
with r as (
select *
from my_table t
where id = 7
union all
select t1.*
from my_table t1
inner join r
on t1.id = r.parent_id
)
select id from r where parent_id is null;
Then run the original "all descendants" query against that ID. If you want to get it all into a single statement, the following *should * work I think (but I'm not where I can test it):
with ancestors as (
select *
from my_table t
where id = 7
union all
select t1.*
from my_table t1
inner join ancestors
on t1.id = ancestors.parent_id
) , related as (
select *
from ancestors
where parent_id is null
union all
select t1.*
from my_table t1
inner join related
on t1.parent_id = related.id
)
select * from related;

SQL - Is it possible to join a table to a resultset created by several select/union-alls?

I'm trying to find a workaround (hack) to some limitations preventing me from using a temporary table or a table variable in my SQL query.
I have a real table (technically it's a derived table that results from an UNPIVOT of a poorly designed table) which lacks several necessary fields. I need to hardcode these fields into the result until we can cleanup the database issue.
Given a table like:
tblEntity
ID | Name
1 | One
2 | Two
I need to join several fields such as:
ID | Order
1 | 2
2 | 1
The join would result in:
ID | Name | Order
1 | One | 2
2 | Two | 1
My question is: can I join tblEntity to a resultset created like:
SELECT 1, 2
UNION ALL
SELECT 2, 1
Is it possible to join? If so, what is the syntax?
select te.*, t.Ord from tblEntity te
inner join (
SELECT 1 as Id, 2 as Ord
UNION ALL
SELECT 2, 1
) t on te.ID = t.Id
Making a few assumptions, this would do it:
SELECT en.ID, en.Name, xx.OrderBy
from tblEntity en
inner join (select 1 Id, 2 OrderBy
union all
select 2,1) xx
on xx.Id = en.ID
In SQL-Server 2008, it's also possible to use Table Value Constructors:
CREATE TABLE #tblEntity
( ID INT
, Name CHAR(10)
) ;
INSERT INTO #tblEntity
(ID, Name)
VALUES
( 1, 'One' ) ,
( 2, 'Two' ) ;
SELECT
t.ID, t.Name, o.Ordr AS "Order"
FROM
#tblEntity AS t
JOIN
( VALUES
(1,2)
, (2,1)
) AS o(ID, Ordr)
ON o.ID = t.ID ;
You can test the above in: data.stackexchange.com
Many ways of doing this e.g.
WITH T1 (Id, "Order")
AS
(
SELECT 1, 2
UNION ALL
SELECT 2, 1
)
SELECT e.*, T1."Order"
FROM tblEntity e
JOIN T1
ON e.Id = T1.Id;
e.g. 2
SELECT e.*, T1."Order"
FROM tblEntity e
JOIN (
VALUES (1, 2),
(2, 1)
) AS T1 (Id, "Order")
ON e.Id = T1.Id;
e.g. 3
WITH T1
AS
(
SELECT *
FROM (
VALUES (1, 2),
(2, 1)
) AS T (Id, "Order")
)
SELECT e.*, T1."Order"
FROM tblEntity e
JOIN T1
ON e.Id = T1.Id;
...and so on.

SQL: Select lowest value that doesn't already exist

In TableA I have an int column.
Is it possible using only a select statement to select the minimum value in the column that DOES NOT EXIST and is greater then 0?
For example, if the col has the values 1,2,9 the select statement will return 3.
If the col has 9,10,11 it will return 1.
I can achieve this using a temp table or using a loop, but I'm wondering if I can do it using just a select statement?
SELECT MIN(t1.ID+1) as 'MinID'
FROM table t1 LEFT JOIN table t2
On t1.ID+1=t2.ID
Where t2.OtherField IS NULL
select
min(nt.id)
from numbertable nt
left outer join originaldata od
on nt.id=od.id
where od.id is null
have a number table that goes from 1 to your max value (or higher)
SELECT DISTINCT x + 1 "val"
EXCEPT SELECT DISTINCT x "val"
ORDER BY "val" ASC
LIMIT 1
What about this?
SELECT Min(id)
FROM (SELECT 1 id
FROM tablea
WHERE 1 NOT IN (SELECT id
FROM tablea)
UNION
SELECT id + 1 id
FROM tablea
WHERE id + 1 NOT IN (SELECT id
FROM tablea)) AS min_ids;
try this:(Updated)
declare #dummy varchar(10) ;
set #dummy =(select top(1) id from dbo.b)
if( #dummy= '1')
begin
select top(1)l.id + 1 as start
from dbo.b as l
left outer join dbo.b as r on l.id + 1 = r.id
where r.id is null
end
else
begin
select '1'
end
Give this a try:
declare #TestTable table (
col int
)
/* Test Case 1: 1,2,9 */
insert into #TestTable
(col)
select 1 union all select 2 union all select 9
SELECT MinValue = (SELECT ISNULL(MAX(t2.col),0)+1
FROM #TestTable t2
WHERE t2.col < t1.col)
FROM #TestTable t1
WHERE t1.col - 1 NOT IN (SELECT col FROM #TestTable)
AND t1.col - 1 > 0
delete from #TestTable
/* Test Case 2: 9,10,11 */
insert into #TestTable
(col)
select 9 union all select 10 union all select 11
SELECT MinValue = (SELECT ISNULL(MAX(t2.col),0)+1
FROM #TestTable t2
WHERE t2.col < t1.col)
FROM #TestTable t1
WHERE t1.col - 1 NOT IN (SELECT col FROM #TestTable)
AND t1.col - 1 > 0
I duplicated my answer from here:
SELECT MIN(a.id) + 1 AS firstfree
FROM (SELECT id FROM table UNION SELECT 0) a
LEFT JOIN table b ON b.id = a.id + 1
WHERE b.id IS NULL
This handles all cases I can think of - including no existing records at all.
The only thing I don't like about this solution is that additional conditions have to be included twice, like that:
SELECT MIN(a.id) + 1 AS firstfree
FROM (SELECT id FROM table WHERE column = 4711 UNION SELECT 0) a
LEFT JOIN table b ON b.column = 4711 AND b.id = a.id + 1
WHERE b.id IS NULL
Please also notice the comments about locking and concurrency - the requirement to fill gaps is in most cases bad design and can cause problems. However, I had a good reason to do it: the IDs are to be printed and typed by humans and we don't want to have IDs with many digits after some time, while all the low ones are free...