How to get different table columns using the same Select statement - oracle

Hi have a table with 9 columns having the same datatype (all are percentage values). I'm trying to create one Select statement in a PL\SQL function to return tuple of 3, depending on an external parameter value.
Syntactically, something like this:
WITH tmp
AS (SELECT '1' col1,
'2' col2,
'3' col3,
'4' col4,
'5' col5,
'6' col6,
'7' col7,
'8' col8,
'9' col9
FROM DUAL
UNION
SELECT '10' col1,
'20' col2,
'30' col3,
'40' col4,
'50' col5,
'60' col6,
'70' col7,
'80' col8,
'90' col9
FROM DUAL
UNION
SELECT '100' col1,
'200' col2,
'300' col3,
'400' col4,
'500' col5,
'600' col6,
'700' col7,
'800' col8,
'900' col9
FROM DUAL)
SELECT CASE
WHEN externaparameter = 1 THEN (col1, col2, col3)
WHEN externaparameter = 2 THEN (col4, col5, col6)
WHEN externaparameter = 3 THEN (col7, col8, col9)
END
INTO var1, var2, var3
FROM tmp;
I have two solutions for this:
Implement CASE statements for each column. But it will create a large select statement and maybe confusing.
SELECT CASE
WHEN externaparameter = 1 THEN col1
WHEN externaparameter = 2 THEN col4
WHEN externaparameter = 3 THEN col7
END,
CASE
WHEN externaparameter = 1 THEN col2
WHEN externaparameter = 2 THEN col5
WHEN externaparameter = 3 THEN col8
END,
CASE
WHEN externaparameter = 1 THEN col3
WHEN externaparameter = 2 THEN col6
WHEN externaparameter = 3 THEN col9
END
INTO var1, var2, var3
FROM tmp;
Or, implement three select statement, with a union. But my original query have a couple of WHERE conditions and for that case I need to repeat them.
SELECT a, b, c
INTO var1, var2, var3
FROM (SELECT col1 a, col2 b, col3 c
FROM tmp
WHERE externalparameter = 1
UNION
SELECT col4 a, col5 b, col6 c
FROM tmp
WHERE externalparameter = 2
UNION
SELECT col7 a, col8 b, col9 c
FROM tmp
WHERE externalparameter = 3)
Did I have a more clean Select statement for this problem? And what are the advantages and drawbacks for each solutions?

If you can't selectively run one of three separate queries at run time based on externalparameter then option one using the case expressions is the cleaner solution.
Problems with the union solution:
Check the query plan generated to be sure, but I imagine that it will execute three separate select statements though they are mutually exclusive. (Unless 12c is much smarter than query optimizes I've word with to date.) This is because the plan might be reused so the plan has to include all three selects to have the correct one in all cases.
The union version includes an implicit distinct which either adds work if things are already distinct in the first version. Or changes the results versus the first version.
Dynamic SQL is sometimes used in cases like this, but I think three static statements run selectively would be better than building a dynamic SQL string.

Related

Rewrite Oracle PL/SQL procedure without merge

I need to rewrite the below procedure without merge.
So basically the insert/update needs to be done without merge, any other alternative if possible.
Database is Oracle12c/12.1
CREATE OR REPLACE PROCEDURE PUSH_DATA(p_id NUMBER) IS
BEGIN
MERGE INTO push_data_temp tgt
USING(
with rcte (id, data, lvl, result)
as (
select id, data, 1,
regexp_substr(data,
'("[^"]*"|[^, "]+)', 1, 1, null, 1) result
from disp_data where id=p_id
union all
select id, data, lvl + 1,
regexp_substr(data, '("[^"]*"|[^, "]+)', 1, lvl + 1, null, 1)
from rcte
where lvl <= regexp_count(data, '("[^"]*"|[^, "]+)')
)
select *
from (
select id, lvl, replace(result,'""','') as result
from rcte
)
pivot (max(result)
FOR (lvl) IN(1 AS col1
,2 AS col2
,3 AS col3
,4 AS col4
,5 AS col5
,6 AS col6
,7 AS col7
,8 AS col8
,9 AS col9
,10 AS col10
,11 AS col11
,12 AS col12))
) src
ON (src.id = tgt.id)
WHEN MATCHED THEN UPDATE
SET col1 = src.col1
, col2 = src.col2
, col3 = src.col3
, col4 = src.col4
, col5 = src.col5
, col6 = src.col6
, col7 = src.col7
, col8 = src.col8
, col9 = src.col9
, col10 = src.col10
, col11 = src.col11
, col12 = src.col12
WHEN NOT MATCHED THEN
INSERT (id, col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12)
VALUES (src.id, src.col1, src.col2,src.col3,src.col4,src.col5,
src.col6,src.col7,src.col8,src.col9,src.col10,src.col11,src.col12);
END;
/
#sujitmohanty had helped me with the procedure.
I have modified the procedure to use normal loop over merge. Keep in mind to change to merge later as it is the best option for insert/update operations as in your case.
I made the procedure parameter to optional, if you pass an pid it will do insert/update for the particular pid or else just pass null and it will do for all.
Note: For huge data you may need to rethink on using bulk collect operation to do the insert or update ( if only required)
CREATE OR REPLACE PROCEDURE PUSH_DATA(p_id NUMBER) IS
CURSOR cur_data IS
WITH rcte (id, data, lvl, result)
AS (
SELECT id, data, 1,
regexp_substr(data,
'("[^"]*"|[^, "]+)', 1, 1, null, 1) result
FROM disp_data where id=coalesce(p_id,id)
UNION ALL
SELECT id, data, lvl + 1,
regexp_substr(data, '("[^"]*"|[^, "]+)', 1, lvl + 1, null, 1)
FROM rcte
WHERE lvl <= regexp_count(data, '("[^"]*"|[^, "]+)')
)
SELECT *
FROM (SELECT id, lvl, replace(result,'""','') as result
FROM rcte)
PIVOT (MAX(result)
FOR (lvl) IN(1 AS col1
,2 AS col2
,3 AS col3
,4 AS col4
,5 AS col5
,6 AS col6
,7 AS col7
,8 AS col8
,9 AS col9
,10 AS col10
,11 AS col11
,12 AS col12));
var cur_data%ROWTYPE;
BEGIN
OPEN cur_data;
LOOP
FETCH cur_data INTO var;
EXIT WHEN cur_data%NOTFOUND;
UPDATE push_data_temp
SET col1 = var.col1
, col2 = var.col2
, col3 = var.col3
, col4 = var.col4
, col5 = var.col5
, col6 = var.col6
, col7 = var.col7
, col8 = var.col8
, col9 = var.col9
, col10 = var.col10
, col11 = var.col11
, col12 = var.col12
WHERE ID = var.id;
IF SQL%ROWCOUNT = 0
THEN
INSERT INTO push_data_temp(id, col1, col2, col3, col4, col5,
col6, col7, col8, col9, col10, col11, col12)
VALUES (var.id, var.col1, var.col2,var.col3,var.col4,var.col5,
var.col6,var.col7,var.col8,var.col9,var.col10,var.col11,var.col12);
END IF;
END LOOP;
END;
/
Test:-
BEGIN
push_data(p_id=>null);
END;
/
Finally Dbfiddle

Splitting a row at a given column

I have a row with 12 columns. What I need to do for this application is create 2 rows, with the first 6 columns in first row, and the second 6 columns in the second row.
COL1 COL2 COL3 COL4 COL5 COL6 COL7 COL8 COL9 COL10 COL11 COL12
I need something like this:
COL1 COL2 COL3 COL4 COL5 COL6
COL7 COL8 COL9 COL10 COL11 COL12
Is this possible to achieve?
If the columns be all of the same type, then a union query might work:
SELECT COL1, COL2, COL3, COL4, COL5, COL6 FROM yourTable
UNION ALL
SELECT COL7, COL8, COL9, COL10, COL11, COL12 FROM yourTable;
Apart from tim's answer, you can also use connect by as following:
SELECT T.RN,
CASE WHEN LVL = 1 THEN COL1 ELSE COL7 END,
CASE WHEN LVL = 1 THEN COL2 ELSE COL8 END,
CASE WHEN LVL = 1 THEN COL3 ELSE COL9 END,
CASE WHEN LVL = 1 THEN COL4 ELSE COL10 END,
CASE WHEN LVL = 1 THEN COL5 ELSE COL11 END,
CASE WHEN LVL = 1 THEN COL6 ELSE COL12 END
FROM (SELECT ROWNUM RN, T.* FROM YOUR_TABLE T)
JOIN (SELECT LEVEL AS LVL
FROM DUAL CONNECT BY LEVEL <= 2) ON (1=1)
Cheers!!

Performance issue view with DENSE_RANK() in oracle

We have a requirement to get the records from the below tables with mentioned matching condition & limit the count of UNIQUE COL3 to 20K.
We have achieved this by using DENSE_RANK() logic but we observed extremely slowness in performance when we have implemented in live system.
For processing 20K UNIQUE COL3 & 60K total records, it is taking 14-15 hours to complete (The process is running over a daily JOB & select FROM view & performing DML operation to few tables based on View records).
SELECT COUNT (distinct COL3) CNT1,COUNT(1) CNT2 FROM VW_ENTP;
--20,000 60,000
Without the DENSE_RAN() logic it is finishing very fast but with DENSE_RAN() it extremely slow.
Request your help to share any recommendation to IMPROVE QUERY PERFORMANCE or any alternative approach. Thanks in advance !
Sample View Code
CREATE OR REPLACE VIEW VW_ENTP(COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8, COL9, COL10) AS
SELECT
COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8, COL9, COL10
FROM
(
SELECT
NP.NFCD COL1,
D.CLO2 COL2,
NP.COL3 COL3,
E.COL4 COL4,
EDT.COL5,
EDT.COL6,
NP. COL7,
NP. COL8,
EDT.COL9 COL9,
NP.COL10,
DENSE_RANK() OVER (ORDER BY NP.COL3) RANK
FROM ENP NP,EDTS EDT,EM E,EDT D,BDT BD
WHERE EDT.COL2=D.COL2
AND E.COL3=NP.COL3
AND E.COL7=NP.COL7
AND E.COL8=NP.COL8
AND E.APP=NP.APP
AND NP.STATUS='P'
AND NP.NFCD=D.COL1
AND E.SUSP !='YES'
AND NP.APP='EDOC'
AND TRUNC(NP.RC_DATE)<=TRUNC(BD.LAST_RUN_DATE)
)
WHERE RANK<=20000;
Note : The above used columns are indexed.
Generally if any join condition is missed it leads to poor performance. Also you're joining few other tables and any of the tables can be causing the performance issue. Suggestion for them can be given only after having look at those tables.
To confirm if dense rank is causing the issue, you can remove it and execute the select statement. If it takes less time, then you can conclude dense rank is causing the issue. But I highly suspect it'll happen.
You can achieve this by row_number() function only:
CREATE OR REPLACE VIEW VW_ENTP(COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8, COL9, COL10) AS
SELECT
COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8, COL9, COL10
FROM
(select a.*, row_number() over (order by col3) rn from (
SELECT
NP.NFCD COL1,
D.CLO2 COL2,
NP.COL3 COL3,
E.COL4 COL4,
EDT.COL5,
EDT.COL6,
NP. COL7,
NP. COL8,
EDT.COL9 COL9,
NP.COL10,
row_number() OVER (partition by np.col3 ORDER BY NP.COL3) RANK
FROM ENP NP,EDTS EDT,EM E,EDT D,BDT BD
WHERE EDT.COL2=D.COL2
AND E.COL3=NP.COL3
AND E.COL7=NP.COL7
AND E.COL8=NP.COL8
AND E.APP=NP.APP
AND NP.STATUS='P'
AND NP.NFCD=D.COL1
AND E.SUSP !='YES'
AND NP.APP='EDOC'
AND TRUNC(NP.RC_DATE)<=TRUNC(BD.LAST_RUN_DATE)
) a where rank > 1 and rank <= 20000)
;
Current Query results :
COL3 RANK
1 1
2 1
3 1
3 2
3 3
3 4
4 1
5 1
Requirement :
COL3 RANK
1 1
2 2
3 3
3 3
3 3
3 3
4 4
5 5

Oracle transpose every "n" columns into a row

I've a SQL which creates few metrics in below format and I want to transpose them into rows at every 'n'-th column for example, cut every 4 columns and convert into rows. Also there can be 'n' number of columns i.e 12, 20, 40, etc (multiples of 4 in my case).
I need to load these rows into a table.
Find below example,
Sample data.
SELECT 1 AS col1, 'Total number of Customer' AS col2, 100 AS col3, NULL AS col4,
1.1 AS col5, 'Total active customers' AS col6, 50 AS col7, NULL AS col8,
1.2 AS col9, 'Total inactive customers' AS col10, 50 AS col11,ABC AS col12
FROM DUAL;
Note: There could be n number of columns. It can be 12, 40 (multiples of 4 in my case).
You could take a union of the groups of 4 columns:
SELECT Col1 AS "S.No", Col2 AS "Metric Name", Col3 AS Count, Col4 AS Example
FROM yourTable
UNION ALL
SELECT Col5, Col6, Col7, Col8
FROM yourTable
UNION ALL
SELECT Col9, Col10, Col11, Col12
FROM yourTable
ORDER BY 1
I wonder how you ended up with such a table, but in any case I recommend using the output of the above query as the new version of your data moving forward.
Here is one solution.
with cte as (
SELECT 1 AS col1, 'Total number of Customer' AS col2, 100 AS col3, NULL AS col4,
1.1 AS col5, 'Total active customers' AS col6, 50 AS col7, NULL AS col8,
1.2 AS col9, 'Total inactive customers' AS col10, 50 AS col11,ABC AS col12
FROM DUAL
)
select col1, col2, col3, col4 from cte
union all
select col5, col6, col7, col8 from cte
union all
select col9, col10, col11, col12 from cte
order by 1
;
However, this looks like the sort of format which should really be handled in the displaying client rather than in SQL.
" there could be 'n' number of columns. It can be 12, 40 (multiples of 4 in my case) "
Then it is even more the case that it should be handled in the client. Because the only way to implement an arbitrary projection is to use dynamic SQL: write a PL/SQL function to generate a bespoke SQL statement for a paramterised query and number of columns. The snag is, you will have to return a weak ref cursor, because the projection is unknown at compile time.

Query with a "With" Clause inside a For Loop Cursor of DB2 PL SQL

I have a very complex query that includes a "With" clause. This query works fine when executed on the DB2 Client. But if the same query is used inside a For Loop Cursor of a PL SQL stored procedure it does not work. On trying to apply the stored procedure to the database, it gives a syntax error as shown below.
SQL0104N An unexpected token "AS" was found following "col5 )
The for loop is as shown below.
FOR records AS cursors CURSOR FOR
(
WITH
temp1
(
col1, col2, col3, col4, col5
)
AS
(
SELECT
col1, col2, col3, col4, col5
FROM
table1
)
WITH
temp2
(
col6, col7, col8, col9, col10
)
AS
(
SELECT
col6, col7, col8, col9, col10
FROM
table2
)
SELECT col1, col2, col3, col4, col5, col6, co7, col8, col9, col10
FROM temp1, temp2
)
DO
-- Do Something here.
END FOR;
Can you please help solve this problem. Thanks in advance.
You have two problems. First, the FOR statement is incorrect; it should refer to a previously declared cursor:
...
CURSOR mycur IS WITH ... SELECT ...;
...
FOR rec IN mycur LOOP ...
Second,the query
WITH temp1 ( col1, col2, col3, col4, col5 ) AS (
SELECT col1, col2, col3, col4, col5 FROM table1
)
WITH temp2 ( col6, col7, col8, col9, col10 ) AS (
SELECT col6, col7, col8, col9, col10 FROM table2
is invalid and would never run by itself, so your claim that it executes in the CLP is untrue.
Finally I got this thing working. The solution was fairly simple. It was to remove the braces enclosing the query as shown below.
FOR records AS cursors CURSOR FOR
WITH
temp1
(
col1, col2, col3, col4, col5
)
AS
(
SELECT
col1, col2, col3, col4, col5
FROM
table1
)
WITH
temp2
(
col6, col7, col8, col9, col10
)
AS
(
SELECT
col6, col7, col8, col9, col10
FROM
table2
)
SELECT col1, col2, col3, col4, col5, col6, co7, col8, col9, col10
FROM temp1, temp2
DO
-- Do Something here.
END FOR ;
I am not really sure why it happens like this. It works just fine for any other normal query without a WITH clause in it.

Resources