oracle - path passing by multiple nodes structure - oracle

I have the following oracle 12c tables structure:
table1:
path_id node_1 node1_port node_2 node2_port
------------------------------------------------------------------------
1 France France_port1 Italy Italy_port1
1 Italy Italy_port1 France France_port1
1 Italy Italy_port2 Belgium Belgium_port1
1 Belgium Belgium_port1 Italy Italy_port2
1 Belgium Belgium_port2 Sweden Sweden_port1
1 Sweden Sweden_port1 Belgium Belgium_port2
2 Belgium Belgium_port1 Germany Germany_port1
2 Germany Germany_port1 Belgium Belgium_port1
table2:
path_id start_node start_node_port end_node end_node_port
----------------------------------------------------------------
1 France France_port1 Sweden Sweden_port1
2 Belgium Belgium_port1 Germany Germany_port1
where path id has multiple nodes and node_1_port and node_2_port represent the hops of this path.
so for example path_id = 1 start from France and end in Sweden (as per table2), passing by Italy and Belgium(as per table1), while path_id = 2 start from Belgium and end in Germany.
can you give me some hints how to proceed as I'm still new to oracle.
EDIT:
I edited the post to make it a little more realistic.
In addition, node_1 and node_2 can be random names. the order of the hops will be got from the connection of node_1 and node_2 : France is connected to Italy , then Italy connected to Belgium , then Belgium connected to Sweden , then the order of the path is : France-->Italy-->Belgium-->Sweden , knowing that the port connecting Italy to France is different than the port connecting Italy to Belgium , and both ports must be included in the final table. It's a bit complicated , I'm not able to get it right.
this is the needed output:
path_id node_name node_port order
---------------------------------------------------
1 France France_port1 1
1 Italy Italy_port1 2
1 Italy Italy_port2 3
1 Belgium Belgium_port1 4
1 Belgium Belgium_port2 5
1 Sweden Sweden_port1 6
2 Belgium Belgium_port1 1
2 Germany Germany_port1 2
This is what I reached so far , knowing that I was forced to use loops as I couldn't think of any other way.
Is there a way to get the result using a query without loops as I have around 1M records in table1.
create table table_final (path_id integer,node_name varchar2(50),node_port varchar2(50),order_rec integer);
declare
id integer :=1;
start_node varchar2(50);
end_node varchar2(50);
start_node_port varchar2(50);
end_node_port varchar2(50);
cnt integer:=1;
cnt_or integer:=1;
cnt1 integer:=0;
TYPE nodes_arr IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
nodes nodes_arr;
TYPE nodes_ports_arr IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
ports nodes_ports_arr;
id_cnt integer;
BEGIN
select max(path_id) into id_cnt from table2;
while id <= id_cnt
LOOP
select start_node,end_node,start_node_port,end_node_port into nodes(1),end_node,ports(1),end_node_port
from table2 where path_id = id;
start_node:=nodes(1);
start_node_port:=ports(1);
insert into table_final
select id,nodes(1),ports(1),cnt from dual;
commit;
while start_node!=end_node
LOOP
cnt:=cnt+1;
cnt_or:=cnt_or+1;
if cnt =2
THEN
select node_2,node2_port into nodes(cnt),ports(cnt)
from table1 where path_id=id and node_1=nodes(cnt-1) and node_2!=node_1;
ELSE
select node_2,node2_port into nodes(cnt),ports(cnt)
from table1 where path_id=id and node_1=nodes(cnt-1) and node_2!=node_1 and node_2!=nodes(cnt-2) ;
END IF;
insert into table_final
select id,nodes(cnt),ports(cnt),cnt_or from dual;
select NVL(count(*),0) into cnt1 from table1 where path_id=id and node_1=nodes(cnt) and node1_port !=ports(cnt);
if cnt1=1
THEN
cnt_or:=cnt_or+1;
insert into table_final
select id,node_1,node1_port,cnt_or
from table1 where path_id=id and node_1=nodes(cnt) and node1_port !=ports(cnt);
END IF;
start_node:=nodes(cnt);
start_node_port:=ports(cnt);
commit;
END LOOP;
cnt:=1;
cnt_or:=1;
id:=id+1;
END LOOP;
END;

I am not able to see any usage of table2 here. You can simply remove duplicates from table1 itself as following:
Select t.*,
row_number()
over (partition by path_id order by to_number(replace(node_name, 'node', ''))) as order_
From
(Select distinct path_id,
Node_name,
Node_name_1_para
From table1) t
Order by path_id, order_;
-- Update --
As per the comments, you should try this query:
Select t1.path_id,
T1.node_name
T.node_name_1_para,
Row_number() over (partition by t1.path_id order by null) as order_
From
Table1 t1 join table2 t2
On (t1.path_id = t2.path_id
And t1.node_1 between t2.start_node and t2.end_node);
Cheers!!

Related

How to generate 18 digit code using cursor based on some conditions in Oracle

I have a table from which I want to generate 18 digit code.
Below is the 18 digit code sample which I want.
R-AP-AP01-SMT-4567
Also for generating the above sample code, here is the data and its logic:
R - Fix value
AP – (2 digit state code from STATE column)
EAST- (From ZONE_NAME column from query below
SMT – (From FORMAT_CODE column from below query)
4567 – (From Store Code column from below query)
SELECT STATE, STORE_CODE, ZONE_NAME FROM TBL_RRSOC_STORE_INFO;
AND
select FORMAT_CODE from TBL_SITE_STORE_FORMAT;
How can it be achieved?
Update
Below is the table description
Table name:- TBL_RRSOC_STORE_INFO
Name Null Type
--------------------------- -------- --------------
RRSOC_ID NOT NULL NUMBER
STORE_CODE NOT NULL NVARCHAR2(55)
STATE NVARCHAR2(55)
SLP_STATE NVARCHAR2(100)
FORMAT_GROUP NVARCHAR2(100)
Table name:- TBL_SITE_STORE_FORMAT
Name Null Type
------------ ---- -------------
ID VARCHAR2(20)
STORE_FORMAT VARCHAR2(100)
ISACTIVE VARCHAR2(3)
FORMAT_GROUP VARCHAR2(100)
FORMAT_CODE VARCHAR2(50)
The way you put it, you'd join those tables somehow (cross join is as good as any other, as you didn't explain it better) and concatenate column values.
Something like this:
SQL> with
2 tbl_rrsoc_store_info (state, store_code, zone_name) as
3 (select 'AP', 'EAST', 'SMT' from dual union all
4 select 'NY', 'WEST', 'XYZ' from dual
5 ),
6 tbl_site_store_format (format_code) as
7 (select 4567 from dual)
8 --
9 select 'R' ||'-'|| r.state ||'-'|| r.store_code ||'-'|| r.zone_name ||'-'|| s.format_code result
10 from tbl_rrsoc_store_info r cross join tbl_site_store_format s;
RESULT
--------------------
R-AP-EAST-SMT-4567
R-NY-WEST-XYZ-4567
SQL>
Function returns a value; you didn't explain how it should look like (which parameters it accepts) so I chose to pass state, presuming it is unique within the table.
Sample data:
SQL> select * From tbl_rrsoc_store_info;
ST STOR ZON
-- ---- ---
AP EAST SMT
NY WEST XYZ
SQL> select * from tbl_site_store_format;
FORMAT_CODE
-----------
4567
Function:
SQL> create or replace function f_test (par_state in varchar2)
2 return varchar2
3 is
4 retval varchar2(18);
5 begin
6 select 'R' ||'-'|| r.state ||'-'|| r.store_code ||'-'|| r.zone_name ||'-'|| s.format_code
7 into retval
8 from tbl_rrsoc_store_info r cross join tbl_site_store_format s
9 where r.state = par_state;
10
11 return retval;
12 end;
13 /
Function created.
Testing:
SQL> select r.state, f_test(r.state) result
2 from tbl_rrsoc_store_info r;
ST RESULT
-- --------------------
AP R-AP-EAST-SMT-4567
NY R-NY-WEST-XYZ-4567
SQL>

How to filter and retrieve the results after a Specific characters from stored procedure in oracle plsql?

I have a column "Names" in the "Employee" table that has following values. The values either contain only single name (first, last, username) or Multiple names separated with semicolon (;). I need to search the values from that table either by first name or last name or username.
I have created a procedure but it is fetching only 1st,4th,5th records. Please let me know how to retrieve 2nd and 3rd records as well.
Firstname and lastname can be given by user with minimum of 2 characters length.
Username is given entire.
Employee:
ID Name Title
1 Andrea Warbutton (awr01) Manager
2 Claire Taylor (cta02);Mark Kites (mak03);Anitha Rooney (anr06) HOD;Supervisor;Business
3 Dave Rites (dar12);Jessica Simpson (jesi10) Lead;Analyst
4 Nick Ken (nik56) Product (Local,Regional)
5 Claire Pilkington (cpt09) Sales Owner
Code:
Create or replace empl (pm_firstname varchar2(100),
pm_lastname varchar2(100),
pm_username varchar2(100))
BEGIN
Select * from Employee
where Upper(Name) like Upper(pm_firstname ||'%'||) -- this will fetch 1st,4th,5th record
OR Upper(SUBSTR(Name, INSTR(Name),' '+1)) like Upper(pm_lastname ||'%'||) -- this will fetch 1st,4th,5th record
OR upper(REGEXP_SUBSTR(Name,'\((.+)\)',1,1,NULL,1)) = Upper(pm_username); -- -- this will fetch 1st,4th,5th record
END;
End empl ;
Please let me know how to retrieve 2nd and 3rd records as well.
Desired Output:
When searched with firstname = "Andrea", the output is below
ID Name Title
1 Andrea Warbutton (awr01) Manager
When searched with firstname = "Claire", the output is below
ID Name Title
2 Claire Taylor (cta02) HOD
5 Claire Pilkington (cpt09) Sales Owner
When searched with lastname = "Simps", the output is below
ID Name Title
3 Jessica Simpson (jesi10) Analyst
When searched with username = "mak03", the output is below
ID Name Title
2 Mark Kites (mak03) Supervisor
When searched with username = "nik56", the output is below
ID Name Title
4 Nick Ken (nik56) Product (Local,Regional)
with
x as (select id, name, '"'||replace(name, ';', '","')||'"' xml from employee),
n as (select id, name, column_value as cv from x, xmltable(xml))
select id,
trim(regexp_substr(cv, '(\S*)(\s)')) fname,
trim(regexp_substr(cv, '(\S*)(\s)', 1, 2)) lname,
regexp_substr(cv, '\((.+)\)', 1, 1, NULL, 1) uname
from n
Your task would be much easier if you normalize these data. Above query outputs:
ID FNAME LNAME UNAME
1 Andrea Warbutton awr01
2 Claire Taylor cta02
2 Mark Kites mak03
2 Anitha Rooney anr06
3 Dave Rites dar12
3 Jessica Simpson jesi10
4 Nick Ken nik56
5 Claire Pilkington cpt09
demo
Now you can search first, last, usernames however you want. First expression finds first word, then second and word between brackets.
Edit:
I posted the table structure with just ID and Name columns. However, I
have Titles column also in the same format separated with (semicolon).
In this case, How can I Normalize Titles as well along with Names
This query worked for provided examples:
with
x as (select id, name, '"'||replace(name, ';', '","')||'"' xmln,
'"'||replace(title, ';', '","')||'"' xmlt
from employee),
n1 as (select id, trim(xn.column_value) nm, rownum rn from x, xmltable(xmln) xn),
n2 as (select id, trim(xt.column_value) tt, rownum rn from x, xmltable(xmlt) xt)
select id, trim(regexp_substr(nm, '(\S*)(\s)')) fname,
trim(regexp_substr(nm, '(\S*)(\s)', 1, 2)) lname,
regexp_substr(nm, '\((.+)\)', 1, 1, NULL, 1) uname,
tt title
from n1 join n2 using (id, rn)
dbfiddle demo
Be careful however, because we cannot write ideal query. If you have entries like Benicio Del Toro, Mary Jo Catlett, Jean Claude Van Damme, it's impossible to write correct regexp. Sometimes second word is part of lastname, sometimes it is firstname, middlename etc.
The proper way is to modify table structure, divide rows, check results and put correct values in correct name columns. Now you have lists which are hard to search and every method may return wrong results.
No need for PL/SQL.
SQL> with temp as
2 (select id,
3 regexp_substr(name, '[^;]+', 1, column_value) name
4 from employee cross join
5 table(cast(multiset(select level from dual
6 connect by level <= regexp_count(name, ';') + 1
7 ) as sys.odcinumberlist))
8 )
9 select id, name
10 from temp
11 where instr(name, '&search_for_name') > 0;
Enter value for search_for_name: Claire
ID NAME
---------- ------------------------------
2 Claire Taylor (cta02)
5 Claire Pilkington (cpt09)
SQL> /
Enter value for search_for_name: mak03
ID NAME
---------- ------------------------------
2 Mark Kites (mak03)
SQL>
What does it do?
temp CTE splits semi-colon separated values into rows
final query uses a simple instr function which detects whether "rows" (extracted previously) contain value you're looking for
If it must be a function, that code can be reused. As you didn't say what exactly (which datatype, I mean) you want to return, I returned a string.
SQL> create or replace function f_search (par_what in varchar2)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 with temp as
7 (select id,
8 regexp_substr(name, '[^;]+', 1, column_value) name
9 from employee cross join
10 table(cast(multiset(select level from dual
11 connect by level <= regexp_count(name, ';') + 1
12 ) as sys.odcinumberlist))
13 )
14 select id ||' - '|| name
15 bulk collect into retval
16 from temp
17 where instr(name, par_what) > 0;
18
19 return retval;
20 end;
21 /
Function created.
SQL> select * from table(f_search('Andrea'));
COLUMN_VALUE
--------------------------------------------------------------------------------
1 - Andrea Warbutton (awr01)
SQL> select * from table(f_search('Claire'));
COLUMN_VALUE
--------------------------------------------------------------------------------
2 - Claire Taylor (cta02)
5 - Claire Pilkington (cpt09)
SQL>

How to split single rows into multiple rows depending on whether one field contains comma separated string in oracle sql?

I have created a function get_depatment_names which returns comma separated values in the single Return Value.
The output of function is : Sales,Retail,Electronic.
Another function get_location_names returns the same way for the location. Output: West York,Shire,Lancas
Now, these two functions , I'm calling in my select statement along with other columns.
SELECT dept_id,
date,
get_department_names dept_name,
get_location_names location
status
FROM department
WHERE dept_id = 1;
Output is displayed as below:
dept_id ---date ----dept_name -------location----- status
1 ---01/01/2018 --- Sales,Retail,Electronic ---West York,Shire,Lancas-- Active
Expected output:
1--01/01/2018 --- Sales --- West York-- Active
1--01/01/2018 --- Retail --- Shire -- Active
1--01/01/2018 --- Electronic ---Lancas --Active
I tried using the regexp_sub with connect in the select stmt as below but is giving an error of "single row subquery returns more than one row".
SELECT dept_id,
date,
(select regexp_substr(get_department_names(id),'[^,]+',1,level) from dual
connect by regexp_substr(get_department_names(id),'[^,]+',1,level) is not null) dept_name,
(select regexp_substr(get_location_names (id),'[^,]+',1,level) from dual
connect by regexp_substr(get_location_names(id),'[^,]+',1,level) is not null) location
status
FROM department
WHERE dept_id = 1;
Please let me know how to correct this.
That would be something like this (lines #1 - 4 represent sample data; query you need begins at line #5):
SQL> with department (dept_id, datum, dept_name, location, status) as
2 (select 1, date '2018-01-01', 'sales,retail,electronic',
3 'West York,Shire,Lancas', 'active' from dual
4 )
5 select dept_id,
6 datum,
7 regexp_substr(dept_name, '[^,]+', 1, level) dept_name,
8 regexp_substr(location , '[^,]+', 1, level) location,
9 status
10 from department
11 where dept_id = 1
12 connect by level <= regexp_count(dept_name, ',') + 1;
DEPT_ID DATUM DEPT_NAME LOCATION STATUS
---------- ---------- --------------- --------------- ------
1 01/01/2018 sales West York active
1 01/01/2018 retail Shire active
1 01/01/2018 electronic Lancas active
SQL>

concatenate the output values in oracle plsql

create table item_value_tab
as
select 1110 Item_value,000394 bb_to_no, 55555 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000394 bb_to_no, 66666 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000394 bb_to_no, 77777 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000123 bb_to_no, 1111 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000124 bb_to_no, 2222 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000124 bb_to_no, 3333 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000011 bb_to_no, 75257 order_number, 'ABC' cust_no from dual
union all
select 1110 Item_value,000011 bb_to_no, 65257 order_number, 'ABC' cust_no from dual;
Take below code in Cursor 1
SELECT DISTINCT DECODE(nvl(Item_value,0),bb_to_no,null,'Item Value '||nvl(bb_to_no,0)||' Is not correct for order number '||order_number||' FOR Customer '||cust_no) vmsg
from item_value_tab;
Getting Output
Item Value 000394 Is not correct for order number 55555 FOR Customer ABC
Item Value 000394 Is not correct for order number 66666 FOR Customer ABC
Item Value 000394 Is not correct for order number 77777 FOR Customer ABC
Item Value 000123 Is not correct for order number 1111 FOR Customer ABC
Item Value 000124 Is not correct for order number 2222 FOR Customer ABC
Item Value 000124 Is not correct for order number 3333 FOR Customer ABC
Item Value 000011 Is not correct for order number 75257 FOR Customer ABC
Item Value 000011 Is not correct for order number 65257 FOR Customer ABC
-------------------------------------------------------------------------------
create table item_not_tab
as
select 0 item_id,53555 bb_to_no,1052 order_number,'AAA' cust_no from dual
union all
select 0 item_id,53655 bb_to_no,1138 order_number,'AAA' cust_no from dual;
Need to take the below code in cursor 2
select DECODE(nvl(item_id,0),0,'items not created for '||bb_to_no||'order number '||order_number||' FOR Customer '||cust_no,NULL) vmsg
from item_not_tab;
Getting Output
items not created for 53555 order number 1052 FOR Customer AAA
items not created for 53655 order number 1052 FOR Customer AAA
Expected output
Item Value Problems
Item Value 000394 Is not correct for order number 55555,66666,77777 FOR Customer ABC
Item Value 000123,000124 Is not correct for order number 1111 FOR Customer ABC
Item Value 000124 Is not correct for order number 2222,3333 FOR Customer ABC
Item Value 000011 Is not correct for order number 75257,65257 FOR Customer ABC
Items not found Problems
items not created for 53555,53655 order number 1052 FOR Customer AAA
note: please don't joins both tables in one cursor, I need to take 2 cursors. Both tables are same actually, but test case purpose I have as 2 d/f tables
oracle 11g 1.1 and 1.2
You need to first concatenate the tot_number inside the LOOP, and then use the value outside the LOOP.
For example,
SQL> SET serveroutput ON
SQL> DECLARE
2 v VARCHAR2(100);
3 BEGIN
4 FOR i IN
5 (SELECT 55555 str FROM dual
6 UNION
7 SELECT 66666 str FROM dual
8 UNION
9 SELECT 77777 str FROM dual
10 )
11 LOOP
12 v:= ltrim(v||','||i.str, ',');
13 END LOOP;
14 dbms_output.put_line('Output is concatenated '||v);
15 END;
16 /
Output is concatenated 55555,66666,77777
PL/SQL procedure successfully completed.
SQL>
Please Check the below QUERY :
SELECT DISTINCT o.bb_to_no,
(SELECT listagg(order_number,',') WITHIN GROUP (ORDER BY 1 desc)
FROM item_value_tab i WHERE o.bb_to_no = i.bb_to_no) AS total_num,
(SELECT DISTINCT(k.cust_no) FROM item_value_tab k
WHERE o.bb_to_no = k.bb_to_no) AS cust_name
FROM item_value_tab o
(OR)
EDIT : Please check this.
//It create collection of type table to store all the order_numbers
CREATE OR REPLACE TYPE t_varchar2_tab AS TABLE OF NUMBER;
// Below procedure will concatenate the list of order_numbers stored in
// the above created collection type.
CREATE OR REPLACE FUNCTION tab_to_string
(p_varchar2_tab IN t_varchar2_tab,
p_delimiter IN VARCHAR2 DEFAULT ',') RETURN VARCHAR2 IS
l_string VARCHAR2(32767);
BEGIN
FOR i IN p_varchar2_tab.FIRST .. p_varchar2_tab.LAST LOOP
IF i != p_varchar2_tab.FIRST THEN
l_string := l_string || p_delimiter;
END IF;
l_string := l_string || p_varchar2_tab(i);
END LOOP;
RETURN l_string;
END tab_to_string;
SELECT bb_to_no,cust_no,
tab_to_string(CAST(COLLECT(order_number) AS t_varchar2_tab))
AS tot_number
FROM item_value_tab
GROUP BY bb_to_no,cust_no;
P.S : you can find more information on COLLECT() function on below link.
http://www.oracle-developer.net/display.php?id=306

Oracle Query for getting CURRENT CTC (Salary) of Each Employee

i want current CTC of each employee following is the design of my table
Ecode Implemented Date Salary
7654323 2010-05-20 350000
7654322 2010-05-17 250000
7654321 2003-04-01 350000
7654321 2004-04-01 450000
7654321 2005-04-01 750000
7654321 2007-04-01 650000
i want oracle query for following output
Ecode Salary
7654321 650000
7654322 250000
7654323 350000
thanks in advance
See also
Oracle Query for getting MAximum CTC (Salary) of Each Employee
If you want to keep the last salary for each ecode sorted by implemented_date:
SQL> WITH data AS (
2 SELECT 7654323 Ecode, '2010-05-20' Implemented_Date, 350000 Salary
3 FROM DUAL
4 UNION ALL SELECT 7654322, '2010-05-17', 250000 FROM DUAL
5 UNION ALL SELECT 7654321, '2003-04-01', 350000 FROM DUAL
6 UNION ALL SELECT 7654321, '2004-04-01', 450000 FROM DUAL
7 UNION ALL SELECT 7654321, '2005-04-01', 750000 FROM DUAL
8 UNION ALL SELECT 7654321, '2007-04-01', 650000 FROM DUAL
9 )
10 SELECT ecode,
11 MAX(salary)
12 KEEP (dense_rank FIRST ORDER BY Implemented_Date DESC) sal
13 FROM DATA
14 GROUP BY ecode;
ECODE SAL
---------- ----------
7654321 650000
7654322 250000
7654323 350000
SELECT *
FROM salary s
INNER JOIN
(SELECT ecode, MAX(implemented_date) as implemented_date
FROM salary GROUP BY ecode) curr
ON curr.ecode = s.ecode AND curr.implemented_date = s.implemented_date
I'd use analytical functions for that. You want to select the first value of salary for each combination of ecode and implementeddate ordered by the implementeddate to put the latest at the top.
select
distinct
first_value(ecode) OVER (PARTITION BY ecode ORDER BY IMPLEMENTEDDATE DESC NULLS LAST) Ecode,
first_value(implementeddate) OVER (PARTITION BY ecode ORDER BY IMPLEMENTEDDATE DESC NULLS LAST) ImplementedDate,
first_value(salary) OVER (PARTITION BY ecode ORDER BY IMPLEMENTEDDATE DESC NULLS LAST) Salary
from
tbl_Salary;
The "DISTINCT" will keep null rows at bay that would otherwise be returned for the other 3 versions of Ecode=7654321 that we're filtering out.
The result is:
ECODE IMPLEMENTEDDATE SALARY
----- --------------- ------
7654321 01/04/2007 650000
7654322 17/05/2010 250000
7654323 20/05/2010 350000

Resources