How to Find specific values in oracle - oracle

i Have some values in my table like this,
column1
50,52,53,54,56,57,58,60,61,63,75 50,53,54 54,75
i want find values containing
53,54
returns
50,52,53,54,56,57,58,60,61,63,75 50,53,54
rows
or 50,52,53
returns
50,52,53,54,56,57,58,60,61,63,75
i tried;
WHERE Regexp_like(DIST_GRP,('53,54]'))

You can search for each number in the column and use AND to combine the filters:
SELECT *
FROM table_name
WHERE ','||column1||',' LIKE '%,54,%'
AND ','||column1||',' LIKE '%,75,%'
or
SELECT *
FROM table_name
WHERE ','||column1||',' LIKE '%,50,%'
AND ','||column1||',' LIKE '%,52,%'
AND ','||column1||',' LIKE '%,53,%'
or you can split the string and count to make sure you have all the matches:
SELECT t.*
FROM table_name t
CROSS APPLY (
SELECT 1
FROM DUAL
WHERE REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL) IN (50, 52, 53)
CONNECT BY LEVEL <= REGEXP_COUNT(t.column1, '\d+')
HAVING COUNT(DISTINCT REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL)) = 3
)
or you can create a collection data type and split the string and re-aggregate into that:
CREATE TYPE int_list AS TABLE OF INT;
then
SELECT t.*
FROM table_name t
CROSS APPLY (
SELECT CAST(
COLLECT(
TO_NUMBER(REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL))
)
AS INT_LIST
) AS list
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(t.column1, '\d+')
) l
WHERE INT_LIST(50,52,53) SUBMULTISET OF l.list;
db<>fiddle here

Related

Group By inside Rtrim(Xmlagg (Xmlelement (e,element || ',')).extract ( '//text()' ).GetClobVal(), ',')

I need to group values ​​inside a query using (or not) the command Rtrim(Xmlagg (Xmlelement (e,column || ',')).extract ( '//text()' ).GetClobVal(), ','), but I can't find any literature where explain a way to group the data inside this command. The code is very simple, as you can see below:
SELECT ID,
Rtrim(Xmlagg (Xmlelement (and, CONTRACTS || ',')).extract ( '//text()' ).GetClobVal(), ',') AS CONTRACTS
FROM TABLE_A
GROUP BY ID
The result in CONTRACTS is always repeated when the ID is found, thats ok, it´s working!
ID
CONTRACTS
876
1,1,1,2,3,3
But what I really need is this return:
ID
CONTRACTS
876
1,2,3
It´s not necessary to use the command Rtrim(Xmlagg (Xmlelement (e,column || ',')).extract ( '//text()' ).GetClobVal(), ','), instead, I just use to concatenate element with comma "," in the same column.
If anyone can help me, I would be very grateful!
If your values will fit into a VARCHAR2 data type (rather than a CLOB) then you can use a nested sub-query to get the DISTINCT values for each ID:
SELECT ID,
LISTAGG(contracts, ',') WITHIN GROUP (ORDER BY contracts) AS CONTRACTS
FROM ( SELECT DISTINCT id, contracts FROM TABLE_A)
GROUP BY ID
Or, from Oracle 19c, it is built-in to LISTAGG:
SELECT ID,
LISTAGG(DISTINCT contracts, ',') WITHIN GROUP (ORDER BY contracts) AS CONTRACTS
FROM TABLE_A
GROUP BY ID
If you want a CLOB then you can use the same technique as the first query:
SELECT ID,
Rtrim(
Xmlagg(
Xmlelement(name, CONTRACTS || ',')
ORDER BY contracts
).extract ( '//text()' ).GetClobVal(),
','
) AS CONTRACTS
FROM (SELECT DISTINCT id, contracts FROM TABLE_A)
GROUP BY ID
Which, for the sample data:
CREATE TABLE table_a (id, contracts) AS
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL;
All output:
ID
CONTRACTS
876
1,2,3
db<>fiddle here
It's much easier to do all those operation in XML functions: DBFiddle
SELECT--+ NO_XML_QUERY_REWRITE
id,
xmlquery(
'string-join(distinct-values($R/R/X/text()),",")'
passing
Xmlelement(
R,
Xmlagg(
Xmlelement (X, CONTRACTS)
order by CONTRACTS
)) as R
RETURNING CONTENT
) AS CONTRACTS
FROM TABLE_A
GROUP BY ID;
Full example with test data:
with table_a (id, contracts) AS (
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL
)
SELECT--+ NO_XML_QUERY_REWRITE
id,
xmlquery(
'string-join(distinct-values($R/R/X/text()),",")'
passing
Xmlelement(
R,
Xmlagg(
Xmlelement (X, CONTRACTS)
order by CONTRACTS
)) as R
RETURNING CONTENT
) AS CONTRACTS
FROM TABLE_A
GROUP BY ID;

Oracle adding a subquery in a CTE

I have the following setup, which works fine and generates output as expected.
I'm trying to add the locations subquery into the CTE so my output will have a random location_id for each row.
The subquery is straight forward and should work but I am getting syntax errors when I try to place it into the 'data's CTE. I was hoping someone could help me out.
CREATE TABLE employees(
employee_id NUMBER(6),
emp_name VARCHAR2(30)
);
INSERT INTO employees(
employee_id,
emp_name
) VALUES
(1, 'John Doe');
INSERT INTO employees(
employee_id,
emp_name
) VALUES
(2, 'Jane Smith');
INSERT INTO employees(
employee_id,
emp_name
) VALUES
(3, 'Mike Jones');
CREATE TABLE locations AS
SELECT level AS location_id,
'Door ' || level AS location_name
FROM dual
CONNECT BY level <=
with rws as (
select level rn from dual connect by level <= 5 ),
data as ( select e.*,round (dbms_random.value(1,5)
) n from employees e)
select employee_id,
emp_name,
trunc (sysdate) + dbms_random.value (0, 5) AS random_date
from rws
join data d on rn <= n
order by employee_id;
-- trying to make this work
with rws as ( select level rn from dual connect by level <= 5 ),
data as ( select e.*, loc.location_id = (
select location_id
from locations order by dbms_random.value()
fetch first 1 row only
),
round (dbms_random.value(1,5)
) n from employees e )
select employee_id,
emp_name,
trunc (sysdate) + dbms_random.value (0, 5) AS random_date
from rws
join data d on rn <= n
order by employee_id;
You need to alias the subquery column expression, rather than trying to assign it to a [variable] name. So instead of this:
with rws as ( select level rn from dual connect by level <= 5 ),
data as ( select e.*, loc.location_id = (
select location_id
from locations order by dbms_random.value()
fetch first 1 row only
),
round (dbms_random.value(1,5)
) n from employees e )
you would do this:
with rws as (
select level rn
from dual
connect by level <= 5
),
data as (
select e.*,
(
select location_id
from locations
order by dbms_random.value()
fetch first 1 row only
) as location_id,
round (dbms_random.value(1,5)) as n
from employees e
)
db<>fiddle
But yes, you'll get the same location_id for each row, which probably isn't what you want.
There are probably better ways to avoid it (or to approach whatever you're actually trying to achieve) but one option is to force the subquery to be correlated by adding something like:
where location_id != -1 * e.employee_id
db<>fiddle
although that might be expensive. It's probably worth asking a new question about that specific aspect.
I am getting the same location_id for every employee_id, which I don't want either.
The subquery is in the wrong place then; move it to the main query, and correlate against both ID and n:
with rws as (
select level rn
from dual
connect by level <= 5
),
data as (
select e.*,
round (dbms_random.value(1,5)) as n
from employees e
)
select d.employee_id,
d.emp_name,
(
select location_id
from locations
where location_id != -1 * d.employee_id * d.n
order by dbms_random.value()
fetch first 1 row only
) as location_id,
trunc (sysdate) + dbms_random.value (0, 5) AS random_date
from rws r
join data d on r.rn <= d.n
order by d.employee_id;
db<>fiddle
Or move the location part to a new CTE, I suppose, with its own row number; and join that on one of your other generated values.

sql placeholder rows

I have an apex item P_USERS which can have a value higher than the amount of rows returning from the query below.
I have a classic report which has the following query:
select
first_name,
last_name
from accounts
where account_role = 'Author'
order by account_nr;
I want placeholder rows to be added to the query (first_name = null, last_name = null etc.), if the total rows from the query is lesser than the value in the apex_item P_USERS.
Any tips on how to achieve this? Maybe with a LEFT join?
If you have more result than the minima you defined, you must add the rest with union.
Here is what you could try to adapt to your case:
SELECT i,c FROM (
select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)), (Select Rownum r From dual Connect By Rownum <= 3)
where (i(+)= r)
union select i,c from (select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)) where i>3
You may try to use a LEFT JOIN.
First, create a list of number until the limit you want like suggested here:
-- let's say you want 300 records
Select Rownum r From dual Connect By Rownum <= 300
Then you can use this to left join and have empty records:
SELECT C, R FROM
( select rownum i, c from (select 'a' c from dual union all select 'b' from dual) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r order by r
The above gives you an ordered list starting with 'a', 'b', then null until the end.
So you could adapt it to your case so:
SELECT F,L FROM
( select rownum i, f, l from (
select first_name f, last_name l
from accounts where account_role = 'Author'
order by account_nr) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r

How to get count by using UNION operator

i'm trying to get total count by using UNION operator but it gives wrong count.
select count(*) as companyRatings from (
select count(*) hrs from (
select distinct hrs from companyA
)
union
select count(*) financehrs from (
select distinct finance_hrs from companyB
)
union
select count(*) hrids from (
select regexp_substr(hr_id,'[^/]+',1,3) hrid from companyZ
)
union
select count(*) cities from (
select regexp_substr(city,'[^/]+',1,3) city from companyY
)
);
individual query's working fine but total count not matching.
individual results here: 12 19 3 6
present total count: 31
Actual total count:40.
so there is any alternate solution without UNION operator?
To add values you'd use +. UNION is to add data sets.
select
(select count(distinct hrs) from companyA)
+
(select count(distinct finance_hrs) from companyB)
+
(select count(regexp_substr(hr_id,'[^/]+',1,3)) from companyZ)
+
(select count(regexp_substr(city,'[^/]+',1,3)) from companyY)
as total
from dual;
But I agree with juergen d; you should not have separate tables per company in the first place.
Edit. Updated query using Sum
select sum(cnt) as companyRatings from
(
select count(*) as cnt from (select distinct hrs from companyA)
union all
select count(*) as cnt from (select distinct finance_hrs from companyB)
union all
select count(*) as cnt from (select regexp_substr(hr_id,'[^/]+',1,3) hrid from companyZ)
union all
select count(*) as cnt from (select regexp_substr(city,'[^/]+',1,3) city from companyY)
)
Previous answer:
Try this
SELECT (
SELECT count(*) hrs
FROM (
SELECT DISTINCT hrs
FROM companyA
)
)
+
(
SELECT count(*) financehrs
FROM (
SELECT DISTINCT finance_hrs
FROM companyB
)
)
+
(
SELECT count(*) hrids
FROM (
SELECT regexp_substr(hr_id, '[^/]+', 1, 3) hrid
FROM companyZ
)
)
+
(
SELECT count(*) cities
FROM (
SELECT regexp_substr(city, '[^/]+', 1, 3) city
FROM companyY
)
)
AS total_count
FROM dual

How to write a table literal in Oracle?

A table with one column and one row can be created with:
select 'create' as col from dual;
This can be used to build table joins:
with
a as (select 'create' as ac from dual),
b as (select 'delete' as bc from dual)
select * from a left outer join b on (ac = bc);
Now I would like to have two rows. I did it in this way:
select 'create' as col from dual
union
select 'delete' as col from dual;
But is there a more compact notation for this? I tried
select ('create', 'delete') as col from dual;
but it does not work.
You can use collection type and TABLE operator, for example (works in Oracle 10g):
SQL> SELECT column_value FROM TABLE(SYS.ODCIVARCHAR2LIST('abc', 'def', 'ghi'));
COLUMN_VALUE
--------------------------------------------------------------------------------
abc
def
ghi
A couple of ways to generate rows. You could use rownum against a table with a larger number of rows:
SELECT roWnum AS a
FROM user_objects
WHERE rownum <= 3
You could use a hierarchical query:
SELECT level AS a
FROM dual
CONNECT BY LEVEL <= 3
EDIT: change int sequence to alpha sequence:
SELECT CHR( ASCII('a') + level - 1 )
FROM dual
CONNECT BY LEVEL <= 3

Resources