Pl/SQL - oracle 9i - Manual Pivoting - plsql

We have a table which has three columns in it:
Customer_name, Age_range, Number_of_people.
1 1-5 10
1 5-10 15
We need to return all the number of people in different age ranges as rows of a single query. If we search for customer #1, the query should just return one row:
Header- Age Range (1-5) Age Range (5-10)
10 15
We needed to get all the results in a single row; When I query for customer 1, the result should be only number of people in a single row group by age_range.
What would be the best way to approach this?

You need to manually perform a pivot:
SELECT SUM(CASE WHEN age_range = '5-10'
THEN number_of_people
ELSE NULL END) AS nop5,
SUM(CASE WHEN age_range = '10-15'
THEN number_of_people
ELSE NULL END) AS nop10
FROM customers
WHERE customer_name = 1;

There are easy solutions with 10g and 11g using LISTGAGG, COLLECT, or other capabilities added after 9i but I believe that the following will work in 9i.
Source (http://www.williamrobertson.net/documents/one-row.html)
You will just need to replace deptno with customer_name and ename with Number_of_people
SELECT deptno,
LTRIM(SYS_CONNECT_BY_PATH(ename,','))
FROM ( SELECT deptno,
ename,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS seq
FROM emp )
WHERE connect_by_isleaf = 1
CONNECT BY seq = PRIOR seq +1 AND deptno = PRIOR deptno
START WITH seq = 1;
DEPTNO CONCATENATED
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.

This will create a stored FUNCTION which means you can access it at any time.
CREATE OR REPLACE FUNCTION number_of_people(p_customer_name VARCHAR2)
RETURN VARCHAR2
IS
v_number_of_people NUMBER;
v_result VARCHAR2(500);
CURSOR c1
IS
SELECT Number_of_people FROM the_table WHERE Customer_name = p_customer_name;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_number_of_people;
EXIT WHEN c1%NOTFOUND;
v_result := v_result || v_number_of_people || ' ' || CHR(13);
END;
END;
To run it, use:
SELECT number_of_people(1) INTO dual;
Hope this helps, and please let me know if there are any errors, I didn't testrun the function myself.

Just do
select Number_of_people
from table
where Customer_name = 1
Are we missing some detail?

Related

Use cursor in LOOP in new QUERY

"I missing the forest through the trees..."
I want to query each column of a table which I retrieve in a FOR LOOP, but the inner query doesn't return the right thing.
Seems that the inner query not use the current column_name.
DECLARE
v_max_TS TIMESTAMP;
BEGIN
FOR cols IN (SELECT column_name FROM all_tab_cols WHERE table_name = '<tablename>')
LOOP
SELECT
MAX(CURR_TIMESTAMP) INTO v_max_TS
FROM <tablename>
WHERE cols.column_name IS NOT NULL
ORDER BY TO_TIMESTAMP(CURR_TIMESTAMP,'MM/DD/YYYY HH24:MI:SS') DESC;
dbms_output.put_line(cols.column_name || ' ' || v_max_TS);
END LOOP;
END;
Apart from the fact that your query doesn't make much sense (as Boneist wrote as a comment), that won't work as you need to use dynamic SQL (execute immediate) for such a purpose.
Here's an example based on Scott's schema. Have a look, adjust it if necessary.
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200); -- will hold the SELECT statement
3 v_max varchar2(30);
4 begin
5 for cols in (select column_name
6 from all_tab_cols
7 where table_name = 'DEPT'
8 )
9 loop
10 l_str := 'select max(' || cols.column_name ||') from dept';
11 execute immediate l_str into v_max;
12 dbms_output.put_line(cols.column_name ||': '|| v_max);
13 end loop;
14 end;
15 /
DEPTNO: 40
DNAME: SALES
LOC: NEW YORK
PL/SQL procedure successfully completed.
SQL>

join the values of two strings in a single loop

I have below Two Strings
Src Id = 1:2:3:4
Src Qty = 2:4:7:9
So I want to Insert into a Table like
Insert into tbl (Src_id,qty) values (1,2);
Insert into tbl (Src_id,qty) values (2,4);
Insert into tbl (Src_id,qty) values (3,7);
and so on for all
So How i can use a loop by using APEX_UTIL.STRING_TO_TABLE or something else to align the Src id and Qty to insert them as single row in a table
You don't need a loop (i.e. PL/SQL); everything can be done in SQL. Here's how:
SQL> with test (src_id, src_qty) as
2 (select '1:2:3:4', '2:4:7:9' from dual)
3 select regexp_substr(src_id, '[^:]+', 1, level) ||','||
4 regexp_substr(src_qty, '[^:]+', 1, level) result
5 from test
6 connect by level <= regexp_count(src_id, ':') + 1;
RESULT
---------------------------------------------------------
1,2
2,4
3,7
4,9
SQL>
I have done the Solution as below
DECLARE
l_src_arr2 APEX_APPLICATION_GLOBAL.VC_ARR2;
l_qty_arr2 APEX_APPLICATION_GLOBAL.VC_ARR2;
BEGIN
l_src_arr2 := APEX_UTIL.STRING_TO_TABLE ('1:2:3:4');
l_qty_arr2 := APEX_UTIL.STRING_TO_TABLE ('2:4:7:9');
FOR i IN 1 .. l_src_arr2.COUNT LOOP
DBMS_OUTPUT.PUT_LINE ('Src = ' || l_src_arr2 (i) || ' qty = ' || l_qty_arr2 (i));
END LOOP;
END;

Pl/sql dbms output

I'm very new to pl/sql and I cannot make this query run.
I want it to find differences between two tables and then output ID of those transactions.
Any help would be appreciated!
SET SERVEROUTPUT ON
DECLARE
diff_id varchar2(50);
diff_id2 varchar2(50);
BEGIN
FOR dcount IN
SELECT
O.transid ,
ABB.transid
into diff_id, diff_id2
FROM
(SELECT *
FROM O.transactions
AND abdate >= trunc(sysdate -3)
) O
FULL OUTER JOIN
(SELECT *
FROM ABB.transactions
AND abdate >= trunc(sysdate -3)
) ABB
ON O.transid = ABB.transid
LOOP
DBMS_OUTPUT.put_line (employee_rec.diff_id);
DBMS_OUTPUT.put_line (employee_rec.diff_id2);
END LOOP;
END;
my desired output would be id of transactions which are not in both
tables. Ie 375 and 480
Ah, yes, 375 and 480. What about 832?
Anyway: you don't need PL/SQL to do that. Would SET operators do any good? For example, if you want to fetch ID s from the first table that aren't contained in the second one, you'd use
select id from first_table
minus
select id from second_table;
Both ways?
select 'in 1st, not in 2nd' what, id
from (select id from first_table
minus
select id from second_table)
union all
select 'in 2nd, not in 1st', id
from (select id from second_table
minus
select id from first_table);
Apply additional conditions, if necessary (ABDATE column, for example).

Deleting duplicate records based on two columns concatenation values

I have a table employee has 30000 records. I need to delete duplicate records based on two columns concatenation. For example name and job, like
martin clerk
martin clerk
Below is my code:
declare
type typ_emp is table of emp%rowtype;
v_emp typ_emp;
cursor cur_emp
is
select *
from emp a
where rowid >
(select min (rowid)
from emp b
where concat (concat (b.ename, '-'), b.job) =
concat (concat (a.ename, '-'), a.job)
)
;
begin
open cur_emp;
loop
fetch cur_emp bulk collect into v_emp;
exit when v_emp.count = 0;
if v_emp.count > 0
then
for i in v_emp.first .. v_emp.last
loop
insert into backup_emp (ename, job)
values (v_emp (i).ename, v_emp (i).job)
;
end loop;
end if;
end loop;
close cur_emp;
delete
from emp s
where s.rowid >
any (select t.rowid
from emp t
where concat (concat (t.ename, '-'), t.job) =
concat (concat (s.ename, '-'), s.job));
commit;
exception
when others then
Raise;
end;
It is taking a long time to delete the records. Can anyone help me in tuning a query for this or suggest me what is the better approach.
Thanks in advance.
Creating function based index might improve your performance
CREATE INDEX concatindex ON emp (ename||'-'||job);
Delete statement would look like this
delete emp a where a.rowid > (select min(rowid) from emp b where b.ename||'-'||b.job=a.ename||'-'||a.job)
unless you need to insert deleted rows into backup table which is not clear from your question. If so I would rather bulkcollect rows into collection. Leave a comment if you need to have detailed this option.
I hope this helps.
SELECT ROWID, ename || '-' || job AS concatenation,
decode(rank() over(PARTITION BY ename || '-' || job ORDER BY ROWID), 1, 'keep', 'delete') AS to_do
FROM emp
ORDER BY ename || '-' || job, ROWID;
Here is my code changes:
cursor cur_emp
is
select *
from
(select b.*
,row_number()over(partition by concat (concat (b.ename, '-'), b.job) order by ename)cnt
from emp b
) where cnt>1;

SQL Concatenate multiple rows

I'm using Teradata, I have a table like this
ID String
123 Jim
123 John
123 Jane
321 Jill
321 Janine
321 Johan
I want to query the table so I get
ID String
123 Jim, John, Jane
321 Jill, Janine, Johan
I tried partition but there can be many names.
How do I get this result. Even, to point me in the right direction would be great.
Unfortunately there's no PIVOT in Teradata (only a TD_UNPIVOT in 14.10).
If you got luck there's an aggregate UDF at your site to do a group concat (probably low possibility).
Otherwise there are two options: recursion or aggregation.
If the maximum number of rows per id is known aggregation is normally faster. It's a lot of code, but most of it is based on cut&paste.
SELECT
id,
MAX(CASE WHEN rn = 1 THEN string END)
|| MAX(CASE WHEN rn = 2 THEN ',' || string ELSE '' END)
|| MAX(CASE WHEN rn = 3 THEN ',' || string ELSE '' END)
|| MAX(CASE WHEN rn = 4 THEN ',' || string ELSE '' END)
|| ... -- repeat up to the known maximum
FROM
(
SELECT
id, string,
ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY string) AS rn
FROM t
) AS dt
GROUP BY 1;
For large tables it's much more efficient when you materialize the result of the Derived Table in a Volatile Table first using the GROUP BY column as PI.
For recursion you should use a Volatile Table, too, as OLAP functions are not allowed in the recursive part. Using a view instead will repeatedly calculate the OLAP function and thus result in bad performance.
CREATE VOLATILE TABLE vt AS
(
SELECT
id
,string
,ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY string DESC) AS rn -- reverse order!
,COUNT(*)
OVER (PARTITION BY id) AS cnt
FROM t
) WITH DATA
UNIQUE PRIMARY INDEX(id, rn)
ON COMMIT PRESERVE ROWS;
WITH RECURSIVE cte
(id, list, rn) AS
(
SELECT
id
,CAST(string AS VARCHAR(1000)) -- define maximum size based on maximum number of rows
,rn
FROM vt
WHERE rn = cnt
UNION ALL
SELECT
vt.id
,cte.list || ',' || vt.string
,vt.rn
FROM vt
JOIN cte
ON vt.id = cte.id
AND vt.rn = cte.rn - 1
)
SELECT id, list
FROM cte
WHERE rn = 1;
There's one problem with this approach, it might need a lot of spool which is easy to see when you omit theWHERE rn = 1.
SELECT ID,
TRIM(TRAILING ',' FROM (XMLAGG(TRIM(String)|| ',' ORDER BY String) (VARCHAR(10000)))) as Strings
FROM db.table
GROUP BY 1
SQL Server 2017+ and SQL Azure: STRING_AGG
Starting with the next version of SQL Server, we can finally concatenate across rows without having to resort to any variable or XML witchery.
STRING_AGG (Transact-SQL)
SELECT ID, STRING_AGG(String, ', ') AS Strings
FROM TableName
GROUP BY ID

Resources