SQL Select related tables into new columns - oracle

I have a table in SQL that has 3 columns:
CategoryName
WarnInterval
ErrorInterval
And then I have another table which has 4 columns:
CategoryName
GroupName
IncludedExcluded
Match
Each row in this table has 2 unique records based on CategoryName, GroupName and IncludedExcluded. For example, as json it looks like this:
{
categoryName: 'Folders',
groupName: 'Products',
includeExcluded: 'E',
match: 'SomeProduct'
}, {
categoryName: 'Folders',
groupName: 'Products',
includeExcluded: 'I',
match: 'SomeOtherProduct'
}
There are 3 Groups (Products, Reps and BusinessCategories), so each Category has only 6 rows of data in this table.
I want to generate an SQL statement that pulls the tables into columns that I can then parse into a json object like this:
{
categoryName: 'Folders',
warnInterval: 40,
errorInterval: 60,
groups: [{
groupName: 'Products',
Include: 'SomeProduct',
Exclude: 'SomeOtherProduct'
}, {
groupName: 'Reps',
Include: 'SomeRep',
Exclude: 'SomeOtherRep'
}, {
groupName: 'BusinessCategories',
Include: 'SomeCategory',
Exclude: 'SomeOtherCategory'
}]
}
I would like the SQL statement to join the 2 tables and present the data into columns like this:
CategoryName
WarnInterval
ErrorInterval
ProductTitle (this is the group name)
ProductIncludes (If the IncludedExcluded value is 'I' then put the match in this column)
ProductExcludes (If the IncludedExcluded value is 'E' then put the match in this column)
RepresentativeTitle
RepresentativeIncludes
RepresentativeExcludes
BusinessCategoryTitle
BusinessCategoryIncludes
BusinessCategoryExcludes
I really hope I explained that well. Can someone help me with the SQL for this? PS, it is Oracle :)

Maybe this is what you're after. The query really breaks the category_matches table into a set of six datasets - three groups * two include / exclude flags.
The list of columns in your question includes "ProductTitle (this is the group name)" which doesn't make sense since each of the groups is represented in each row of data. But maybe I have misunderstood
WITH
category_intervals (category_name, warn_interval, error_interval) AS
(SELECT 'Folders',40,60 FROM dual UNION ALL
SELECT 'catA',10,20 FROM dual UNION ALL
SELECT 'catB',50,100 FROM dual
)
,category_matches(category_name, group_name, include_exclude, match_name) AS
(SELECT 'Folders','Products','E','Product 1' FROM dual UNION ALL
SELECT 'Folders','Products','I','Product 2' FROM dual UNION ALL
SELECT 'Folders','Reps','E','Rep 1' FROM dual UNION ALL
SELECT 'Folders','Reps','I','Rep 2' FROM dual UNION ALL
SELECT 'Folders','Business Categories','E','Category 1' FROM dual UNION ALL
SELECT 'Folders','Business Categories','I','Category 2' FROM dual UNION ALL
--
SELECT 'catA','Products','E','Product 3' FROM dual UNION ALL
SELECT 'catA','Products','I','Product 4' FROM dual UNION ALL
SELECT 'catA','Reps','E','Rep 3' FROM dual UNION ALL
SELECT 'catA','Reps','I','Rep 4' FROM dual UNION ALL
SELECT 'catA','Business Categories','E','Category 3' FROM dual UNION ALL
SELECT 'catA','Business Categories','I','Category 4' FROM dual UNION ALL
--
SELECT 'catB','Products','E','Product 5' FROM dual UNION ALL
SELECT 'catB','Products','I','Product 6' FROM dual UNION ALL
SELECT 'catB','Reps','E','Rep 5' FROM dual UNION ALL
SELECT 'catB','Reps','I','Rep 6' FROM dual UNION ALL
SELECT 'catB','Business Categories','E','Category 5' FROM dual UNION ALL
SELECT 'catB','Business Categories','I','Category 6' FROM dual
)
SELECT
ci.category_name
,ci.warn_interval
,ci.error_interval
,ip.match_name product_include
,ep.match_name product_exclude
,ir.match_name rep_include
,er.match_name rep_exclude
,ibc.match_name bus_cat_include
,ibc.match_name bus_catt_exclude
FROM
category_intervals ci
,(SELECT *
FROM category_matches
WHERE group_name = 'Products'
AND include_exclude = 'I'
) ip
,(SELECT *
FROM category_matches
WHERE group_name = 'Products'
AND include_exclude = 'E'
) ep
,(SELECT *
FROM category_matches
WHERE group_name = 'Reps'
AND include_exclude = 'I'
) ir
,(SELECT *
FROM category_matches
WHERE group_name = 'Reps'
AND include_exclude = 'E'
) er
,(SELECT *
FROM category_matches
WHERE group_name = 'Business Categories'
AND include_exclude = 'I'
) ibc
,(SELECT *
FROM category_matches
WHERE group_name = 'Business Categories'
AND include_exclude = 'E'
) ebc
WHERE 1=1
AND ci.category_name = ip.category_name
AND ci.category_name = ep.category_name
AND ci.category_name = ir.category_name
AND ci.category_name = er.category_name
AND ci.category_name = ibc.category_name
AND ci.category_name = ebc.category_name
ORDER BY
ci.category_name
;

Related

Group By inside Rtrim(Xmlagg (Xmlelement (e,element || ',')).extract ( '//text()' ).GetClobVal(), ',')

I need to group values ​​inside a query using (or not) the command Rtrim(Xmlagg (Xmlelement (e,column || ',')).extract ( '//text()' ).GetClobVal(), ','), but I can't find any literature where explain a way to group the data inside this command. The code is very simple, as you can see below:
SELECT ID,
Rtrim(Xmlagg (Xmlelement (and, CONTRACTS || ',')).extract ( '//text()' ).GetClobVal(), ',') AS CONTRACTS
FROM TABLE_A
GROUP BY ID
The result in CONTRACTS is always repeated when the ID is found, thats ok, it´s working!
ID
CONTRACTS
876
1,1,1,2,3,3
But what I really need is this return:
ID
CONTRACTS
876
1,2,3
It´s not necessary to use the command Rtrim(Xmlagg (Xmlelement (e,column || ',')).extract ( '//text()' ).GetClobVal(), ','), instead, I just use to concatenate element with comma "," in the same column.
If anyone can help me, I would be very grateful!
If your values will fit into a VARCHAR2 data type (rather than a CLOB) then you can use a nested sub-query to get the DISTINCT values for each ID:
SELECT ID,
LISTAGG(contracts, ',') WITHIN GROUP (ORDER BY contracts) AS CONTRACTS
FROM ( SELECT DISTINCT id, contracts FROM TABLE_A)
GROUP BY ID
Or, from Oracle 19c, it is built-in to LISTAGG:
SELECT ID,
LISTAGG(DISTINCT contracts, ',') WITHIN GROUP (ORDER BY contracts) AS CONTRACTS
FROM TABLE_A
GROUP BY ID
If you want a CLOB then you can use the same technique as the first query:
SELECT ID,
Rtrim(
Xmlagg(
Xmlelement(name, CONTRACTS || ',')
ORDER BY contracts
).extract ( '//text()' ).GetClobVal(),
','
) AS CONTRACTS
FROM (SELECT DISTINCT id, contracts FROM TABLE_A)
GROUP BY ID
Which, for the sample data:
CREATE TABLE table_a (id, contracts) AS
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL;
All output:
ID
CONTRACTS
876
1,2,3
db<>fiddle here
It's much easier to do all those operation in XML functions: DBFiddle
SELECT--+ NO_XML_QUERY_REWRITE
id,
xmlquery(
'string-join(distinct-values($R/R/X/text()),",")'
passing
Xmlelement(
R,
Xmlagg(
Xmlelement (X, CONTRACTS)
order by CONTRACTS
)) as R
RETURNING CONTENT
) AS CONTRACTS
FROM TABLE_A
GROUP BY ID;
Full example with test data:
with table_a (id, contracts) AS (
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL
)
SELECT--+ NO_XML_QUERY_REWRITE
id,
xmlquery(
'string-join(distinct-values($R/R/X/text()),",")'
passing
Xmlelement(
R,
Xmlagg(
Xmlelement (X, CONTRACTS)
order by CONTRACTS
)) as R
RETURNING CONTENT
) AS CONTRACTS
FROM TABLE_A
GROUP BY ID;

REGEXP_SUBSTR not able to process only current row

(SELECT LISTAGG(EVENT_DESC, ',') WITHIN GROUP (ORDER BY EVENT_DESC) FROM EVENT_REF WHERE EVENT_ID IN
( SELECT REGEXP_SUBSTR(AFTER_VALUE,'[^,]+', 1, level) FROM DUAL
CONNECT BY REGEXP_SUBSTR(AFTER_VALUE, '[^,]+', 1, level) IS NOT NULL
)
)
A table from which I am fetching AFTER_VALUE has values of integer which is comma seperated like
AFTER_VALUE data
Expected output
1
Event1
1,2
Event1,Event2
1,12,2,5
Event1,Event12,Event2,Event5
15,13
Event15,Event13
these are Ids in EVENT_REF table which have some description. I am trying to basically present
ex. 1,2 as Event1, Event2 and send back from query. There are multiple events so using REPLACE would be very tedious.
When using above query I'm getting error as “ORA-01722: invalid number” whenever there is more than one value in AFTER_VALUE column Ex. if there exists only one id , then the query works but for values like 1,2 or 1,13 etc it throws invalid number error.
PS: The event names are not Event1,Event2 etc , I have just put for reference.
You don't even need regular expressions for this assignment. Standard string function replace() can do the same thing, and faster. You only need an extra 'Event' at the beginning of the string, since that one doesn't "replace" anything.
Like this: (note that you don't need the with clause; I included it only for quick testing)
with
event_ref (after_value) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select after_value,
'Event' || replace(after_value, ',', ',Event') as desired_output
from event_ref
;
AFTER_VALUE DESIRED_OUTPUT
----------- -----------------------------
1 Event1
1,2 Event1,Event2
1,12,2,5 Event1,Event12,Event2,Event5
15,13 Event15,Event13
Ah,ok, looks, like you have other characters in your comma-separated list, so you can use this query:
with EVENT_REF(EVENT_ID,EVENT_DESC) as (
select 1, 'Desc 1' from dual union all
select 2, 'Desc 2' from dual union all
select 3, 'Desc 3' from dual union all
select 4, 'Desc 4' from dual union all
select 5, 'Desc 5' from dual union all
select 12, 'Desc12' from dual union all
select 13, 'Desc13' from dual union all
select 15, 'Desc15' from dual
)
select
(SELECT LISTAGG(EVENT_DESC, ',')
WITHIN GROUP (ORDER BY EVENT_DESC)
FROM EVENT_REF
WHERE EVENT_ID IN
( SELECT to_number(REGEXP_SUBSTR(AFTER_VALUE,'\d+', 1, level))
FROM DUAL
CONNECT BY level<=REGEXP_COUNT(AFTER_VALUE, '\d+')
)
)
from (
select '1' AFTER_VALUE from dual union all
select '1,2' AFTER_VALUE from dual union all
select '1,12,2,5' AFTER_VALUE from dual union all
select '15,13' AFTER_VALUE from dual
);
PS. And do not forget that to_number has 'default on conversion error' now: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/TO_NUMBER.html
There is no need to split and concatenate substrings, just use regexp_replace:
with EVENT_REF (AFTER_VALUE) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select regexp_replace(AFTER_VALUE,'(\d+)','Event\1') from EVENT_REF;
REGEXP_REPLACE(AFTER_VALUE,'(\D+)','EVENT\1')
-----------------------------------------------
Event1
Event1,Event2
Event1,Event12,Event2,Event5
Event15,Event13

Oracle is giving error while creating stored procedure. ORA-22905: cannot access rows from a non-nested table item

Hi all I have requirement to pick records using dynamic pivoting in Oracle. I have done with my query which is working fine using "antonsPivoting" https://technology.amis.nl/2006/05/24/dynamic-sql-pivoting-stealing-antons-thunder/.
When i add the query in procedure it gives error: ORA-22905: cannot access rows from a non-nested table item.
Sample table script:
CREATE TABLE DEPARTMENT(DEPT_ID NUMBER PRIMARY KEY, DEPT_NAME VARCHAR2(25))
CREATE TABLE EMPLOYEE(EMP_ID NUMBER, EMP_NAME VARCHAR(100), DEPT_ID NUMBER , FOREIGN KEY(DEPT_ID) REFERENCES DEPARTMENT(DEPT_ID))
INSERT INTO DEPARTMENT(DEPT_ID, DEPT_NAME)
SELECT 1, 'HR' FROM DUAL
UNION ALL SELECT 2, 'OPS' FROM DUAL
UNION ALL SELECT 3, 'MKT' FROM DUAL
UNION ALL SELECT 4, 'FIN' FROM DUAL
UNION ALL SELECT 5, 'IT' FROM DUAL
UNION ALL SELECT 6, 'SERV' FROM DUAL
COMMIT;
INSERT INTO EMPLOYEE(EMP_ID, EMP_NAME, DEPT_ID)
SELECT 1, 'A', 1 FROM DUAL
UNION ALL SELECT 1, 'A', 1 FROM DUAL
UNION ALL SELECT 2, 'B', 1 FROM DUAL
UNION ALL SELECT 3, 'C', 2 FROM DUAL
UNION ALL SELECT 4, 'D', 3 FROM DUAL
UNION ALL SELECT 5, 'E', 3 FROM DUAL
UNION ALL SELECT 6, 'F', 4 FROM DUAL
UNION ALL SELECT 7, 'G', 4 FROM DUAL
UNION ALL SELECT 8, 'H', 4 FROM DUAL
UNION ALL SELECT 9, 'I', 5 FROM DUAL
UNION ALL SELECT 10, 'J', 5 FROM DUAL
UNION ALL SELECT 11, 'K', 1 FROM DUAL
UNION ALL SELECT 12, 'L', 1 FROM DUAL
COMMIT;
sample query script working fine:
SELECT D.DEPT_NAME, COUNT(E.EMP_ID) TOTAL_EMP
FROM DEPARTMENT D
LEFT JOIN EMPLOYEE E ON D.DEPT_ID= E.DEPT_ID
GROUP BY D.DEPT_NAME
;
Dynamic pivoting example working fine:
select * from table( pivot( '
SELECT D.DEPT_NAME, COUNT(E.EMP_ID) TOTAL_EMP
FROM DEPARTMENT D
LEFT JOIN EMPLOYEE E ON D.DEPT_ID= E.DEPT_ID
GROUP BY D.DEPT_NAME' ) )
;
adding in procedure which gives error:
CREATE OR REPLACE PROCEDURE GET_EMPLOYEE (P_RESULT OUT SYS_REFCURSOR)
IS
BEGIN
OPEN P_RESULT FOR
select * from table( pivot( '
SELECT D.DEPT_NAME, COUNT(E.EMP_ID) TOTAL_EMP
FROM DEPARTMENT D
LEFT JOIN EMPLOYEE E ON D.DEPT_ID= E.DEPT_ID
GROUP BY D.DEPT_NAME' ) )
;
END
;
Have you created the Pivot function ?
Please go through below link where this is already answered.
Link

List all ancestors of a child in hierarchical data structure

Consider the following sample table:
with v1 as (
select 'I' as parent_id, 'M' as child_id from dual union all
select 'M', 'M1' from dual union all
select 'M', 'D' from dual union all
select 'I', 'P' from dual union all
select 'P', 'K' from dual union all
select 'A', 'P1' from dual union all
select 'C', 'A' from dual union all
select 'A', 'I' from dual union all
select 'P1', 'K1' from dual
)
select ListAgg(child_id,'<-')
within group(order by Level desc) as Path
from v1
START WITH child_id = 'D'
CONNECT BY PRIOR parent_id = child_id;
It returns:
A<-I<-M<-D
It is not returning A's parent which is C.
What should I change in the query so that it returns C as well like below:
C<-A<-I<-M<-D
NOTE that before executing the query, I have no means to know that C is the root of the hierarchy. So, I can not pass C in the query (and that is how I have designed the my query above).
You can use following query:
With v1 as (
select 'C' as parent_id, 'A' as child_id from dual union all
select 'I', 'M' from dual union all
select 'M', 'M1' from dual union all
select 'M', 'D' from dual union all
select 'I', 'P' from dual union all
select 'P', 'K' from dual union all
select 'A', 'P1' from dual union all
select 'A', 'I' from dual union all
select 'P1', 'K1' from dual
)
select ListAgg(case when level =1 then parent_id || '<-' || child_id else parent_id end,'<-')
within group(order by Level desc) as Path
from v1
START WITH child_id = 'D'
CONNECT BY prior parent_id = child_id;
Sqlfiddle demo
Cheers!!

Passing a delimited string in the NOT IN clause

The below SQL conceptually replicates the problem I'm trying to solve. Despite passing a NOT IN clause all three records are returned.
SELECT * FROM (
SELECT 'JACK' AS VALUE FROM DUAL
UNION
SELECT 'JOHN' AS VALUE FROM DUAL
UNION
SELECT 'BOB' AS VALUE FROM DUAL
) WHERE VALUE NOT IN (SELECT 'BOB,JOHN' FROM DUAL);
I have a table that holds a delimited string that I want to use as the criteria to exclude records from the dataset. However, the problem I have is that the returned string is not broken down into its delimited items. I want the above to translate to:
SELECT * FROM (
SELECT 'JACK' AS VALUE FROM DUAL
UNION
SELECT 'JOHN' AS VALUE FROM DUAL
UNION
SELECT 'BOB' AS VALUE FROM DUAL
) WHERE VALUE NOT IN ('BOB','JOHN');
You can use regexp_substr for that problem:
SELECT * FROM (
SELECT 'JACK' AS VALUE FROM DUAL
UNION
SELECT 'JOHN' AS VALUE FROM DUAL
UNION
SELECT 'BOB' AS VALUE FROM DUAL
)
WHERE VALUE NOT IN (SELECT regexp_substr('BOB,JOHN','[^,]+', 1, LEVEL) FROM dual CONNECT BY regexp_substr('BOB,JOHN', '[^,]+', 1, LEVEL) IS NOT NULL)
'BOB,JOHN' is not a list of two string values it is one string value that just happens to contain a comma in the string and:
'JACK' = 'BOB,JOHN'
'JOHN' = 'BOB,JOHN'
'BOB' = 'BOB,JOHN'
Are all false so your query will return all rows as matched by the NOT IN filter.
You can surround your values and list with the delimiter characters and test whether the value is not a sub-string of the list like this:
SELECT *
FROM (
SELECT 'JACK' AS VALUE FROM DUAL UNION ALL
SELECT 'JOHN' AS VALUE FROM DUAL UNION ALL
SELECT 'BOB' AS VALUE FROM DUAL
)
WHERE INSTR( ',' || 'BOB,JOHN' || ',', ',' || value || ',' ) = 0
Or you can use a user-defined collection:
CREATE OR REPLACE TYPE stringlist IS TABLE OF VARCHAR2(20);
Then use the MEMBER OF operator to test whether a value is a member of the collection:
SELECT *
FROM (
SELECT 'JACK' AS VALUE FROM DUAL UNION ALL
SELECT 'JOHN' AS VALUE FROM DUAL UNION ALL
SELECT 'BOB' AS VALUE FROM DUAL
)
WHERE VALUE NOT MEMBER OF StringList( 'BOB', 'JOHN' );

Resources