row and column delimiting in oracle sql - sql

I'm trying to create a sub-table to reference within a query; the data I need is a separate title and code within a CLOB field like this. Code and title are separated by the semicolon, and each pair is separated by a carriage return (represented by ~).
category
values
offender
S;Student~T;Teacher~O;Other
reporter
S;Student~T;Teacher~O;Other
I'd like to pull a table like this:
category
code
title
offender
S
Student
offender
T
Teacher
offender
O
Other
reporter
S
Student
reporter
T
Teacher
reporter
O
Other
I'm trying to wrap my head around how to use regexp_substr and can't seem to get it to work, it tells me the command isn't ended properly. The table name is "gen":
select
regexp_substr(replace(replace(replace(gen.values,CHR(10),'~'),CHR(13),'~'),';Please Select;*~~',''),'[^~]+',1,LEVEL) as code_title
from dual
CONNECT BY regexp_substr(code_title,'[^~]+',1,LEVEL) IS NOT NULL
where category = 'Offender';
Any advice (or other best practices for streamlining my query) is very much appreciated!

Table description:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
CATEGORY VARCHAR2(20)
CVALUES CLOB
Table contents:
SQL> select * from test;
CATEGORY CVALUES
-------------------- ----------------------------------------
offender S;Student
T;Teacher
O;Other
reporter S;Student
T;Teacher
O;Other
Query:
SQL> with temp as
2 (select
3 category,
4 regexp_substr(cvalues, '[^' ||chr(10) ||']+', 1, column_value) val
5 from test cross join
6 table(cast(multiset(select level from dual
7 connect by level <= regexp_count(cvalues, chr(10)) + 1
8 ) as sys.odcinumberlist))
9 )
10 select category,
11 regexp_substr(val, '^\w') code,
12 regexp_substr(val, '\w+$') title
13 from temp;
CATEGORY CODE TITLE
-------------------- ---------- --------------------
offender S Student
offender T Teacher
offender O Other
reporter S Student
reporter T Teacher
reporter O Other
6 rows selected.
SQL>

Related

How do you insert multiple copies of a record in PL/SQL based on conditions

I'm making a task tracking application in Oracle Apex. I'm having trouble figuring out how to insert multiple copies of the same record based on the RESOURCES value. When assigning a task, it may have more than one resource attached to it. I believe the delimiter in Apex is a colon ':' character. How do I insert a task record the same amount of times as there are resources attached to the task (and have each resource attached to one)? RESOURCES is of type VARCHAR2.
INSERT INTO TASKS_PM (TASK_ID,PROJECTID,START_DATE,END_DATE,DESCRIPTION,TASK_NAME,RESOURCES,PRIORITY,STATUS,PROGRESS,HAS_PARENT,PARENT_TASK)
VALUES (
null,
:P3_PROJECTID,
TO_DATE(:P3_START_DATE,'DD-MON-YYYY'),
TO_DATE(:P3_END_DATE,'DD-MON-YYYY'),
:P3_DESCRIPTION,
:P3_TASK_NAME,
:P3_RESOURCES,
:P3_PRIORITY,
:P3_STATUS,
:P3_PROGRESS,
:P3_HAS_PARENT,
:P3_PARENT_TASK);
For example, if the following resources were attached to an insert: "Bobby Joe" and "Tyrion Lannister", there would be two copies of this record made, one with Bobby Joe as the resource, and the other with Tyrion Lannister as the resource.
Thanks!
You'd split P3_RESOURCES into rows; here's one way to do it (I'm using a CTE which "simulates" some of the values you have in page items):
SQL> with temp (p3_projectid, p3_description, p3_resources) as
2 (select 1, 'Some description', 'Bobby Joe:Tyrion Lannister' from dual)
3 select p3_projectid,
4 p3_description,
5 regexp_substr(p3_resources, '[^:]+', 1, level) p3_resource
6 from temp
7 connect by level <= regexp_count(p3_resources, ':') + 1;
P3_PROJECTID P3_DESCRIPTION P3_RESOURCE
------------ ---------------- --------------------
1 Some description Bobby Joe
1 Some description Tyrion Lannister
SQL>
In your case, you'd
insert into tasks_pm (projectid, description, resources)
select :p3_projectid,
:p3_description,
regexp_substr(:p3_resources, '[^:]+', 1, level)
from dual
connect by level <= regexp_count(:p3_resources, ':') + 1;
(add other columns yourself)

How to search for comma delimited string Oracle SQL? [duplicate]

I'm using Oracle Apex 4,2. I have a table with a column in it called 'versions'. In the 'versions' column for each row there is a list of values that are separated by commas e.g. '1,2,3,4'.
I'm trying to create a Select List whose list of values will be each of the values that are separated by commas for one of the rows. What would the SQL query for this be?
Example:
Table Name: Products
Name | Versions
--------------------
myProd1 | 1,2,3
myProd2 | a,b,c
Desired output:
Two Select Lists.
The first one is obvious, I just select the name column from the products table. This way the user can select whatever product they want.
The second one is the one I'm not sure about. Let's say the user has select 'myProd1' from the first Select List. Then the second select should contain the following list of values for the user to select from: '1.0', '1.1' or '1.2'.
After reading your latest comments I understand that what you want is not an LOV but rather list item. Although it can be an LOV too. The first list item/lov will have all products only that user selects from it, e.g. Prod1, Prod2, Prod3... The second list item will have all versions converted from comma separated values as in your example to table as in my examples below. Because in my understanding user may pick only a single value per product from this list. Single product may have many values, e.g. Prod1 has values 1,2,3, 4. But user needs to select only one. Correct? This is why you need to convert comma values to table. The first query select is smth lk this:
SELECT prod_id
FROM your_prod_table
/
id
--------
myProd1
myProd2
.....
The second query should select all versions where product_id is in your_prod_table:
SELECT version FROM your_versions_table
WHERE prod_id IN (SELECT prod_id FROM your_prod_table)
/
Versions
--------
1,2,3,4 -- myProd1 values
a,b,c,d -- myProd2 values
.....
The above will return all versions for the product, e.g. all values for myProd1 etc...
Use my examples converting comma sep. values to table. Replace harcoded '1,2,3,4' with your value column from your table. Replace dual with your table name
If you need products and versions in a single query and single result then simply join/outer join (left, right join) both tables.
SELECT p.prod_id, v.version
FROM your_prod_table p
, your_versions_table v
WHERE p.prod_id = v.prod_id
/
In this case you will get smth lk this in output:
id | Values
------------------
myProd1 | 1,2,3,4
myProd2 | a,b,c,d
If you convert comma to table in above query then you will get this - all in one list or LOV:
id | Values
------------------
myProd1 | 1
myProd1 | 2
myProd1 | 3
myProd1 | 4
myProd2 | a
myProd2 | b
myProd2 | c
myProd2 | d
I hope this helps. Again, you may use LOV or list values if available in APEX. Two separate list of values - one for products other for versions - make more sense to me. In case of list items you will need two separate queries as above and it will be easier to do comma to table conversion for values/versions only. But is is up to you.
Comma to table examples:
-- Comma to table - regexp_count --
SELECT trim(regexp_substr('1,2,3,4', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <= regexp_count('1,2,3,4', ',')+1
/
-- Comma to table - Length -
SELECT trim(regexp_substr('1,2,3,4', '[^,]+', 1, LEVEL)) token
FROM dual
CONNECT BY LEVEL <= length('1,2,3,4') - length(REPLACE('1,2,3,4', ',', ''))+1
/
-- Comma to table - instr --
SELECT trim(regexp_substr('1,2,3,4', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <= instr('1,2,3,4', ',', 1, LEVEL - 1)
/
The output of all that above is the same:
STR_2_TAB
----------
1
2
3
4
Comma to table - PL/SQL-APEX example. For LOV you need SQL not PL/SQL.
DECLARE
v_array apex_application_global.vc_arr2;
v_string varchar2(2000);
BEGIN
-- Convert delimited string to array
v_array:= apex_util.string_to_table('alpha,beta,gamma,delta', ',');
FOR i in 1..v_array.count LOOP
dbms_output.put_line('Array: '||v_array(i));
END LOOP;
-- Convert array to delimited string
v_string:= apex_util.table_to_string(v_array,'|');
dbms_output.put_line('String: '||v_string);
END;
/

How to specify a limit on Postgres json_agg

I want a JSON output having the distinct values of a column and also a limited number of rows.
This is the sample table that I have in a Postgres Database:
Name Author Copies Sold
---- ------- -----------
Book1 James 10
Book2 James 10
Book3 Alex 12
Book4 James 11
Book5 Amanda 1
I want to write an SQL query that returns a list of all the unique author names and also every row but with a limit of 3
This is the SQL query that I have so far
WITH first_query AS(
SELECT * FROM sample_table LIMIT 3
)
SELECT json_build_object("all_authors",json_agg(DISTINCT(author)),
"book_details",json_agg(row_to_json(first_query))
)
FROM first_query;
This gives me the following output:
{"all_authors":["James","Alex"],
"book_details":[{"name":"Book1","author":"James","copies sold":10},
{"name":"Book2","author":"James","copies sold":10},
{"name":"Book3","author":"Alex","copies sold":12}]}
In the above output, the only Authors in the list are James and Alex. However, I want the names of all three authors but still limiting "book_details" to the first three. i.e. I want Amanda to be on the list too.
Basically, this is the output I want:
{"all_authors":["James","Alex", "Amanda"],
"book_details":[{"name":"Book1","author":"James","copies sold":10},
{"name":"Book2","author":"James","copies sold":10},
{"name":"Book3","author":"Alex","copies sold":12}]}
How do I get all distinct values of a column and still have a limit on the query?
here is how you can do it;
with cte as (
SELECT * FROM books limit 3
)
SELECT json_build_object('all_authors',json_agg(DISTINCT(author)),'book_details',(select json_agg(row_to_json(cte.*,true)) from cte))
FROM books

Return count of records in each row SQL

I have one table like this.
SQL> SELECT * FROM FRUIT;
F_NAME
----------
APPLE
PPPALEP
APLEE
PPAALLEEPP
ornPpfpP
PPdhppPP
Above one is my source table and I want to below output.If i am giving 'P' in multiform like including capital and small both.
I want to count only 'P' from each row.
OUTPUT
------
F_NAME COUNT
------ -----
APPLE 2
PPPALEP 4
APLEE 1
PPAALLEEPP 4
ornPpfpP 4
PPdhppPP 6
Thanks in advance.
Oracle has the very convenient regexp_count(). So:
select f_name, regexp_count(f_name, 'P') as cnt
from fruit;
You can count the number of occurrences by replacing P with blanks and subtracting the length of the replaced string from the original string.
select f_name,length(f_name)-length(replace(f_name,'P','')) cnt
from fruit
Edit: Per OP's comment, to count both P and p, use upper or lower when replacing the character with an empty string.
select f_name,length(f_name)-length(replace(upper(f_name),'P','')) cnt
from fruit

Comparing list of values against table

I tried to find solution for this problem for some time but without success so any help would be much appreciated. List of IDs needs to be compared against a table and find out which records exist (and one of their values) and which are non existent. There is a list of IDs, in text format:
100,
200,
300
a DB table:
ID(PK) value01 value02 value03 .....
--------------------------------------
100 Ann
102 Bob
300 John
304 Marry
400 Jane
and output I need is:
100 Ann
200 missing or empty or whatever indication
300 John
Obvious solution is to create table and join but I have only read access (DB is closed vendor product, I'm just a user). Writing a PL/SQL function also seems complicated because table has 200+ columns and 100k+ records and I had no luck with creating dynamic array of records. Also, list of IDs to be checked contains hundreds of IDs and I need to do this periodically so any solution where each ID has to be changed in separate line of code wouldn't be very useful.
Database is Oracle 10g.
there are many built in public collection types. you can leverage one of them like this:
with ids as (select /*+ cardinality(a, 1) */ column_value id
from table(UTL_NLA_ARRAY_INT(100, 200, 300)) a
)
select ids.id, case when m.id is null then '**NO MATCH**' else m.value end value
from ids
left outer join my_table m
on m.id = ids.id;
to see a list of public types on your DB, run :
select owner, type_name, coll_type, elem_type_name, upper_bound, precision, scale from all_coll_types
where elem_type_name in ('FLOAT', 'INTEGER', 'NUMBER', 'DOUBLE PRECISION')
the hint
/*+ cardinality(a, 1) */
is just used to tell oracle how many elements are in our array (if not specified, the default will be an assumption of 8k elements). just set to a reasonably accurate number.
You can transform a variable into a query using CONNECT BY (tested on 11g, should work on 10g+):
SQL> WITH DATA AS (SELECT '100,200,300' txt FROM dual)
2 SELECT regexp_substr(txt, '[^,]+', 1, LEVEL) item FROM DATA
3 CONNECT BY LEVEL <= length(txt) - length(REPLACE(txt, ',', '')) + 1;
ITEM
--------------------------------------------
100
200
300
You can then join this result to the table as if it were a standard view:
SQL> WITH DATA AS (SELECT '100,200,300' txt FROM dual)
2 SELECT v.id, dbt.value01
3 FROM dbt
4 RIGHT JOIN
5 (SELECT to_number(regexp_substr(txt, '[^,]+', 1, LEVEL)) ID
6 FROM DATA
7 CONNECT BY LEVEL <= length(txt) - length(REPLACE(txt, ',', '')) + 1) v
8 ON dbt.id = v.id;
ID VALUE01
---------- ----------
100 Ann
300 John
200
One way of tackling this is to dynamically create a common table expression that can then be included in the query. The final synatx you'd be aiming for is:
with list_of_values as (
select 100 val from dual union all
select 200 val from dual union all
select 300 val from dual union all
...)
select
lov.val,
...
from
list_of_values lov left outer join
other_data t on (lov.val = t.val)
It's not very elegant, particularly for large sets of values, but compatibility with a database on which you might have few privileges is very good.