I'm writing a procedure that have to replace a set of special characters with another set of them that are accepted to an application system. How can I re-write better the following statement that I'm using in the procedure?
select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace('%BICI* "(MOTO), |X PLAY? 4G: RED&WHITE& \/<DIRETTA>','(','-'),'%','perc'),'?','.'),'|','-'),':',';'),',','.'),'<','-'),'>','-'),'&','and'),'\','-'),'/','-'),'"','-'),')','-'),'*','-')
from dual;
I can't use a recursive procedure.
Any suggestions?
Thanks!
Ilaria
This is an interesting one. Here's a working example based on Florin's example:
with trans_tbl(id, symbol, txt) as (
select 1, '(', '-' from dual union
select 2, '%', 'perc' from dual union
select 3, '?', '.' from dual union
select 4, '|', '-' from dual union
select 5, ':', ';' from dual union
select 6, ',', '.' from dual union
select 7, '<', '-' from dual union
select 8, '>', '-' from dual union
select 9, '&', 'and' from dual union
select 10, '\', '-' from dual union
select 11, '/', '-' from dual union
select 12, '"', '-' from dual union
select 13, ')', '-' from dual union
select 14, '*', '-' from dual
),
data_tbl(str) as (
select '%BICI* "(MOTO), |X PLAY? 4G: RED&WHITE& \/<DIRETTA>' from dual
),
working_tbl(str, id) as (
SELECT str, 0 id
FROM data_tbl
UNION ALL
SELECT replace(working_tbl.str,symbol,txt), trans_tbl.id
FROM working_tbl
JOIN trans_tbl
ON working_tbl.id = trans_tbl.id - 1
)
--select str, id from working_tbl;
SELECT str
from working_tbl
where id = (select max(id)
from trans_tbl);
STR
----------------------------------------------------------
percBICI- --MOTO-. -X PLAY. 4G; REDandWHITEand ---DIRETTA-
Related
have a query and test CASE that shows the number of full-time and part-time students. A full-time student is enrolled in at least 4 courses. A part-time student is enrolled in at least 1 course, but no more than 3.
Although the query appears to work it seems a bit verbose. I was wondering if there is a more succinct way to rewrite the query. In addition, I can would like to display the students first/last names with each row that meets the criteria
Perhaps with something like this?
, LISTAGG(
NVL2(s.student_id, s.last_name || ', ' || s.first_name, NULL),
'; '
) WITHIN GROUP (ORDER BY s.last_name, s.first_name) AS students
Below are my tables, data and query I would like to shorten if possible. Thanks to all who answer and for your expertise.
CREATE TABLE students(student_id, first_name, last_name) AS
SELECT 1, 'Faith', 'Aaron' FROM dual UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM dual UNION ALL
SELECT 3, 'Leslee', 'Altman' FROM dual UNION ALL
SELECT 4, 'Patty', 'Kern' FROM dual UNION ALL
SELECT 5, 'Beth', 'Cooper' FROM dual UNION ALL
SELECT 95, 'Zak', 'Despart' FROM dual UNION ALL
SELECT 96, 'Owen', 'Balbert' FROM dual UNION ALL
SELECT 97, 'Jack', 'Aprile' FROM dual UNION ALL
SELECT 98, 'Nicole', 'Kramer' FROM dual UNION ALL
SELECT 99, 'Jill', 'Coralnick' FROM dual;
CREATE TABLE student_courses (student_id,course_id) AS
SELECT 1, 1 FROM dual UNION ALL
SELECT 2, 1 FROM dual UNION ALL
SELECT 3, 1 FROM dual UNION ALL
SELECT 4, 1 FROM dual UNION ALL
SELECT 5, 1 FROM dual UNION ALL
SELECT 1, 2 FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 2 FROM dual UNION ALL
SELECT 4, 2 FROM dual UNION ALL
SELECT 5, 2 FROM dual UNION ALL
SELECT 1, 3 FROM dual UNION ALL
SELECT 2, 3 FROM dual UNION ALL
SELECT 3, 3 FROM dual UNION ALL
SELECT 4, 3 FROM dual UNION ALL
SELECT 5, 3 FROM dual UNION ALL
SELECT 97, 1 FROM dual UNION ALL
SELECT 97, 3 FROM dual UNION ALL
SELECT 97, 5 FROM dual UNION ALL
SELECT 97, 6 FROM dual UNION ALL
SELECT 98, 3 FROM dual UNION ALL
SELECT 98, 4 FROM dual UNION ALL
SELECT 98, 5 FROM dual UNION ALL
SELECT 99, 2 FROM dual UNION ALL
SELECT 99, 4 FROM dual UNION ALL
SELECT 99, 5 FROM dual UNION ALL
SELECT 99, 6 FROM dual;
WITH enrolled_student_course_counts AS (
SELECT
s.student_id
, s.first_name
, s.last_name
, COUNT(sc.course_id) AS course_count
FROM students s
LEFT JOIN student_courses sc
ON s.student_id = sc.student_id
GROUP BY
s.student_id
, s.first_name
, s.last_name
HAVING COUNT(sc.course_id) > 0
)
, student_enrollment_statuses AS (
SELECT
student_id
, first_name
, last_name
, CASE WHEN course_count >= 4 THEN 'full-time'
WHEN course_count BETWEEN 1 AND 3 THEN 'part-time'
END AS student_enrollment_status
FROM enrolled_student_course_counts
)
SELECT
UPPER(student_enrollment_status) AS student_enrollment_status
, COUNT(student_enrollment_status) AS student_enrollment_status_count
FROM student_enrollment_statuses
GROUP BY student_enrollment_status;
As you only need the numbers (and not any other data), shorten the query so that it searches only the student_courses table:
SQL> with temp as
2 (select student_id,
3 count(course_id) cnt
4 from student_courses
5 group by student_id
6 )
7 select
8 sum(case when cnt < 4 then 1 else 0 end) part_time,
9 sum(case when cnt >= 4 then 1 else 0 end) full_time
10 from temp;
PART_TIME FULL_TIME
---------- ----------
6 2
SQL>
I have this data:
app
asscs
mod_asscs
a
56
cb-56
a
67
cb-67
b
38
cb-38
a
12
12
I want to group by column 'app' and count the cases where 'mod_asscs' value is equal to concat('cb-', asscs). I also want to output the array in a separate column 'mod_asscs_array' so that the output is the following:
app
mod_asscs_array
scs_count
a
cb-56, cb-67
2
b
cb-38
1
So far this is what I have:
SELECT DISTINCT
app,
( CASE WHEN concat('cb-', asscs) = mod_asscs THEN mod_asscs || ',') AS mod_asscs_array,
COUNT( CASE WHEN concat('cb-', asscs) = mod_asscs THEN mod_asscs || ',') AS scs_count
FROM data_table
GROUP BY
app
Looks like aggregation.
Sample data:
SQL> with test (app, asscs, mod_asscs) as
2 (select 'a', 56, 'cb-56' from dual union all
3 select 'a', 67, 'cb-67' from dual union all
4 select 'b', 38, 'cb-38' from dual union all
5 select 'a', 12, '12' from dual
6 )
Query:
7 select app,
8 listagg(mod_asscs, ', ') within group (order by mod_asscs) array,
9 count(*) cnt
10 from test
11 where mod_asscs = 'cb-'|| asscs
12 group by app;
A ARRAY CNT
- -------------------- ----------
a cb-56, cb-67 2
b cb-38 1
SQL>
If you need to show all the "apps", even those with a "count" of zero, then you need conditional aggregation, something like this:
with
test (app, asscs, mod_asscs) as (
select 'a', 56, 'cb-56' from dual union all
select 'a', 67, 'cb-67' from dual union all
select 'b', 38, 'cb-38' from dual union all
select 'a', 12, '12' from dual union all
select 'c', 33, 'cb-23' from dual
)
select app,
listagg(case when mod_asscs = 'cb-' || asscs
then mod_asscs end, ', ')
within group (order by asscs) as mod_asscs_array,
count(case when mod_asscs = 'cb-' || asscs
then mod_asscs end) as scs_count
from test
group by app
order by app -- if needed
;
APP MOD_ASSCS_ARRAY SCS_COUNT
--- -------------------- ----------
a cb-56, cb-67 2
b cb-38 1
c 0
if there are duplicates in the array, do I do listagg(distinct.. and count(distinct case when ...?
You cannot use LISTAGG(DISTINCT ... as the LISTAGG function does not currently support the DISTINCT keyword; instead you need to use DISTINCT first in a sub-query and then use LISTAGG:
SELECT app,
LISTAGG(mod_asscs, ',') WITHIN GROUP (ORDER BY mod_asscs)
AS mod_asscs_array,
COUNT(*) AS scs_count
FROM (
SELECT DISTINCT
app,
mod_asscs
FROM data_table
WHERE 'cb-' || asscs = mod_asscs
)
GROUP BY app
Which, for the sample data:
CREATE TABLE data_table (app, asscs, mod_asscs) AS
SELECT 'a', 56, 'cb-56' FROM DUAL UNION ALL
SELECT 'a', 56, 'cb-56' FROM DUAL UNION ALL
SELECT 'a', 67, 'cb-67' FROM DUAL UNION ALL
SELECT 'b', 38, 'cb-38' FROM DUAL UNION ALL
SELECT 'a', 12, '12' FROM DUAL;
Outputs:
APP
MOD_ASSCS_ARRAY
SCS_COUNT
a
cb-56,cb-67
2
b
cb-38
1
db<>fiddle here
(SELECT LISTAGG(EVENT_DESC, ',') WITHIN GROUP (ORDER BY EVENT_DESC) FROM EVENT_REF WHERE EVENT_ID IN
( SELECT REGEXP_SUBSTR(AFTER_VALUE,'[^,]+', 1, level) FROM DUAL
CONNECT BY REGEXP_SUBSTR(AFTER_VALUE, '[^,]+', 1, level) IS NOT NULL
)
)
A table from which I am fetching AFTER_VALUE has values of integer which is comma seperated like
AFTER_VALUE data
Expected output
1
Event1
1,2
Event1,Event2
1,12,2,5
Event1,Event12,Event2,Event5
15,13
Event15,Event13
these are Ids in EVENT_REF table which have some description. I am trying to basically present
ex. 1,2 as Event1, Event2 and send back from query. There are multiple events so using REPLACE would be very tedious.
When using above query I'm getting error as “ORA-01722: invalid number” whenever there is more than one value in AFTER_VALUE column Ex. if there exists only one id , then the query works but for values like 1,2 or 1,13 etc it throws invalid number error.
PS: The event names are not Event1,Event2 etc , I have just put for reference.
You don't even need regular expressions for this assignment. Standard string function replace() can do the same thing, and faster. You only need an extra 'Event' at the beginning of the string, since that one doesn't "replace" anything.
Like this: (note that you don't need the with clause; I included it only for quick testing)
with
event_ref (after_value) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select after_value,
'Event' || replace(after_value, ',', ',Event') as desired_output
from event_ref
;
AFTER_VALUE DESIRED_OUTPUT
----------- -----------------------------
1 Event1
1,2 Event1,Event2
1,12,2,5 Event1,Event12,Event2,Event5
15,13 Event15,Event13
Ah,ok, looks, like you have other characters in your comma-separated list, so you can use this query:
with EVENT_REF(EVENT_ID,EVENT_DESC) as (
select 1, 'Desc 1' from dual union all
select 2, 'Desc 2' from dual union all
select 3, 'Desc 3' from dual union all
select 4, 'Desc 4' from dual union all
select 5, 'Desc 5' from dual union all
select 12, 'Desc12' from dual union all
select 13, 'Desc13' from dual union all
select 15, 'Desc15' from dual
)
select
(SELECT LISTAGG(EVENT_DESC, ',')
WITHIN GROUP (ORDER BY EVENT_DESC)
FROM EVENT_REF
WHERE EVENT_ID IN
( SELECT to_number(REGEXP_SUBSTR(AFTER_VALUE,'\d+', 1, level))
FROM DUAL
CONNECT BY level<=REGEXP_COUNT(AFTER_VALUE, '\d+')
)
)
from (
select '1' AFTER_VALUE from dual union all
select '1,2' AFTER_VALUE from dual union all
select '1,12,2,5' AFTER_VALUE from dual union all
select '15,13' AFTER_VALUE from dual
);
PS. And do not forget that to_number has 'default on conversion error' now: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/TO_NUMBER.html
There is no need to split and concatenate substrings, just use regexp_replace:
with EVENT_REF (AFTER_VALUE) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select regexp_replace(AFTER_VALUE,'(\d+)','Event\1') from EVENT_REF;
REGEXP_REPLACE(AFTER_VALUE,'(\D+)','EVENT\1')
-----------------------------------------------
Event1
Event1,Event2
Event1,Event12,Event2,Event5
Event15,Event13
Hi all I have requirement to pick records using dynamic pivoting in Oracle. I have done with my query which is working fine using "antonsPivoting" https://technology.amis.nl/2006/05/24/dynamic-sql-pivoting-stealing-antons-thunder/.
When i add the query in procedure it gives error: ORA-22905: cannot access rows from a non-nested table item.
Sample table script:
CREATE TABLE DEPARTMENT(DEPT_ID NUMBER PRIMARY KEY, DEPT_NAME VARCHAR2(25))
CREATE TABLE EMPLOYEE(EMP_ID NUMBER, EMP_NAME VARCHAR(100), DEPT_ID NUMBER , FOREIGN KEY(DEPT_ID) REFERENCES DEPARTMENT(DEPT_ID))
INSERT INTO DEPARTMENT(DEPT_ID, DEPT_NAME)
SELECT 1, 'HR' FROM DUAL
UNION ALL SELECT 2, 'OPS' FROM DUAL
UNION ALL SELECT 3, 'MKT' FROM DUAL
UNION ALL SELECT 4, 'FIN' FROM DUAL
UNION ALL SELECT 5, 'IT' FROM DUAL
UNION ALL SELECT 6, 'SERV' FROM DUAL
COMMIT;
INSERT INTO EMPLOYEE(EMP_ID, EMP_NAME, DEPT_ID)
SELECT 1, 'A', 1 FROM DUAL
UNION ALL SELECT 1, 'A', 1 FROM DUAL
UNION ALL SELECT 2, 'B', 1 FROM DUAL
UNION ALL SELECT 3, 'C', 2 FROM DUAL
UNION ALL SELECT 4, 'D', 3 FROM DUAL
UNION ALL SELECT 5, 'E', 3 FROM DUAL
UNION ALL SELECT 6, 'F', 4 FROM DUAL
UNION ALL SELECT 7, 'G', 4 FROM DUAL
UNION ALL SELECT 8, 'H', 4 FROM DUAL
UNION ALL SELECT 9, 'I', 5 FROM DUAL
UNION ALL SELECT 10, 'J', 5 FROM DUAL
UNION ALL SELECT 11, 'K', 1 FROM DUAL
UNION ALL SELECT 12, 'L', 1 FROM DUAL
COMMIT;
sample query script working fine:
SELECT D.DEPT_NAME, COUNT(E.EMP_ID) TOTAL_EMP
FROM DEPARTMENT D
LEFT JOIN EMPLOYEE E ON D.DEPT_ID= E.DEPT_ID
GROUP BY D.DEPT_NAME
;
Dynamic pivoting example working fine:
select * from table( pivot( '
SELECT D.DEPT_NAME, COUNT(E.EMP_ID) TOTAL_EMP
FROM DEPARTMENT D
LEFT JOIN EMPLOYEE E ON D.DEPT_ID= E.DEPT_ID
GROUP BY D.DEPT_NAME' ) )
;
adding in procedure which gives error:
CREATE OR REPLACE PROCEDURE GET_EMPLOYEE (P_RESULT OUT SYS_REFCURSOR)
IS
BEGIN
OPEN P_RESULT FOR
select * from table( pivot( '
SELECT D.DEPT_NAME, COUNT(E.EMP_ID) TOTAL_EMP
FROM DEPARTMENT D
LEFT JOIN EMPLOYEE E ON D.DEPT_ID= E.DEPT_ID
GROUP BY D.DEPT_NAME' ) )
;
END
;
Have you created the Pivot function ?
Please go through below link where this is already answered.
Link
I'm trying to find all the records that contains only a single ; in a column.
For example
a;sdasd
as;dasd;dasd
as;dasd;das
only a;sdasd will be returned.
I have tried %;% but it will return all the strings that contain ;.
SELECT id
FROM your_table
WHERE LENGTH(col) - LENGTH(REPLACE(col,';')) = 1;
Is one solution.
Another uses REGEXP_LIKE:
WITH q AS (SELECT 1 ID, 'a;b;c;' str FROM dual
UNION
SELECT 2, ';abc' FROM dual
UNION
SELECT 3, 'a;b;c;defg;h' FROM dual
UNION
SELECT 4, 'abcdefghi;' FROM dual
UNION
SELECT 5, 'ab;cde' FROM dual
UNION
SELECT 6, 'abcdef' FROM dual)
SELECT *
FROM q
WHERE regexp_like(str,'^[^;]*;[^;]*$');
ID STR
---------- ------------
2 ;abc
4 abcdefghi;
5 ab;cde
Ine other possibility (no String manipulation is necessary):
FROM your_table
WHERE col like '%;%' AND INSTR(col, ';', 1) = INSTR(col, ';', -1)