Convert SQL query to pivot - sql

I can't seem able to get the logic to convert the following query to a pivot SQL. My table has 20 columns with roles on them, I'd like to convert those columns into rows so, when exported to Excel, I can filter on a single column since the values can be the same on the 20 columns. So far what I've done is convert the 20 columns into a single one and then split that single one into rows:
select distinct TASKID,
regexp_substr(t.roles,'[^|]+', 1, lines.column_value) as role
from (
select TASKID,
TRIM(ROLE1) || '|' ||
TRIM(ROLE2) || '|' ||
TRIM(ROLE3) || '|' ||
TRIM(ROLE4) || '|' ||
TRIM(ROLE5) || '|' ||
TRIM(ROLE6) || '|' ||
TRIM(ROLE7) || '|' ||
TRIM(ROLE8) || '|' ||
TRIM(ROLE9) || '|' ||
TRIM(ROLE10) || '|' ||
TRIM(ROLE11) || '|' ||
TRIM(ROLE12) || '|' ||
TRIM(ROLE13) || '|' ||
TRIM(ROLE14) || '|' ||
TRIM(ROLE15) || '|' ||
TRIM(ROLE16) || '|' ||
TRIM(ROLE17) || '|' ||
TRIM(ROLE18) || '|' ||
TRIM(ROLE19) || '|' ||
TRIM(ROLE20) as roles
from menu_roles
where RLTYPE='58'
) t,
TABLE(CAST(MULTISET(select LEVEL from dual connect by instr(t.roles, '|', 1, LEVEL - 1) > 0) as sys.odciNumberList)) lines
where regexp_substr(t.roles,'[^|]+', 1, lines.column_value) is not null
order by regexp_substr(t.roles,'[^|]+', 1, lines.column_value)
I'd understand that using PIVOT would be more efficient vs concatenating and splitting a string.
Thank you!

You appear to want UNPIVOT:
SELECT task_id,
role
FROM menu_roles
UNPIVOT ( role FOR role_number IN ( ROLE1, ROLE2, ROLE3, ROLE4 /*, ... */ ) );
Or, using UNION ALL:
SELECT task_id, role1 AS role FROM menu_roles
UNION ALL SELECT task_id, role2 AS role FROM menu_roles
UNION ALL SELECT task_id, role3 AS role FROM menu_roles
UNION ALL SELECT task_id, role4 AS role FROM menu_roles
-- ...

Related

Search and return multiple wildcard values in a single column

All -
I'm looking at a large volume of sql query history data. Ultimately I need to create a distinct list of tables used by each from a list of their executed queries. Let's say my simplified table for this example is:
create table zwork_example (
username varchar2(50),
sql_text clob);
insert into zwork_example (username, sql_text)
values ('user1', 'schema1.table1, schema1.table2, schema2.table1, schema1.table1');
insert into zwork_example (username, sql_text)
Values ('user2', 'schema1.table3, schema1.table2, schema2.table1, schema1.table6');
Does anyone have any ideas on how I can search for schema1* and return N number of table names that belong to schema1? In this example I have a particular schema I'm interested in, so I can explicitly state that schema1 is the only schema I'm interested in returning the table names from.
The output I would look for given this example is:
User Schema Tables
-------- -------- --------
User1 schema1 table1, table2
User2 schema1 table3, table2, table6
CLOB, large data set ... doesn't sound promising. Read: it'll probably take some time to get the result. For such a small data set, see if this helps.
SQL> with
2 search_for (schema) as
3 (select 'schema1' from dual),
4 temp as
5 (select distinct
6 username,
7 trim(regexp_substr(dbms_lob.substr(sql_text, 32767), '[^,]+', 1, column_value)) col
8 from zwork_example cross join
9 table(cast(multiset(select level from dual
10 connect by level <= regexp_count(sql_text, ',') + 1
11 ) as sys.odcinumberlist))
12 )
13 select
14 t.username,
15 s.schema,
16 listagg(trim(replace(t.col, s.schema ||'.', null)), ', ') within group (order by null) tables
17 from temp t join search_for s on instr(t.col, s.schema) > 0
18 group by t.username, s.schema;
USERNAME SCHEMA TABLES
---------- ------- --------------------------------------------------
user1 schema1 table1, table2
user2 schema1 table2, table3, table6
SQL>
What does it do?
search_for CTE contains a schema you're searching for (schema1, right?)
temp CTE splits the sql_text into rows
moreover, in order to get distinct list of tables, I applied dbms_lob.substr to the column, hoping that you don't actually have strings longer than that (32767, that is)
final query just aggregates tables it found - rows where schemas match
For a large dataset you can use:
WITH search_string ( schema_name ) AS (
SELECT 'schema1' FROM DUAL
),
matches ( username, sql_text, schema_name, end_pos, matches ) AS (
SELECT username,
sql_text,
schema_name,
CASE
WHEN sql_text LIKE schema_name || '%'
THEN INSTR( sql_text, ',' )
WHEN sql_text NOT LIKE ', ' || schema_name || '.'
THEN 0
ELSE INSTR(
sql_text,
', ' || schema_name || '.',
INSTR( sql_text, ', ' || schema_name || '.' ) + 3
)
END,
CASE
WHEN sql_text LIKE schema_name || '%'
THEN EMPTY_CLOB() || SUBSTR(
sql_text,
LENGTH(schema_name || '.') + 1,
INSTR( sql_text, ',' ) - LENGTH(schema_name || '.') - 1
)
WHEN INSTR( sql_text, ', ' || schema_name || '.' ) = 0
THEN NULL
WHEN INSTR( sql_text, ',', INSTR( sql_text, ', ' || schema_name || '.' ) + 3 ) = 0
THEN EMPTY_CLOB() || SUBSTR(
sql_text,
INSTR( sql_text, ', ' || schema_name || '.' )
+ LENGTH(', ' || schema_name || '.')
)
ELSE EMPTY_CLOB() || SUBSTR(
sql_text,
INSTR( sql_text, ', ' || schema_name || '.' )
+ LENGTH(', ' || schema_name || '.'),
INSTR( sql_text, ',', INSTR( sql_text, ', ' || schema_name || '.' ) + 3 )
- INSTR( sql_text, ', ' || schema_name || '.' )
- LENGTH(', ' || schema_name || '.')
)
END
FROM zwork_example
CROSS JOIN search_string
UNION ALL
SELECT username,
sql_text,
schema_name,
INSTR(
sql_text,
', ' || schema_name || '.',
end_pos + 3
),
matches
|| ', '
|| CASE
WHEN INSTR( sql_text, ',', end_pos + 1 ) = 0
THEN SUBSTR(
sql_text,
end_pos + LENGTH(', ' || schema_name || '.')
)
ELSE SUBSTR(
sql_text,
end_pos + LENGTH(', ' || schema_name || '.'),
INSTR( sql_text, ',', end_pos + 1 )
- end_pos - LENGTH(', ' || schema_name || '.')
)
END
FROM matches
WHERE end_pos > 0
)
SELECT username,
matches
FROM matches
WHERE end_pos = 0;
Which, for the sample data:
create table zwork_example (
username varchar2(50),
sql_text clob
);
DECLARE
v_text CLOB;
BEGIN
insert into zwork_example (username, sql_text)
values ('user1', 'schema1.table1, schema1.table2, schema2.table1, schema1.table1');
insert into zwork_example (username, sql_text)
Values ('user2', 'schema1.table3, schema1.table2, schema2.table1, schema1.table6');
insert into zwork_example (username, sql_text)
Values ('user3', 'schema2.table3, schema3.table2, schema4.table1, schema2.table6');
insert into zwork_example (username, sql_text)
Values ('user4', 'schema2.table3, schema3.table2, schema4.table1, schema1.table6');
v_text := 'schema1.table1';
FOR i IN 2 .. 250 LOOP
v_text := v_text || ', schema1.table' || i;
END LOOP;
insert into zwork_example (username, sql_text) values ( 'user5', v_text );
END;
/
Outputs:
USERNAME
MATCHES
user3
user4
table6
user1
table1, table2, table1
user2
table3, table2, table6
user5
table1, table2, table3, table4, table5, table6, table7, table8, table9, table10, table11, table12, table13, table14, table15, table16, table17, table18, table19, table20, table21, table22, table23, table24, table25, table26, table27, table28, table29, table30, table31, table32, table33, table34, table35, table36, table37, table38, table39, table40, table41, table42, table43, table44, table45, table46, table47, table48, table49, table50, table51, table52, table53, table54, table55, table56, table57, table58, table59, table60, table61, table62, table63, table64, table65, table66, table67, table68, table69, table70, table71, table72, table73, table74, table75, table76, table77, table78, table79, table80, table81, table82, table83, table84, table85, table86, table87, table88, table89, table90, table91, table92, table93, table94, table95, table96, table97, table98, table99, table100, table101, table102, table103, table104, table105, table106, table107, table108, table109, table110, table111, table112, table113, table114, table115, table116, table117, table118, table119, table120, table121, table122, table123, table124, table125, table126, table127, table128, table129, table130, table131, table132, table133, table134, table135, table136, table137, table138, table139, table140, table141, table142, table143, table144, table145, table146, table147, table148, table149, table150, table151, table152, table153, table154, table155, table156, table157, table158, table159, table160, table161, table162, table163, table164, table165, table166, table167, table168, table169, table170, table171, table172, table173, table174, table175, table176, table177, table178, table179, table180, table181, table182, table183, table184, table185, table186, table187, table188, table189, table190, table191, table192, table193, table194, table195, table196, table197, table198, table199, table200, table201, table202, table203, table204, table205, table206, table207, table208, table209, table210, table211, table212, table213, table214, table215, table216, table217, table218, table219, table220, table221, table222, table223, table224, table225, table226, table227, table228, table229, table230, table231, table232, table233, table234, table235, table236, table237, table238, table239, table240, table241, table242, table243, table244, table245, table246, table247, table248, table249, table250
db<>fiddle here

Oracle xmlagg get correct output

I'm trying to do a dynamic pivot for some columns in a table. Normally, it goes smoothly with a listagg but in this case, since the resulting variable was greater than 3K characters, I had to go with xmlagg. However, I'm not able to get the output as
'COLUMN' as "COLUMN"
so I can then pivot the columns just I'm used to do with listagg.
SELECT
rtrim(XMLAGG(xmlelement(e, ''''
|| agr_name
|| ''' as "'
|| agr_name
|| '"', ', ').extract('//text()')
ORDER BY
agr_name
).getclobval(), ', ') agr_name
FROM
(
SELECT DISTINCT
agr_name
FROM
dat_skills
);
What I'm getting instead is
&apos;COLUMN; as "COLUMN;,
Use xmlcast - it will solve several of your problems. I don't have your table, but here is how it works on a different one:
SELECT
rtrim(xmlcast(XMLAGG(xmlelement(e, ''''
|| object_type
|| ''' as "'
|| object_type
|| '"', ', ')
ORDER BY
object_type) as clob), ', ')
object_type
FROM
(
SELECT DISTINCT
object_type
FROM
user_objects
);
OBJECT_TYPE
--------------------------------------------------------------------------------
'FUNCTION' as "FUNCTION", 'INDEX' as "INDEX", 'LIBRARY' as "LIBRARY", 'LOB' as "
LOB", 'PROCEDURE' as "PROCEDURE", 'SEQUENCE' as "SEQUENCE", 'TABLE' as "TABLE",
'TYPE' as "TYPE", 'TYPE BODY' as "TYPE BODY", 'VIEW' as "VIEW"
You no longer need to extract text (cast as CLOB does that already), you don't need to worry about escaping and unescaping, and the result is a CLOB as you need it to be.
Include call to UTL_I18N.unescape_reference:
SELECT UTL_I18N.unescape_reference (
XMLAGG (XMLELEMENT (
e,
'''' || agr_name || ''' as "' || agr_name || '"',
', ').EXTRACT ('//text()')
ORDER BY agr_name).getclobval ()) agr_name
FROM (SELECT DISTINCT agr_name
FROM dat_skills);
With:
SQL> SELECT UTL_I18N.unescape_reference (
2 XMLAGG (XMLELEMENT (
3 e,
4 '''' || agr_name || ''' as "' || agr_name || '"',
5 ', ').EXTRACT ('//text()')
6 ORDER BY agr_name).getclobval ()) agr_name
7 FROM (SELECT DISTINCT agr_name
8 FROM dat_skills);
AGR_NAME
--------------------------------------------------------------------------------
'ACCOUNTING' as "ACCOUNTING", 'OPERATIONS' as "OPERATIONS", 'RESEARCH' as "RESEA
RCH", 'SALES' as "SALES",
Without:
SQL> SELECT
2 XMLAGG (XMLELEMENT (
3 e,
4 '''' || agr_name || ''' as "' || agr_name || '"',
5 ', ').EXTRACT ('//text()')
6 ORDER BY agr_name).getclobval () agr_name
7 FROM (SELECT DISTINCT dname agr_name
8 FROM dept);
AGR_NAME
--------------------------------------------------------------------------------
&apos;ACCOUNTING&apos; as "ACCOUNTING", &apos;OPERATIONS&apos; as &quo
SQL>
Assuming you don't have any other characters that need unescaping, you can replace " and &apos; them with the actual quotes:
SELECT REPLACE(
REPLACE(
RTRIM(
XMLAGG(
xmlelement(
e,
'''' || agr_name || ''' as "' || agr_name || '"',
', '
).extract('//text()')
ORDER BY agr_name
).getclobval(),
', '
),
'"',
'"'
),
'&apos;',
''''
) AS agr_name
FROM (
SELECT DISTINCT
agr_name
FROM dat_skills
);
db<>fiddle here

How to remove/avoid a particular string in SQL output

Below is the SQL Query:
select '"'|| trim(COLUMN1) ||'"|"'|| trim(COLUMN2) ||'"|"'|| trim(COLUMN3) ||'"'
from TABLE1 where ....
The output I get is:
"DATA1"|""|"DATA3"
"DATA4"|""|"DATA6"
But, I want it to display the output like below:
"DATA1"||"DATA3"
"DATA4"||"DATA6"
That means, if there is some null value of a particular column, it must not display "". I hope you all got it.
Please help me achieving this, as I am automating this process where in the output file goes directly to the destination application (i.e. I won't be able to modify it manually).
Thanks!
You can use case to avoid printing anything if the column is null
select '"'|| trim(COLUMN1) || '"|' ||
case when COLUMN2 is null then '' else '"' || trim(COLUMN2) || '"' end
|| '|"' || trim(COLUMN3) ||'"'
from TABLE1 where ....
You can use NVL2( value, value_if_not_null, value_if_null ):
SELECT NVL2( COLUMN1, '"' || TRIM( COLUMN1 ) || '"', NULL )
|| '|' || NVL2( COLUMN2, '"' || TRIM( COLUMN2 ) || '"', NULL )
|| '|' || NVL2( COLUMN3, '"' || TRIM( COLUMN3 ) || '"', NULL )
FROM table1
WHERE -- ...
Or CASE:
SELECT CASE WHEN COLUMN1 IS NOT NULL THEN '"' || TRIM( COLUMN1 ) || '"' END
|| '|' || CASE WHEN COLUMN2 IS NOT NULL THEN '"' || TRIM( COLUMN2 ) || '"' END
|| '|' || CASE WHEN COLUMN3 IS NOT NULL THEN '"' || TRIM( COLUMN3 ) || '"' END
FROM table1
WHERE -- ...
replace Function
select replace('"'|| trim(COLUMN1) ||'"|"'|| trim(COLUMN2) ||'"|"'|| trim(COLUMN3) ||'"', '""','')
from TABLE1 where ....
Use
REPLACE(String, '""', '')
select REPLACE('"'|| trim(COLUMN1) ||'"|"'|| trim(COLUMN2) ||'"|"'|| trim(COLUMN3) ||'"', '""', '') from TABLE1 where ....

Refactor sql query that uses PostGIS functions

I have a query that looks like this:
SELECT *,
ST_Distance(
ST_GeographyFromText('SRID=4326;POINT(' || users.longitude || ' ' || users.latitude || ')'),
ST_GeographyFromText('SRID=4326;POINT(-84.334078 45.273343)')) as distance
FROM users
WHERE ST_DWithin(
ST_GeographyFromText('SRID=4326;POINT(' || users.longitude || ' ' || users.latitude || ')'),
ST_GeographyFromText('SRID=4326;POINT(-84.334078 45.273343)'),
2000
)
ORDER BY distance ASC;"
I've see some repetitions here. I'm wondering is there any way to make this query more readable?
Lateral join:
select *, ST_Distance(a, b) distance
from
users,
ST_GeographyFromText('SRID=4326;POINT(' || users.longitude || ' ' || users.latitude || ')') a,
ST_GeographyFromText('SRID=4326;POINT(-84.334078 45.273343)') b
where ST_DWithin(a, b, 2000)
order by distance asc;

format column headers during concat,oracle

I need to format column headers in the output of sql while using concat
Eg:
SELECT '' || to_char(sysdate,'ddmmyyyy') as DATE || ',' || ENO|| ',' || NAME|| ''
FROM EMP;
would retrieve me
ORA-00923: FROM keyword not found where expected.
Need the output as:
DATE ENO NAME
-----------------
251013 7560 RAM
251013 7561 ROSS
This format works
SELECT to_char(sysdate,'ddmmyyyy') || ',' || ENO || ',' || NAME as "DATE,ENO,NAME"
FROM EMP
but I have an issue with
ORA-00972: identifier is too long
when the length of column names inside as "" exceeds 30 characters
Eg:
SELECT to_char(sysdate,'ddmmyyyy') || ',' || ENO || ',' || NAME ||
',' || EMPLOYEE_IDENTIFICATION_NUMBER as "DATE,ENO,NAME,EMPLOYEE_IDENTIFICATION_NUMBER"
FROM EMP;
To achieve this output you have to build your query like this
SELECT to_char(sysdate,'ddmmyyyy') || ',' || ENO || ',' || NAME as "DATE,ENO,NAME" FROM EMP
You need to move the alias, if you really need it, at the end of the SELECT clause. Also the empty strings ('') can be removed:
SELECT to_char(sysdate,'ddmmyyyy') || ',' || ENO || ',' || NAME as DATE FROM EMP;