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 |
Related
I know I've seen this before, but can't come up with the search terms to find it.
I have a CTE returning a comma separated list from the below table:
Table
Create table Table
(
ID Number
, Name varchar2(100)
);
insert all
into Table (ID, Name) values (1, 'Alex')
into Table (ID, Name) values (2, 'Amy')
into Table (ID, Name) values (3, 'Jim')
select * from dual;
ID
Name
1
Alex
2
Amy
3
Jim
select substr(
listagg(Table.ID || ',') within group (order by null)
, 1
, length(listagg(Table.ID || ',') within group (order by null)) - 1
) IDs
from Table
where Name like 'A%'
Which gives me the results: 1,2
I'm trying to use this result in a query's in clause:
with CTE as
(
select substr(
listagg(tbl.ID || ',') within group (order by null)
, 1
, length(listagg(tbl.ID || ',') within group (order by null)) - 1
) IDs
from Table
where Name like 'A%'
)
select *
from Table
where cast(ID as varhcar2(1000)) in (select IDs from CTE) --Use results here
--believe the cast is required to compare, otherwise get a ORA-01722: invalid number
Which I want to return:
ID
Name
1
Alex
2
Amy
How can I use the CTE's resulting IDs string as the parameter of my in clause?
I'm afraid I don't understand your "problem". CTE is really strange; SUBSTR of something? Why? LISTAGG returns the same result anyway. Then you want to ... what? split that result so that you could use it in another query? As if you want to make it as complex as possible (and beyond) to solve something "simple". Therefore: what real problem are you trying to solve?
Anyway, here you go: you'll have to split aggregated string into rows if you want to use it in IN clause:
SQL> with CTE as
2 (select listagg(ID || ',') within group (order by null) IDs
3 from Test
4 where Name like 'A%'
5 )
6 select *
7 from Test
8 where id in (select regexp_substr(IDs, '[^,]+', 1, level)
9 from CTE
10 connect by level <= regexp_count(IDS, ',') + 1
11 );
ID NAME
---------- ----------
1 Alex
2 Amy
SQL>
The same result is returned by a simple
SQL> select *
2 from Test
3 where Name like 'A%';
ID NAME
---------- ----------
1 Alex
2 Amy
SQL>
That's why I asked: what problem are you trying to solve?
[EDIT] As of trailing comma: there's none, at least not any Oracle version I used (11g, 12c, 18cXE, 21cXE):
SQL> select listagg(id, ',') within group (order by null) result from test;
RESULT
------------------------------
1,2,3
SQL> select listagg(name, ',') within group (order by null) result from test;
RESULT
------------------------------
Alex,Amy,Jim
SQL>
For tables
t1:
C1 C2 C3
1 2 3
4 5 6
table t2:
C1 C2 C4
33 44 55
What query should be written to do a union of all common columns so that the result will be this:
C1 C2
1 2
4 5
33 44
Important to note that I'm not looking for a solution for only two tables but for a solution that does this for every table in the database. DB is SQLite
SQLite does not support dynamic sql, so the most that you can do by using its own capabilities is construct a SQL statement that would do what you want and you can execute it by using a programming language like Java or Python.
This query:
WITH tables AS (SELECT name FROM sqlite_master WHERE type = 'table')
SELECT pti.name col
FROM tables t CROSS JOIN pragma_table_info(t.name) pti
GROUP BY col
HAVING COUNT(*) = (SELECT COUNT(*) FROM tables);
returns all the columns that are common in all tables of the databse.
By using GROUP_CONCAT() you can get all these columns as a comma separated list which you can use in a SELECT statement:
WITH tables AS (SELECT name FROM sqlite_master WHERE type = 'table')
SELECT GROUP_CONCAT(col) columns
FROM (
SELECT pti.name col
FROM tables t CROSS JOIN pragma_table_info(t.name) pti
GROUP BY col
HAVING COUNT(*) = (SELECT COUNT(*) FROM tables)
);
Finally, concatenate the keywords 'SELECT' and 'FROM' and each table's name and with GROUP_CONCAT() once more and 'UNION ALL' (or 'UNION') as separator construct the sql statement:
WITH tables AS (SELECT name FROM sqlite_master WHERE type = 'table')
SELECT GROUP_CONCAT(sql, ' UNION ALL ') sql
FROM (
SELECT 'SELECT ' ||
(
SELECT GROUP_CONCAT(col) columns
FROM (
SELECT pti.name col
FROM tables t CROSS JOIN pragma_table_info(t.name) pti
GROUP BY col
HAVING COUNT(*) = (SELECT COUNT(*) FROM tables)
)
) || ' FROM ' || name AS sql
FROM tables
);
This will return a string like:
SELECT col1,col2 FROM table1 UNION ALL SELECT col1,col2 FROM table2 UNION ALL SELECT col1,col2 FROM table3
which you can execute.
See a simplified demo.
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.
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
)
I have table structure as below, Need the output as mentioned below
Table :
A B
CUSTOMER_TYPE_ID 4
CUSTOMER_TYPE_ID 3
CUSTOMER_TYPE_ID 2
CUSTOMER_TYPE_ID 1
CUSTOMER_TYPE_ID 0
Answer :
'4','3','2','1','0'
How to do it?
As described, this isn't a pivot but aggregate string concatenation. The Oracle function is LISTAGG():
select listagg(b, ',') within group (order by b desc) as b
from t
group by a;
EDIT:
If you want single quotes around the values:
select listagg('''' || b || '''', ',') within group (order by b desc) as b
from t
group by a;