Oracle: SQL Dynamic cursor statement - sql

I have a dynamic temporary table like below.
Table name for assumption: TB_EMP_TEMP_TABLE
Column1 | column2 | column3
Emp_NM | EMP_ID |TB_EMP_DTLS
Emp_Adr | EMP_ID |TB_EMP_DTLS
Emp_Sal | EMP_ID |TB_EMP_OTHER
The above data is retrieved as a Cursor(Emp_cursor) and i need to construct a dynamic SQL Query as below based on cursor data.
Expected Output:
SELECT TB_EMP_DTLS.EMP_NM,TB_EMP_DTLS.EMP_Adr,TB_EMP_OTHER.EMP_SAL
FROM TB_EMP_DTLS,TB_EMP_OTHER
WHERE TB_EMP_DTLS.EMP_ID=TB_EMP_OTHER.EMP_ID
I havent worked extensively on PLSQL/Cursor concepts. How the cursor can be looped to get expected output.

If i understand it right, you want column1 values selected from column3 tables joined by column2 columns.
It's not elegant but should work:
select listagg(v, ' ') within group (order by n asc) my_cursor from (
with
tb as (select distinct column3 val from tb_emp_temp_table), --tables
sl as (select distinct column3||'.'||column1 val from tb_emp_temp_table), --selected columns
pr as (select distinct column3||'.'||column2 val from tb_emp_temp_table) --predicates
select 1 n, 'SELECT' v from dual
union
select 2 n, listagg(val, ', ') within group (order by val) v from sl
union
select 3 n, 'FROM' v from dual
union
select 4 n, listagg(val, ', ') within group (order by val) v from tb
union
select 5 n, 'WHERE' v from dual
union
select 6 n, listagg(pra.val||'='||prb.val, ' AND ') within group (order by pra.val) v from pr pra, pr prb where pra.val != prb.val
)

Related

SQL server: Question about query group and auto id

i have a table which has 2 columns like this, picture is input and output:
Explain input: 2 column is 2 person who relation ship together. Exam: A relation with B, C,D,H
Output: i want to merge 2 column , with Column group ID auto and column RelationShip
ID group auto: i tried query: row_number() OVER (ORDER BY [columnA]) n
RelationShip: is group all person relatioship together and group them to 1 ID group. Exam person A,B,C,D,H realation ship together and group id auto is 1
I have a demo in https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=f81ff386b07589654ad133a8e4b30472
In my query, first i turn the data into a single column, second i count letter, next step i don't know query.
I tried recursive CTE but error (perhaps I don't understand clearly about recursive)
I want to solve this problem in sql query.
You can use a recursive CTE to assign each "column" value a number. This starts by created edges in both directions and then following them:
with pairs as (
select columnA, columnB from myTable
union -- on purpose to remove duplicates
select columnB, columnA from myTable
),
cte as (
select distinct columnA, columnA as columnB,
convert(varchar(max), ',' + columnA + ',') as visited,
1 as lev
from pairs
union all
select cte.columnA, p.columnB, concat(visited, p.columnA, ','), lev + 1
from cte join
pairs p
on cte.columnB = p.columnA
where cte.visited not like concat('%,', p.columnB, ',%') and
lev < 5
)
select cte.columnA, min(cte.columnB),
dense_rank() over (order by min(cte.columnB))
from cte
group by cte.columnA;
You can then join this in to assign a group id:
with pairs as (
select columnA, columnB from myTable
union -- on purpose to remove duplicates
select columnB, columnA from myTable
),
cte as (
select distinct columnA, columnA as columnB,
convert(varchar(max), ',' + columnA + ',') as visited,
1 as lev
from pairs
union all
select cte.columnA, p.columnB, concat(visited, p.columnA, ','), lev + 1
from cte join
pairs p
on cte.columnB = p.columnA
where cte.visited not like concat('%,', p.columnB, ',%') and
lev < 5
),
groups as (
select cte.columnA, min(cte.columnB) as min_columnB,
dense_rank() over (order by min(cte.columnB)) as group_id
from cte
group by cte.columnA
)
select t.*, g.group_id
from mytable t join
groups g
on g.columnA = t.columnA;
Here is a db<>fiddle.

Frequency of all combinations of values for certain column

I have a dataset in SQL Server 2012 with a column for id and value, like this:
[id] [value]
--------------
A 15
A 11
A 11
B 13
B 15
B 12
C 12
C 13
D 13
D 12
My goal is to get a frequency count of all combinations of [value], with two caveats:
Order doesn't matter, so [11,12,15] is not counted separately from [12,11,15]
Repeated values are counted separately, so [11,11,12,15] is counted separately from [11,12,15]
I'm interested in all combinations, of any length (not just pairs)
So the outcome would look like:
[combo] [frequency]
---------------------
11,11,15 1
12,13,15 1
12,13 2
I've seen answers here involving recursion that answer similar questions but where order counts, and answers here involving self-joins that yield pair-wise combinations. These come close but I'm not quite sure how to adapt for my specific needs.
You can use string_agg():
select vals, count(*) as frequency
from (select string_agg(value, ',') within group (order by value) as vals, id
from t
group by id
) i
group by vals;
SQL Server 2012 doesn't support string_agg() but you can use the XML hack:
select vals, count(*) as frequency
from (select id,
stuff( (select concat(',', value)
from t t2
where t2.id = i.id
for xml path ('')
), 1, 1, ''
) as vals
from (select distinct id from t) i
) i
group by vals;
Your number string is just all the values with the same id in increasing order. So I'm treating the lowest id as a canonical name for the full sequence and all its matches. This spares all the string manipulations though you can expand as necessary.
Just tag each duplicate value with a counter and then look for groups that pair up completely.
with data as (
select id, value,
row_number() over (partition by id, value) as rn
), matches as (
select l.id, r.id as match
from data l full outer join data r on
l.value = r.value and l.rn = r.rn and l.id <= r.id
group by l.id
having count(l.id) = count(*) and count(r.id) = count(*)
)
select id, count(match) as frequency
from matches
group by id;
The logic in the middle query is also easily adaptable for finding subset of values in common.
You can achieve this using CTEs and row_number functions.
DECLARE #table table(id CHAR(1), val int)
insert into #table VALUES
('A',15),
('A',11),
('A',11),
('B',13),
('B',15),
('B',12),
('C',12),
('C',13),
('D',13),
('D',12);
;WITH CTE_rnk as
(
SELECT id,val, row_number() over (partition by id order by val) as rnk
from #table
),
CTE_concat as
(
SELECT id, cast(val as varchar(100)) as val, rnk
from CTE_rnk
where rnk =1
union all
SELECT r.id, cast(concat(c.val,',',r.val) as varchar(100)) as val,r.rnk
from CTE_rnk as r
inner join CTE_concat as c
on r.rnk = c.rnk+1
and r.id = c.id
),
CTE_maxcombo as
(
SELECT id,val, row_number() over(partition by id order by rnk desc) as rnk
from CTE_concat
)
select val as combo, count(*) as frequency
from CTE_maxcombo where rnk = 1
group by val
+----------+-----------+
| combo | frequency |
+----------+-----------+
| 11,11,15 | 1 |
| 12,13 | 2 |
| 12,13,15 | 1 |
+----------+-----------+

SQL: histograms for multiple columns

Given the following table:
Column A Column B
east red
west blue
east green
I want to find out the of column values of each column and how many times each value is present in the table. Given the output above the result should look like:
A values A value counts B values B value counts
east 2 red 1
west 1 blue 1
green 1
This is achievable by running SELECT colX, count(colX) From Table GROUP BY colX for each column. This is not a scalable solution if there is a complex WHERE condition since it needs to be executed for each query.
An alternative is to execute the complex where query once and compute the aggregations in the server code. But is there a single SQL query that can compute that?
You can use window function :
select cola, count(*) over (partition by cola) as a_count,
colb, count(*) over (partition by colb) as b_count
This will count for both columns (a & b) with their values display.
You can use subqueries to aggregate, then union all and aggregate again to combine the results:
select max(a) as a, max(a_cnt) as a_cnt, max(b) as b, max(b_cnt) as b
from ((select a, count(*) as a_cnt, null as b, null as b_cnt,
row_number() over (order by count(*) desc) as seqnum
from t
group by a
) union all
(select null, null, b, count(*),
row_number() over (order by count(*) desc) as seqnum
from t
group by b
)
) ab
group by seqnum
order by seqnum;
If you are using Oracle you can use user_tab_cols to generate the SQL for all columns in your table
SELECT 'SELECT '
|| Listagg(column_name
||',count(1) over (partition by '
||column_name
||') as '
||column_name
||'_cnt', ',')
within GROUP (ORDER BY column_id)
||' FROM '
||'TEST_DATA'
FROM user_tab_cols
WHERE table_name = 'TEST_DATA'
Sample output is below
SELECT ID,count(1) over (partition by ID) as ID_cnt,VALUE,count(1) over (partition by
VALUE) as VALUE_cnt FROM TEST_DATA

convert row data into column in oracle [duplicate]

This question already has answers here:
Dynamic Pivot in Oracle's SQL
(10 answers)
Closed 4 years ago.
I have a table mapping which has column code and values as shown below.
A
B
C
D
i want to convert this row level data into columns like
column1 column2 column3 column4
A B C D
Can any one please help here.
Also i don't want to hard code my table data in the query as data might be different every day.
If you want to have a query that gives you a variable number of columns, a way could be dynamic SQL; for example, this query will build a query that does the job, no matter the number of records:
select
'select *
from
mapping
pivot ( max(code) for code in (' ||
listagg('''' || code || ''' AS column' || n, ',') within group (order by code) ||
'))'
from (select code, rownum n from mapping)
this gives this query:
select *
from
mapping
pivot ( max(code) for code in ('A' AS column1,'B' AS column2,'C' AS column3,'D' AS column4))
which gives:
COLUMN1 COLUMN2 COLUMN3 COLUMN4
------- ------- ------- -------
A B C D
1 row selected.
Now the issue is how would you use this; you can run a dynamic query with execute immediate, but here you don't know in advance the number of columns, so you can not fetch the result of this query into anything.
A different approach could be by generating an XML result, for example:
select
dbms_xmlgen.getxml(
'select *
from
mapping
pivot ( max(code) for code in (' ||
listagg('''' || code || ''' AS column' || n, ',') within group (order by code) ||
'))'
)
gives:
<?xml version="1.0"?>
<ROWSET>
<ROW>
<COLUMN1>A</COLUMN1>
<COLUMN2>B</COLUMN2>
<COLUMN3>C</COLUMN3>
<COLUMN4>D</COLUMN4>
</ROW>
</ROWSET>
from (select code, rownum n from mapping)
Check Below Query.
select listagg(CODE,' ') within group(order by CODE) CODE
from tableName
Check This Demo.
Use PIVOT:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE mapping ( code ) AS
SELECT 'A' FROM DUAL UNION ALL
SELECT 'B' FROM DUAL UNION ALL
SELECT 'C' FROM DUAL UNION ALL
SELECT 'D' FROM DUAL;
Query 1:
SELECT *
FROM (
SELECT ROW_NUMBER() OVER ( ORDER BY code ) rn,
code
FROM mapping
)
PIVOT ( MAX( code ) FOR rn IN (
1 AS column1,
2 AS column2,
3 AS column3,
4 AS coumn4
) )
Results:
| COLUMN1 | COLUMN2 | COLUMN3 | COUMN4 |
|---------|---------|---------|--------|
| A | B | C | D |

Oracle Regex - Remove duplicates including chinese

I'm trying to remove the duplicates in the results of a query involving listagg.
I'm using this syntax:
REGEXP_REPLACE (LISTAGG (PR.NAME, ',' ) WITHIN GROUP (ORDER BY 1),
'([^,]+)(,\1)+',
'\1') AS PRODUCERS
However, occurrences including chinese characters are not removed:
Any idea ?
Your regular expression does not work. If the LISTAGG output is A,A,AA then the regular expression ([^,]+)(,\1)+ does not check that it has matched a complete element of your list and will match A,A,A which is 2½ elements of the list and will give the output AA instead of the expected A,AA. Worse, if you have the string BA,BABAB,BABD then the regular expression will replace BA,BA with BA and then replace BAB,BAB with BAB and you end up with the string BABABD which does not match any of the elements of the original list.
An example demonstrating this is:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE names ( id, name ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 3, 'B' FROM DUAL UNION ALL
SELECT 4, 'C' FROM DUAL UNION ALL
SELECT 5, 'A' FROM DUAL UNION ALL
SELECT 6, 'AA' FROM DUAL UNION ALL
SELECT 7, 'A' FROM DUAL UNION ALL
SELECT 8, 'BA' FROM DUAL UNION ALL
SELECT 9, 'A' FROM DUAL
/
Query 1:
SELECT REGEXP_REPLACE (
LISTAGG (NAME, ',' ) WITHIN GROUP (ORDER BY 1),
'([^,]+)(,\1)+',
'\1'
) AS constant_sort
FROM names
Results:
| CONSTANT_SORT |
|---------------|
| AA,BA,C |
If you want to get the distinct elements then you can use DISTINCT (as per Littlefoot's answer) or you can COLLECT the values into a user-defined collection and then use the SET function to remove duplicates. You can then pass this de-duplicated collection to a table collection expression and use LISTAGG to get your output:
Oracle 11g R2 Schema Setup:
CREATE TYPE StringList IS TABLE OF VARCHAR2(4000)
/
Query 2:
SELECT (
SELECT LISTAGG( column_value, ',' )
WITHIN GROUP ( ORDER BY ROWNUM )
FROM TABLE( n.unique_names )
) AS agg_names
FROM (
SELECT SET( CAST( COLLECT( name ORDER BY NAME ) AS StringList ) )
AS unique_names
FROM names
) n
Results:
| AGG_NAMES |
|-------------|
| A,AA,B,BA,C |
Regarding your comment:
in the context of a bigger query involving a lot of join and given my begginers skills I would have no idea how to implement this model
For example, if your query was:
SELECT REGEXP_REPLACE(
LISTAGG (PR.NAME, ',' ) WITHIN GROUP (ORDER BY 1),
'([^,]+)(,\1)+',
'\1'
) AS PRODUCERS,
other_column1,
other_column2
FROM table1 pr
INNER JOIN table2 t2
ON (pr.some_condition = t2.some_condition )
WHERE t2.some_other_condition = 'TRUE'
GROUP BY other_column1, other_column2
Then you can change it to:
SELECT (
SELECT LISTAGG( COLUMN_VALUE, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
FROM TABLE( t.PRODUCERS )
) AS producers,
other_column1,
other_column2
FROM (
SELECT SET( CAST( COLLECT( PR.name ORDER BY PR.NAME ) AS StringList ) )
AS PRODUCERS,
other_column1,
other_column2
FROM table1 pr
INNER JOIN table2 t2
ON (pr.some_condition = t2.some_condition )
WHERE t2.some_other_condition = 'TRUE'
GROUP BY other_column1, other_column2
) t
(I can't see images; company policy).
Why wouldn't you remove duplicates before applying LISTAGG? Something like
select listagg(x.distinct_name, ',') within group (order by 1) producers
from (select DISTINCT name distinct_name
from some_table
) x
Another way to remove duplicates is to use window functions and case:
select listagg(case when seqnum = 1 then name end, ',') within group (order by 1) as producers
from (select . . .,
row_number() over (partition by name order by name) as seqnum
from . . .
) t
This does require modifications to the rest of the query, but you should still be able to do the rest of the aggregations and computations.