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
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
'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
--------------------------------------------------------------------------------
'ACCOUNTING' as "ACCOUNTING", 'OPERATIONS' as &quo
SQL>
Assuming you don't have any other characters that need unescaping, you can replace " and ' them with the actual quotes:
SELECT REPLACE(
REPLACE(
RTRIM(
XMLAGG(
xmlelement(
e,
'''' || agr_name || ''' as "' || agr_name || '"',
', '
).extract('//text()')
ORDER BY agr_name
).getclobval(),
', '
),
'"',
'"'
),
''',
''''
) AS agr_name
FROM (
SELECT DISTINCT
agr_name
FROM dat_skills
);
db<>fiddle here