How to write recursive union alls in PSQL? - sql

Let's say I have a table of pairwise-related numbers in a table related_entities like this:
| column_a | column_b |
| -------- | -------- |
| 100000 | 100001 |
| 100001 | 100002 |
| 100002 | 100003 |
...
| 100099 | 100100 |
| 100100 | 100101 |
How does one write a query that generates a table like this:
| column_a | column_b |
| -------- | -------- |
| 100000 | 100001 |
| 100000 | 100002 |
| 100000 | 100003 |
...
| 100000 | 100100 |
| 100000 | 100101 |
Further explanation: 100000 is related to 100101 (across 100 nodes). The number of maximum related nodes is unknown. Practical need — we have a table that relates 2 people to one another. There are an unknown number of total relationships.
For a table with a maximum of 3 (A-B and B-C) related nodes, it's pretty easy:
select *
from related_entities
union all
select r1.column_a, r2.column_b
from related_entities r1
join related_entities r2 on r2.column_a = r1.column_b
;
I just can't figure out how to write this in an efficient manner where maximum nodes are unknown.

What you are doing is exactly the right method to get started with recursive queries when one is not used to them.
From your query, all you have to do now is add a CTE and use it instead of related_entities r1. You have built the 1st step of your recursion; doing so will cause it to execute in a loop.
WITH RECURSIVE MyRecursiveCTE AS (
select *
from related_entities
/* Add WHERE column_a = ... here if needed */
union all
select r1.column_a, r2.column_b
from MyRecursiveCTE r1
join related_entities r2 on r2.column_a = r1.column_b
)
SELECT * FROM MyRecursiveCTE
Of course, you can also do r2.column_b = r1.column_a to browse the hierarchy from the bottom up instead of from the top down.
To the above, I could add you currently seem to have no protection against loops. You may want to add a column as to protect yourself about infinite recursion.
WITH RECURSIVE MyRecursiveCTE AS (
select 1 AS level, *
from related_entities
union all
select 1+r1.level, r1.column_a, r2.column_b
from MyRecursiveCTE r1
join related_entities r2 on r2.column_a = r1.column_b AND level < 50
)
SELECT * FROM MyRecursiveCTE

Related

How to duplicate and merge rows through select query

I've an existing Postgresql select query output that gives me,
+------+------------+------+------+
| Type | ID | Pass | Fail |
+------+------------+------+------+
| IQC | ABC_IQC_R2 | 0 | 6 |
+------+------------+------+------+
| IQC | ABC_IQC_R1 | 2 | 6 |
+------+------------+------+------+
| IQC | ABC_IQC | 498 | 8 |
+------+------------+------+------+
How do I duplicate the row of ID-> ABC_IQC into two while merging both R1 & R2 values into that row? (As shown below)
+------+---------+------------+------+------+--------+--------+
| Type | ID | R_ID | Pass | Fail | R_Pass | R_Fail |
+------+---------+------------+------+------+--------+--------+
| IQC | ABC_IQC | ABC_IQC_R2 | 498 | 8 | 0 | 6 |
+------+---------+------------+------+------+--------+--------+
| IQC | ABC_IQC | ABC_IQC_R1 | 498 | 8 | 2 | 6 |
+------+---------+------------+------+------+--------+--------+
The two logics I can think of is,
Run through the ID to search for ABC (But I'm unsure of how to match them). Duplicate the row ABC_IQC & then merge them using Lateral Join (Still unsure how)
Duplicate a column for ABC_IQC(ID column) from both R2 & R1 (now becoming R_ID). Search ID for the original ABC_IQC row and extract the value of pass and fail into both R2 & R1 row.
Here is my current query to get the initial query output,
SELECT
split_part(NewLotID, '_', 2) AS "Type",
LotSummary ->> 'ID' AS "ID",
LotSummary ->> 'Pass' AS "Pass",
LotSummary ->> 'Fail' AS "Fail"
FROM
(
SELECT
LotSummary,
regexp_replace(LotSummary ->> 'ID','[- ]','_','g') AS NewLotID
.
.
.
I'm not expecting a full answer because I've hardly provided any code, just any ideas or insights that might be helpful! Thank you in advance.
I think you want join:
with q as (
<your query here>
)
select q.type, q.id, qr.id as r_id, q.pass, q.fail,
qr.pass as r_pass, qr.fail as r_fail
from q join
q qr
on q.id = 'ABC_IQC' and qr.id like 'ABC_IQC_%';
You can actually generalize this:
with q as (
<your query here>
)
select q.type, q.id, qr.id as r_id, q.pass, q.fail,
qr.pass as r_pass, qr.fail as r_fail
from q join
q qr
on q.id ~ '^[^_]+_[^_]+$' and
qr.id like q.id || '_%';

How to fetch records from DB which fulfill a certain criteria

I have the following problem and wanted to ask if this is the correct way to do it or if there is a better way of doing it:
Assume I have the following table/data in my DB:
|---|----|------|-------------|---------|---------|
|id |city|street|street_number|lastname |firstname|
|---|----|------|-------------|---------|---------|
| 1 | ar | K1 | 13 |Davenport| Hector |
| 2 | ar | L1 | 27 |Cannon | Teresa |
| 3 | ar | A1 | 135 |Brewer | Izaac |
| 4 | dc | A2 | 8 |Fowler | Milan |
| 5 | fr | C1 | 18 |Kaiser | Ibrar |
| 6 | fr | C1 | 28 |Weaver | Kiri |
| 7 | ny | O1 | 37 |Petersen | Derrick |
I now get some some requests of the following structures: (city/street/street_number)
E.g.: {(ar,K1,13),(dc,A2,8),(ny,01,37)}
I want to retrieve the last name of the person living there. Since the request amount is quite large I don't want to run over all the request one-by-one. My current implementation is to insert the data into a temporary table and join the values.
Is this the right approach or is there some better way of doing this?
You can construct a query using in with tuples:
select t.*
from t
where (city, street, street_number) in ( (('ar', 'K1', '13'), ('dc', 'A2', '8'), ('ny', '01', '37') );
However, if the data starts in the database, then a temporary table or subquery is better than bringing the results back to the application and constructing such a query.
I think you can use the hierarchy query and string function as follows:
WITH YOUR_INPUT_DATA AS
(SELECT '(ar,K1,13),(dc,A2,8),(ny,01,37)' AS INPUT_STR FROM DUAL),
--
CTE AS
( SELECT REGEXP_SUBSTR(STR,'[^,]',1,2) AS STR1,
REGEXP_SUBSTR(STR,'[^,]',1,3) AS STR2,
REGEXP_SUBSTR(STR,'[^,]',1,4) AS STR3
FROM (SELECT SUBSTR(INPUT_STR,
INSTR(INPUT_STR,'(',1,LEVEL),
INSTR(INPUT_STR,')',1,LEVEL) - INSTR(INPUT_STR,'(',1,LEVEL) + 1) STR
FROM YOUR_INPUT_DATA
CONNECT BY LEVEL <= REGEXP_COUNT(INPUT_STR,'\),\(') + 1))
--
SELECT * FROM YOUR_TABLE WHERE (city,street,street_number)
IN (SELECT STR1,STR2,STR3 FROM CTE);

SQL Problem - Query to connect movement transactions (FROM-TO) in a SQL table without looping

I have a very big table which contains product movement transactions (FROM-TO).
Example Table
+-----------------+----------------+--------------+--------+
| FROM STATION ID | TO STATION ID | Product Type | Volume |
+-----------------+----------------+--------------+--------+
| A | B | T1 | 1000 |
| B | C | T1 | 300 |
| B | D | T1 | 400 |
| C | E | T1 | 200 |
| C | F | T1 | 100 |
+-----------------+----------------+--------------+--------+
I need to connect these transaction together in SQL way so that I could allocate cost for those volume to each station properly.
Expected Result
A->B->C->E
A->B->C->F
A->B->D
Currently, I'm doing this by looping to join the table with the table itself to connect transactions. In this case, it would loop for joining the original table itself for 3 times. However, it takes way too much time to process, especially when number of transactions is increased.
Could you help advise me any smarter SQL way to solve this problem?
Thanks.
Try this can replace group_concat with listagg depending upon the database
Select [from] , group_concat(case
when [from]
<> t.[from]
and
to=t.[from] then t.to end, ',' )
from( Select
* FROM table) t group by
from

How to generate new rows depending on sting value from one column in sql?

I need to generate new rows depending on value from "Number" column. I've tried CHARINDEX and STUFF function without any success.
Exampledata (where ".." means in between, "| means and):
CREATE TABLE mytable(
Row VARCHAR(3) NOT NULL PRIMARY KEY
,Description VARCHAR(11)
,Number VARCHAR(26)
);
INSERT INTO mytable(Row,Description,Number) VALUES ('100','Testing','1105..1110|2805|2820..2830');
INSERT INTO mytable(Row,Description,Number) VALUES (NULL,NULL,NULL);
INSERT INTO mytable(Row,Description,Number) VALUES (NULL,NULL,NULL);
Expected output:
+-----+-------------+--------+
| Row | Description | Number |
+-----+-------------+--------+
| 100 | Testing | 1105 |
| 100 | Testing | 1106 |
| 100 | Testing | 1107 |
| 100 | Testing | 1108 |
| 100 | Testing | 1109 |
| 100 | Testing | 1110 |
| 100 | Testing | 2805 |
| 100 | Testing | 2820 |
| 100 | Testing | 2821 |
| 100 | Testing | 2822 |
| 100 | Testing | 2823 |
| 100 | Testing | 2824 |
| 100 | Testing | 2825 |
| 100 | Testing | 2826 |
| 100 | Testing | 2827 |
| 100 | Testing | 2828 |
| 100 | Testing | 2829 |
| 100 | Testing | 2830 |
+-----+-------------+--------+
#vasdan, could you please check following SQL script
Please note that I used to user-defined function which you can find similar ones on the web
First one is SQL split function
The second is SQL numbers function
;with cte as (
select
t.row,
t.description,
t.number,
s.id grpno,
replace(s.val,'..','|') as val
from mytable as t
cross apply dbo.split(t.Number,'|') s
), cte2 as (
select distinct
row,
description,
grpno,
case when ( count(*) over (partition by row, grpno) ) = 1 then null else 'X' end as range,
min(s.val) over (partition by row, grpno) minval,
max(s.val) over (partition by row, grpno) maxval
from cte
cross apply dbo.split(cte.val,'|') s
)
select
row,description,i
from cte2
cross apply dbo.NumbersTable(minval,maxval,1) n
Here is the output
If you need help, I'ld like to help you further on this example
Use a recursive CTE to bulk create the numbers, use a parameter for the big string, insert only those that appear in the string
with CTE as
(
select 1105 as NN
union all
select NN + 1
from CTE
where NN < 2830
)
insert into MyTable
select 100, 'Testing', NN
from CTE
where #MyString like '%'+NN+'%'
The easiest way is to create a Numbers table for yourself. Having a numbers table is useful in numerous situations just like this one. It allows you to do very easy SQL queries on ranges of numbers.
Check out this MSDN article: The SQL Server Numbers Table, Explained
Once you have a numbers table, you can easily populate your target table with values "WHERE numbers.number between 1105 and 1110"
SQL 2005 introduced the Common Table Expression (CTE) and the ROW_NUMBER() window function enabling in-line creation of numbers tables.
https://technet.microsoft.com/en-us/library/ms190766(v=sql.105).aspx
Table Value Constructor's (TVC) were introduced in SQL 2008 which offers a very compact way to create a numbers table tailored specifically to the precise number of rows you need.
https://technet.microsoft.com/en-us/library/dd776382(v=sql.110).aspx
-- Further reading: http://dataeducation.com/you-require-a-numbers-table/

Copy and Cascade insert using PL/SQL

Given data structure:
I have the following table My_List, where Sup_ID is Primary Key
My_List
+--------+----------+-----------+
| Sup_ID | Sup_Name | Sup_Code |
+--------+----------+-----------+
| 1 | AA | 23 |
| 2 | BB | 87 |
| 3 | CC | 90 |
+--------+----------+-----------+
And the following table _MyList_details, where Buy_ID is Primary Key and Sup_ID is Foreign Key points at My_List.Sup_ID
My_List_details
+--------+--------+------------+------------+------------+
| Buy_ID | Sup_ID | Sup_Detail | Max_Amount | Min_Amount |
+--------+--------+------------+------------+------------+
| 23 | 1 | AAA | 1 | 10 |
| 33 | 2 | BBB | 11 | 20 |
| 43 | 3 | CCC | 21 | 30 |
+--------+--------+------------+------------+------------+
Finally, I have the table My_Sequence as follow:
My_Sequence
+-----+------+
| Seq | Name |
+-----+------+
| 4 | x |
| 5 | y |
| 6 | z |
+-----+------+
---------------------------------------------------
Objectives
Write PL/SQL script to:
Using a cursor, I need to copy My_List records and re-insert it with the new Sup_ID copied from My_Sequence.Seq.
I need to copy My_List_details records and re-insert them with the new Sup_ID foreign key.
------------------------------------------------------------------------------
Expected Outcome
My_List
+--------+----------+----------+
| Sup_ID | Sub_Name | Sub_Code |
+--------+----------+----------+
| 1 | AA | 23 |
| 2 | BB | 87 |
| 3 | CC | 90 |
| 4 | AA | 23 |
| 5 | BB | 87 |
| 6 | CC | 90 |
+--------+----------+----------+
My_List_details
+--------+--------+------------+------------+------------+
| Buy_ID | Sup_ID | Sub_Detail | Max_Amount | Min_Amount |
+--------+--------+------------+------------+------------+
| 23 | 1 | AAA | 1 | 10 |
| 33 | 2 | BBB | 11 | 20 |
| 43 | 3 | CCC | 21 | 30 |
| 53 | 4 | AAA | 1 | 10 |
| 63 | 5 | BBB | 11 | 20 |
| 73 | 6 | CCC | 21 | 30 |
+--------+--------+------------+------------+------------+
What I have started with is the following:
DECLARE
NEW_Sup_ID Sup_ID%type := Seq;
c_Sup_Name Sup_Name%type;
c_Sup_Code Sup_Code%type;
c_Buy_ID Buy_ID%type;
c_Sup_Detail Sup_Detail%type;
c_Max_Amount Max_Amount%type
c_My_Min_Amount Min_Amount%type
CURSOR c_My_List
IS
SELECT * FROM My_List;
CURSOR c_My_List_details
IS
SELECT * FROM My_List_details
BEGIN
FETCH c_My_List INTO NEW_Sup_ID, c_Sup_Name, c_Sup_Code;
INSERT INTO My_List;
FETCH c_My_List_details INTO c_Buy_ID, NEW_Sup_ID, c_Sup_Detail, c_Max_Amount, c_Min_Amount
INSERT INTO My_List_details
END;
/
Aside from the syntax errors, I do not see my script copy row by row and insert them to both tables accordingly. Further, the number of My_Sequence records is bigger than the number of My_List records. So what I need is, if My_List records are 50, I need the script to copy the first 50 Seq from My_Sequence.
---------------------------------------------------------------------------------
Question
How to achieve this result? I have searched and found Tom Kyte for cascade update but I am not sure if I do need to use this package, I am a bit beginner in PL/SQL and it is a bit complicated for me to utilize such a comprehensive package. Further, it's for cascade update and my case is about re-insert. I'd appreciate any help
The following Sql Statements will perform the task on the schema defined at this SqlFiddle. Note that I have changed a couple of field and table names - because they clash with Oracle terms. SqlFiddle seems to have some problems with my code, but it has been tested on another (amphibious) client which shall remain nameless.
The crucial point (As I said in my comments) is deriving a rule to map old sequence number to new. The view SEQUENCE_MAP performs this task in the queries below.
You may be disappointed by my reply because it depends upon there being the exact same number of sequence records as LIST/LIST_DETAILS, and hence it can only be run once. Your final PL/SQL can perform the necessary checks, I hope.
Hopefully it is a matter of refining the sequence_map logic to get you where you want to be.
Avoid using cursors; ideally when manipulating relational data you need to think in terms of sets of data rather than rows. This is because if you use set-thinking Oracle can do its magic in optimising, parallelising and so-on. Oracle is brilliant at scaling up - If a table is split over multiple disks, for example, it may process your request with data from the multiple disks simultaneously. If you force it into a row-by-row, procedural logic you may find that the applications you write do not scale up well.
CREATE OR REPLACE VIEW SEQUENCE_MAP AS (
SELECT OLD_SEQ, NEW_SEQ FROM
(
( SELECT ROWNUM AS RN, SUP_ID AS OLD_SEQ FROM
(SELECT SUP_ID FROM LIST ORDER BY SUP_ID) ) O
JOIN
( SELECT ROWNUM AS RN, SUP_ID AS NEW_SEQ FROM
(SELECT SEQ AS SUP_ID FROM SEQUENCE_TABLE ORDER BY SEQ) ) N
ON N.RN = O.RN
)
);
INSERT INTO LIST
(
SELECT
NEW_SEQ, SUB_NAME, SUB_CODE
FROM
SEQUENCE_MAP
JOIN LIST L ON
L.SUP_ID = SEQUENCE_MAP.OLD_SEQ
);
INSERT INTO LIST_DETAILS
(
SELECT
BUY_ID, NEW_SEQ, SUB_DETAIL, MAX_FIELD, MIN_FIELD
FROM
SEQUENCE_MAP
JOIN LIST_DETAILS L ON
L.SUP_ID = SEQUENCE_MAP.OLD_SEQ
);
I would do 2 inner loops, and search the next sequence to use.
I imagine the new buy_id is assigned via trigger using a sequence, or something equivalent, else you'll have to generate it in your code.
I have no Oracle database available to test it, so don't pay attention to syntax.
DECLARE
NEW_Sup_ID Sup_ID%type := Seq;
c_Sup_ID Sup_ID%type := Seq;
c_Sup_Name Sup_Name%type;
c_Sup_Code Sup_Code%type;
c_Buy_ID Buy_ID%type;
c_Sup_Detail Sup_Detail%type;
c_Max_Amount Max_Amount%type;
c_My_Min_Amount Min_Amount%type;
CURSOR c_My_List
IS
SELECT * FROM My_List;
CURSOR c_My_List_details
IS
SELECT * FROM My_List_details where sup_id=c_Sup_ID;
BEGIN
for c_My_List IN c_Sup_ID, c_Sup_Name, c_Sup_Code loop
select min(seq) from My_sequence into NEW_Sup_ID;
INSERT INTO My_List (sup_id,...) values (NEW_Sup_ID,...);
for c_My_List_details IN c_Buy_ID, NEW_Sup_ID, c_Sup_Detail, c_Max_Amount, c_Min_Amount loop
INSERT INTO My_List_details (sup_id, ...) values (NEW_Sup_ID,...);
end loop;
deelte from from My_sequence where seq= NEW_Sup_ID;
end loop;
commit;
END;
/