Dynamic Oracle Pivot_In_Clause - sql

I'm kinda stuck. I want to do a user-role-relationship pivot table and my query so far looks like this:
WITH PIVOT_DATA AS (
SELECT *
FROM
(
SELECT USERNAME, GRANTED_ROLE
FROM DBA_USERS#DB_LINK U LEFT OUTER JOIN DBA_ROLE_PRIVS#DB_LINK R
ON U.USERNAME = R.GRANTEE
)
)
SELECT *
FROM PIVOT_DATA
PIVOT
(
COUNT(GRANTED_ROLE)
FOR GRANTED_ROLE
IN('CONNECT') -- Just an example
)
ORDER BY USERNAME ASC;
It works really fine and does the job, but I don't want to write to write any role I want to search for in the pivot_in_clause, because we got like tons of them and I don't want to check every time if there are any changes.
So is there a way to write a SELECT in the pivot_in_clause? I tried it myself:
[...]
PIVOT
(
COUNT(GRANTED_ROLE)
FOR GRANTED_ROLE
IN( SELECT ROLE FROM DBA_ROLES#DB_LINK )
)
[...]
But it always gives me an ORA-00936: "missing expression" in line 1 of the whole query and I don't know why. Can't there be a SELECT in the pivot_in_clause or am I doing it wrong?

You can build dynamic query in your script,
look at this example:
variable rr refcursor
declare
bb varchar2(4000);
cc varchar2( 30000 );
begin
WITH PIVOT_DATA AS (
SELECT *
FROM
(
SELECT USERNAME, GRANTED_ROLE
FROM DBA_USERS U LEFT OUTER JOIN DBA_ROLE_PRIVS R
ON U.USERNAME = R.GRANTEE
)
)
select ''''|| listagg( granted_role, ''',''' )
within group( order by granted_role ) || '''' as x
into bb
from (
select distinct granted_role from pivot_data
)
;
cc := q'[
WITH PIVOT_DATA AS (
SELECT *
FROM
(
SELECT USERNAME, GRANTED_ROLE
FROM DBA_USERS U LEFT OUTER JOIN DBA_ROLE_PRIVS R
ON U.USERNAME = R.GRANTEE
)
)
SELECT *
FROM PIVOT_DATA
PIVOT
(
COUNT(GRANTED_ROLE)
FOR GRANTED_ROLE
IN(]' || bb || q'[) -- Just an example
)
ORDER BY USERNAME ASC]';
open :rr for cc;
end;
/
SET PAGESIZE 200
SET LINESIZE 16000
print :rr
Here is the result (only small fragment, because it is very wide and long)
-----------------------------------------------------------------------------------------------------------------------------------
USERNAME 'ADM_PARALLEL_EXECUTE_TASK' 'APEX_ADMINISTRATOR_ROLE' 'AQ_ADMINISTRATOR_ROLE' 'AQ_USER_ROLE'
------------------------------ --------------------------- ------------------------- ----------------------- ----------------------
ANONYMOUS 0 0 0 0
APEX_030200 0 0 0 0
APEX_PUBLIC_USER 0 0 0 0
APPQOSSYS 0 0 0 0
..............
IX 0 0 1 1
OWBSYS 0 0 1 1

Related

Query select with next id order

I have a table with ID and NextID like this:
MainID || NextID
1 || 2
2 || 3
3 || 5
4 || 6
5 || 4
6 || ...
... || ...
what I want to achieve is select data into like this
MainID || NextID
1 || 2
2 || 3
3 || 5
5 || 4
4 || 6
6 || ...
... || ...
what i've tried is simple query like :
SELECT * FROM 'table' ORDER BY NextID
but of course it didn't meet my needs,
I have an idea to create a temp table and insert with loop but takes too much time to complete :
WHILE #NextID IS NOT NULL
BEGIN
INSERT INTO 'table'(MainID, NextID)
SELECT MainID, NextId
FROM 'table' WHERE MainID=#NextID
END
Can anyone help me?
Thanks
Recursive cte will return rows in the order of nodes visited
with t as (
select f.*, right('00000000'+cast(f.mainId as varchar(max)),9) path
from yourtable f
where MainID=1
union all
select f.*, path + '->' + right('00000000'+cast(f.mainId as varchar(max)),9)
from t
join yourtable f on t.NextID = f.MainID
)
select *
from t
order by path
db<>fiddle
where MainId=1 is an arbitrary start. You may wish also start with
where not exists (select 1 from yourtable f2 where f2.Nextid = f.MainId)
Edit
Added explicit order by
For this particular case you may use right join with some ordering
select t2.*
from some_table t1
right join some_table t2
on t1. main_id = t2.next_id
order by case when t2.next_id is null then 9999999 else t2.main_id + t2.next_id end;
the 999999 in the "order by" part is to place last line (6, null) to the end of the output.
Good luck with adopting the query to your real data

Declare function in Oracle SQL temporary script

I have a script that I run in the Toad for Oracle (or in the PL/SQL Developer):
select distinct AC.STATUS, S.DESCRIPTION, count(*) || ' / ' || (
select count(*)
from ACC AC
where AC.STATUS is not null
) "Count"
from ACC AC
join ACC_STATUS S
on AC.STATUS = S.STATUS
group by AC.STATUS, S.DESCRIPTION
order by AC.STATUS;
so it works pretty good and shows me some result like this:
-7 Status 1 30 / 174
-6 Status 2 124 / 174
-3 Status 3 6 / 174
-2 Status 4 4 / 174
-1 Status 5 10 / 174
I want to refactor this and extract the first select (in parentheses) into a function something like:
declare function zz
return number
is
declare res number;
begin
select count(*) into res
from ACC AC
where AC.STATUS is not null;
return res;
end zz;
and then use it somehow in the script.
Is it possible? And which syntax have I use? I do not want to save the function into database – only to run it for my own need in Toad or PL/SQL Developer. (We have Oracle version 12.1.0.2.0.)
In Oracle 12c or above you can utilize WITH factoring clause as
with
function f_test return number is
retval number;
begin
select count(*)
into retval
from acc
where status is not null;
return retval;
end;
select distinct
ac.status,
s.description,
count(*) || ' / ' || f_test
from acc ac join acc_status s on ac.status = s.status
group by ac.status, s.description
order by ac.status;
Otherwise, in lower versions, no - you can't do that and you'll have to create a stored function (the one that exists in the database).
Why? You can just use analytic functions:
select AC.STATUS, S.DESCRIPTION,
count(*) || ' / ' || sum(count(*)) over () as "count"
from ACC AC join
ACC_STATUS S
on AC.STATUS = S.STATUS
group by AC.STATUS, S.DESCRIPTION
order by AC.STATUS;
You don't have to worry about NULL values, because the JOIN filters them out.

concatenation code column not found in a table using sql

I have a table with few columns and I am trying to concatenate the code column with rank. But it throws me an error by saying code not found, this is in the concatenation I am doing with rank and code. The code column is a calculated column as you can see from the query. Could anyone please point me where I am doing a mistake?
table mm
ID cal_dtm rank milestone_hier_onb tr
CNT 1/31/2020 1 RFR EEZ
table dd
ID frs lst frst lst_s cal
CNT 6/20/2018 6/28/2018 6/28/2018 1/31/2020
Query
WITH r
AS (
SELECT dd.ID,
dd.frs,
dd.lst,
dd.frst,
dd.lst_s,
dd.cal_dtm,
mm.tr,
mm.ipf_rank,
mm.milestone_hier_onb
FROM dos dd
LEFT JOIN plw mm ON dd.ID = mm.ID
AND dd.cal_dtm = mm.cal_dtm
)
SELECT *
FROM (
SELECT r.ID,
r.cal_dtm,
'lp' AS code,
r.lst_s AS planned,
r.lst AS actual,
r.tr,
r.ipf_rank AS rank,
r.milestone_hier_onb AS onb,
* * CONCAT (
r.ipf_rank,
code
) AS milestone_label * *
FROM r
WHERE r.lst IS NOT NULL
UNION
SELECT r.ID,
r.cal_dtm,
'fp' AS code,
r.frst AS planned,
r.frs AS actual,
r.tr,
r.ipf_rank AS rank,
r.milestone_hier_onb AS onb,
* * CONCAT (
r.ipf_rank,
code
) AS milestone_label * *
FROM r
WHERE r.frs IS NOT NULL
)
ORDER BY ID,
cal_dtm,
code
Expected output
ID Cal code pl Al tr milestone_label rank onb
CNT 1/31/2020 lp 6/28/2018 6/28/2018 lp 1 1
CNT 1/31/2020 fp 6/20/2018
You can't use CODE like that; it is a constant so - concatenate that constant, e.g.
... 'lp' as code,
r.ipf_rank || 'lp' as milestone_label
...
How to add space?
r.ipf_rank ||' '|| 'lp' as milestone_label
or
r.ipf_rank ||' lp' as milestone_label
Let try:
remove order by
add alias **concat( r.ipf_rank, code) => **concat( r.ipf_rank, r.code)

doing an update statement involving a join in oracle sql

I tried the following code, but it did not work
BEGIN
For i in (select BUS_RPT_ID, BUS_RPT_PRIMARY_POC_ID from BUS_RPT_DTL )
LOOP
update BUS_RPT_DTL
set BUS_RPT_DTL.BUS_RPT_PRIMARY_POC_ID = (select usr_id
from BUS_RPT_DTL b
join FNM_USR u
on LOWER(trim(u.FRST_NAME || ' ' || u.LST_NAME)) =LOWER(trim(b.BUS_RPT_PRIMARY_POC_NME))
where b.BUS_RPT_ID = i.BUS_RPT_ID
and i.BUS_RPT_PRIMARY_POC_ID is not null
);
END LOOP;
END;
i basically have a report table with a poc id and a poc name, the poc name is fillled out but i want to pull the poc id from a usr table and plug it into the poc id in the report table, can anyone help me out?
You dont need a loop. A single update statement would be sufficient.
update BUS_RPT_DTL b
set b.BUS_RPT_PRIMARY_POC_ID = (select usr_id
from FNM_USR u
on LOWER(trim(u.FRST_NAME || ' ' || u.LST_NAME)) =LOWER(trim(b.BUS_RPT_PRIMARY_POC_NME))
)
Where b.BUS_RPT_PRIMARY_POC_ID is not null
Cheers!!
create table BUS_RPT_DTL as
(select 1 bus_rpt_id, 101 bus_rpt_primary_poc_id, 'Joe Dubb' BUS_RPT_PRIMARY_POC_NME from dual union
select 2 bus_rpt_id, 202, 'Bernie Bro' BUS_RPT_PRIMARY_POC_NME from dual union
select 3 bus_rpt_id, null, 'Don Junior' BUS_RPT_PRIMARY_POC_NME from dual
)
;
create table FNM_USR as
( select 909 usr_id, 'Joe' frst_name, 'Dubb' lst_name from dual union
select 808 usr_id, 'Bernie' frst_name, 'Bro' lst_name from dual union
select 707 usr_id, 'Don' frst_name, 'Junior' lst_name from dual
)
;
select * from BUS_RPT_DTL;
update BUS_RPT_DTL b set bus_rpt_primary_poc_id = (select usr_id from fnm_usr u where LOWER(trim(u.FRST_NAME || ' ' || u.LST_NAME)) = LOWER(trim(b.BUS_RPT_PRIMARY_POC_NME)))
where BUS_RPT_PRIMARY_POC_ID is not null
;
select * from BUS_RPT_DTL;
You can alternatively use a Merge Statement, in which you can Update the column BUS_RPT_PRIMARY_POC_ID for the matching cases for your Where clause, otherwise it would Insert new rows.
MERGE INTO BUS_RPT_DTL bb
USING ( SELECT USR_ID
FROM BUS_RPT_DTL b
JOIN FNM_USR u
ON LOWER(TRIM(u.FRST_NAME || ' ' || u.LST_NAME)) =
LOWER(TRIM(b.BUS_RPT_PRIMARY_POC_NME)) b
ON ( bb.BUS_RPT_ID = b.BUS_RPT_ID AND bb.BUS_RPT_PRIMARY_POC_ID IS NOT NULL )
WHEN MATCHED THEN UPDATE SET bb.BUS_RPT_PRIMARY_POC_ID = b.USR_ID
WHEN NOT MATCHED THEN INSERT(bb.BUS_RPT_PRIMARY_POC_NME, bb.BUS_RPT_ID, bb.BUS_RPT_PRIMARY_POC_ID)
VALUES(b.BUS_RPT_PRIMARY_POC_NME , b.BUS_RPT_ID , b.BUS_RPT_PRIMARY_POC_ID );

How can i tune this select statement to prevent it from having select from same table many times?

I need to tune this sql statement provided by developer, how can I speed it up and prevent the HAVING part from select from same users table many time?
SELECT *
FROM (
SELECT t.*, ROWNUM pageination__row__123__
FROM (
SELECT *
FROM (
SELECT SUM(rcm) sum_rcm, SUM(real_amount) sum_ra, MIN(min_share) min_share, MAX(max_share) max_share, SUM(DECODE(rcm_flag, 'Y', 0, rcm)) rcm_n, b.parentid
FROM bet_total b
WHERE draw_date BETWEEN :1 AND :2
GROUP BY parentid
HAVING (
parentid
IN (
SELECT id FROM users u2,
(
SELECT u3.path
FROM users u3
WHERE u3.type = 2 AND u3.lv = 1 AND u3.tesing = 0 AND u3.user_key = :3
) q
WHERE u2.path BETWEEN q.path AND q.path || chr(to_number('FFFFFFFF', 'xxxxxxxx')) AND u2.type = 2
) AND SUM(rcm) > :4
)
) b1, users u5
WHERE (b1.parentid = u5.id AND rcm_n > 0 )
ORDER BY u5.path
) t
) t
WHERE t.pageination__row__123__ <= :5;
SQL Execution Plan
Index of the table Bet_total
Index of table users
You could try using the WITH-Clause.