Select numbers where not exist with a specific code - sql

I want to select all numbers where COD = 'GV' but also select the numbers that do not exist with code 'GV'... number 4170 for example...
COD NUM
---- ----
GV 4168
SERV 4168
GV 4169
SERV 4169
SERV 4170
SERV 4171
GV 4171

Literally, transcribed what you asked for:
-- numbers for COD = GV
select num
from your_table
where cod = 'GV'
union
-- numbers for COD <> GV
select num
from your_table
where cod <> 'GV
Simplified:
select distinct num
from your_table;
as you, basically, want all numbers, regardless of the COD value.

You can use a partitioned outer join:
WITH bounds ( min_num, max_num ) AS (
SELECT MIN( num ),
MAX( num )
FROM test_data
)
SELECT t.COD,
n.NUM
FROM (
SELECT min_num + LEVEL - 1 AS num
FROM bounds
CONNECT BY LEVEL <= max_num - min_num + 1
) n
LEFT OUTER JOIN test_data t
PARTITION BY ( t.COD )
ON ( n.NUM = t.NUM )
WHERE t.NUM IS NULL
AND t.COD = 'GV';
Which, for your sample data:
CREATE TABLE test_data ( COD, NUM ) AS
SELECT 'GV', 4168 FROM DUAL UNION ALL
SELECT 'SERV', 4168 FROM DUAL UNION ALL
SELECT 'GV', 4169 FROM DUAL UNION ALL
SELECT 'SERV', 4169 FROM DUAL UNION ALL
SELECT 'SERV', 4170 FROM DUAL UNION ALL
SELECT 'SERV', 4171 FROM DUAL UNION ALL
SELECT 'GV', 4171 FROM DUAL;
Outputs:
COD | NUM
:-- | ---:
GV | 4170
db<>fiddle here

Related

How to arrange/sort string in Oracle SQL - 11g

I have a string an string "ADBDkK" and I need to sort it as "ABDDKk", like Arrays.sort() in java. I know it can be done by using PL/SQL but I need of this in Oracle SQL statement.
Input:
ADBDkK
ZXYABC
Output:
ABDDKk
ABCXYZ
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE test ( value ) AS
SELECT 'ADBDkK' FROM DUAL UNION ALL
SELECT 'ZXYABC' FROM DUAL;
Query 1:
WITH chars ( id, value, ch, lvl ) AS (
SELECT ROWNUM, value, SUBSTR( value, 1, 1 ), 1
FROM test
UNION ALL
SELECT id, value, SUBSTR( value, lvl+1, 1 ), lvl+1
FROM chars
WHERE lvl < LENGTH( value )
)
SELECT LISTAGG( ch ) WITHIN GROUP ( ORDER BY ch ) AS value
FROM chars
GROUP BY id
ORDER BY id
Results:
| VALUE |
|--------|
| ABDDKk |
| ABCXYZ |
Query 2:
SELECT LISTAGG( COLUMN_VALUE )
WITHIN GROUP ( ORDER BY COLUMN_VALUE ) AS value
FROM (
SELECT value,
ROWNUM AS id
FROM test
) t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT SUBSTR( t.value, LEVEL, 1 )
FROM DUAL
CONNECT BY LEVEL <= LENGTH( t.value )
)
AS SYS.ODCIVARCHAR2LIST
)
) c
GROUP BY t.id
ORDER BY t.id
Results:
| VALUE |
|--------|
| ABDDKk |
| ABCXYZ |
For a single string:
select listagg(regexp_substr('ADBDkK', '\w', 1 ,level),'')
within group (order by 1) from dual
connect by regexp_substr('ADBDkK', '\w', 1 ,level) is not null;
A slightly different way using a function:
CREATE OR REPLACE FUNCTION sort_string(my_string IN VARCHAR2)
RETURN VARCHAR2 IS
ret_string VARCHAR2(4000);
BEGIN
SELECT LISTAGG(regexp_substr(my_string, '\w', 1, level), '') WITHIN
GROUP(
ORDER BY 1)
INTO ret_string
FROM dual
CONNECT BY regexp_substr(my_string, '\w', 1, level) IS NOT NULL;
RETURN ret_string;
END;
Then from sqlplus:
SQL> select sort_string('ADBDkK') as RESULT from dual;
RESULT
------
ABDDKk
SQL> select sort_string('ZXYABC') as RESULT from dual;
RESULT
------
ABCXYZ
with t1 as (
select 'London Singapur' tmp from dual union all
select 'Singapur China' tmp from dual union all
select 'USA JAPAN ' tmp from dual union all
select 'JAPAN USA' tmp from dual union all
select 'Singapur London' tmp from dual
),
dst as ( select ROWNUM rwn,tmp,REGEXP_COUNT(tmp,'[^[:space:]]+') cnt_array from t1 ),
rc(id, cnt_id, tmp, CNT_ARRAY, SL) AS (
SELECT rwn id,1 cnt_id,tmp, CNT_ARRAY, regexp_substr(tmp,'[^[:space:]]+',1,1) sl FROM dst
UNION ALL
SELECT rc.id, rc.cnt_id+1 cnt_id, rc.tmp, rc.CNT_ARRAY, regexp_substr(rc.tmp,'[^[:space:]]+',1,rc.cnt_id+1) sl
FROM dst tr, rc
WHERE tr.rwn = rc.id and rc.cnt_id+1<=tr.CNT_ARRAY
),
srt as (select rc.*,row_number() over (partition by id order by id) nb,
listagg(sl,' ') WITHIN GROUP (ORDER BY sl) over(partition by id) fiel_srt
from rc)
select * from srt where nb=1

Average sum ignoring max or min

I want to use AVG to get the average of some values, but ignoring the max and min values only if they are 1.5 bellow or above the second max and min values. I will put some examples:
Example 1:
SELECT *
FROM (
SELECT 100.5 v FROM DUAL UNION
SELECT 101.5 v FROM DUAL UNION
SELECT 103.1 v FROM DUAL ) D
I need this result, ignoring the 103.1 value:
100.5
101.5
Example 2:
SELECT *
FROM (
SELECT 100.5 v FROM DUAL UNION
SELECT 101.5 v FROM DUAL UNION
SELECT 103.1 v FROM DUAL UNION
SELECT 106.2 v FROM DUAL) D
I need this result, ignoring only the 106.2 value:
100.5
101.1
103.1
Example 3:
SELECT *
FROM (
SELECT 100.0 v FROM DUAL UNION
SELECT 102.0 v FROM DUAL UNION
SELECT 103.0 v FROM DUAL UNION
SELECT 105.0 v FROM DUAL UNION
SELECT 107.0 v FROM DUAL) D
I need this result, ignoring 100.0 and 107.0 values:
102.0
103.0
105.0
When there is only two values it doesnt matter.
With the right result, I can AVG(value) correctly.
You need a combination of analytic functions (lead/lag) and conditional aggregation. Here's what I came up with. Note that I allow for multiple groups, the "adjusted" average must be computed for each group separately (a common task in statistics when you must throw out the outliers in each group, when they exist):
with
inputs ( id, val ) as (
select 101, 33 from dual union all
select 102, 23 from dual union all
select 102, 22.8 from dual union all
select 103, 30 from dual union all
select 103, 40 from dual union all
select 104, 90 from dual union all
select 104, 92 from dual union all
select 104, 92 from dual union all
select 104, 91.5 from dual union all
select 104, 91.7 from dual
)
-- End of simulated inputs (for testing only, not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select id,
avg ( case when cnt >= 3
and ( lag_val is null and lead_val - val >= 1.5
or
lead_val is null and val - lag_val >= 1.5
)
then null
else val
end
) as adjusted_avg_val
from (
select id, val, count(val) over (partition by id) as cnt,
lag ( val ) over ( partition by id order by val ) as lag_val,
lead ( val ) over ( partition by id order by val ) as lead_val
from inputs
)
group by id
;
Output:
ID ADJUSTED_AVG_VAL
--- ----------------
101 33
102 22.9
103 35
104 91.8
Try using the following combination of row_number lead and lag
with cte as (
SELECT 100.5 v FROM DUAL UNION ALL
SELECT 101.5 v FROM DUAL UNION ALL
SELECT 103.1 v FROM DUAL UNION ALL
SELECT 106.2 v FROM DUAL)
-- end of sample data
select avg(v)
from
(
select row_number() over (order by v desc) arn,
row_number() over (order by v) drn,
lag(v) over (order by v) av,
lead(v) over (order by v) dv,
v
from cte
) t
where (arn != 1 and drn != 1) or -- if they are no maximum nor minumum
(drn = 1 and v + 1.5 > dv) or -- if they are minimum
(arn = 1 and v - 1.5 < av) or -- if they are maximum
(av is null and arn < 3) or -- if there are just two ore one value
(dv is null and drn < 3) -- if there are just two ore one value
In SQL you simply just need to express the result, so ...
WITH D as(
SELECT 100.0 v FROM DUAL UNION
SELECT 102.0 FROM DUAL UNION
SELECT 103.0 FROM DUAL UNION
SELECT 105.0 FROM DUAL UNION
SELECT 107.0 FROM DUAL)
SELECT avg(v)
FROM D
where (v < (select max(v) from D )
and ((select max(v) from D )
-(select max(v) from D where v !=
(select max(v) from D ) ) > 1.5))
or
(v > (select min(v) from D )
and ((select min(v) from D )
+(select min(v) from D where v !=
(select min(v) from D ) ) > 1.5))
... should do the trick!
But thinking ahead... the below version may also be useful ;)
WITH D as(
SELECT 1 PK, 100.0 v FROM DUAL UNION
SELECT 1,102.0 FROM DUAL UNION
SELECT 1,103.0 FROM DUAL UNION
SELECT 1,105.0 FROM DUAL UNION
SELECT 1,107.0 FROM DUAL)
SELECT PK,avg(v)
FROM D
where (v < (select max(v) from D group by PK)
and ((select max(v) from D group by PK)
-(select max(v) from D where v !=
(select max(v) from D group by PK) group by PK) > 1.5))
or
(v > (select min(v) from D group by PK)
and ((select min(v) from D group by PK)
+(select min(v) from D where v !=
(select min(v) from D group by PK) group by PK) > 1.5))
GROUP BY PK
In real life though you would consider the execution plans of the above in a large dataset too (homework).
For any further clarifications, I am at your disposal via comments.
Sincerely,
Ted

Oracle/SQL - Need query that will select max value from string in each row

I need a graceful way to select the max value from a field holding a comma delimited list.
Expected Values:
List_1 | Last
------ | ------
A,B,C | C
B,D,C | D
I'm using the following query and I'm not getting what's expected.
select
list_1,
(
select max(values) WITHIN GROUP (order by 1)
from (
select
regexp_substr(list_1,'[^,]+', 1, level) as values
from dual
connect by regexp_substr(list_1, '[^,]+', 1, level) is not null)
) as last
from my_table
Anyone have any ideas to fix my query?
with
test_data ( id, list_1 ) as (
select 101, 'A,B,C' from dual union all
select 102, 'B,D,C' from dual union all
select 105, null from dual union all
select 122, 'A' from dual union all
select 140, 'A,B,B' from dual
)
-- end of simulated table (for testing purposes only, not part of the solution)
select id, list_1, max(token) as max_value
from ( select id, list_1,
regexp_substr(list_1, '([^,])(,|$)', 1, level, null, 1) as token
from test_data
connect by level <= 1 + regexp_count(list_1, ',')
and prior id = id
and prior sys_guid() is not null
)
group by id, list_1
order by id
;
ID LIST_1_ MAX_VAL
---- ------- -------
101 A,B,C C
102 B,D,C D
105
122 A A
140 A,B,B B
In Oracle 12.1 or higher, this can be re-written using the LATERAL clause:
select d.id, d.list_1, x.max_value
from test_data d,
lateral ( select max(regexp_substr(list_1, '([^,]*)(,|$)',
1, level, null, 1)) as max_value
from test_data x
where x.id = d.id
connect by level <= 1 + regexp_count(list_1, ',')
) x
order by d.id
;

Retrieve records from a specific column in oracle

S.NO id Pid
1 123 PAQ123
2 433 WSD3FF
3 565 PAS45E
4 123 PAQ123X
5 433 WSD3FFY
6 123 PAQ123Z
suppose the above is the sample records in the database.
Now I want to find out in the database whether there is any word (example PAQ123) which is repeating with some prefixes/suffixes like in (PAQ123X,PAQ123Z).
How can I write a query which would result into the above list scenario?
Oracle Setup:
CREATE TABLE table_name ( S_NO, id, Pid ) AS
SELECT 1, 123, 'PAQ123' FROM DUAL UNION ALL
SELECT 2, 433, 'WSD3FF' FROM DUAL UNION ALL
SELECT 3, 565, 'PAS45E' FROM DUAL UNION ALL
SELECT 4, 123, 'PAQ123X' FROM DUAL UNION ALL
SELECT 5, 433, 'WSD3FFY' FROM DUAL UNION ALL
SELECT 6, 123, 'PAQ123Z' FROM DUAL;
Query:
SELECT *
FROM (
SELECT t.*,
( SELECT COUNT(*)
FROM table_name x
WHERE t.id = x.id
AND LENGTH( t.Pid ) < LENGTH( x.pid )
AND INSTR( x.Pid, t.Pid ) = 1 ) AS num_matches
FROM Table_name t
)
WHERE num_matches > 0;
Output:
S_NO ID PID NUM_MATCHES
---------- ---------- ------- -----------
1 123 PAQ123 2
2 433 WSD3FF 1
If you want to get the matches then you can use a collection:
CREATE TYPE stringlist AS TABLE OF VARCHAR2(100);
/
Query:
SELECT *
FROM (
SELECT t.*,
CAST(
MULTISET(
SELECT PID
FROM table_name x
WHERE t.id = x.id
AND LENGTH( t.Pid ) < LENGTH( x.pid )
AND INSTR( x.Pid, t.Pid ) = 1
)
AS stringlist
) AS matches
FROM Table_name t
)
WHERE matches IS NOT EMPTY;
or (since I'm not sure MULTISET is in 10g):
SELECT *
FROM (
SELECT t.*,
CAST(
(
SELECT COLLECT( PID )
FROM table_name x
WHERE t.id = x.id
AND LENGTH( t.Pid ) < LENGTH( x.pid )
AND INSTR( x.Pid, t.Pid ) = 1
)
AS stringlist
) AS matches
FROM Table_name t
)
WHERE matches IS NOT EMPTY;
Output
S_NO ID PID MATCHES
---------- ---------- ------- ------------------------------------
1 123 PAQ123 TEST.STRINGLIST('PAQ123X','PAQ123Z')
2 433 WSD3FF TEST.STRINGLIST('WSD3FFY')

Oracle sql group sum

I have table With ID,Sub_ID and value coloumns
ID SUB_ID Value
100 1 100
100 2 150
101 1 100
101 2 150
101 3 200
102 1 100
SUB ID can vary from 1..maxvalue( In this example it is 3). I need Sum of values for each Sub_ID. If SUB_ID is less than MAXVALUE for a particlaur ID then it should take MAX(SUB_ID) of each ID As shown below ( In this example for ID=100 for SUB_ID 3 it should take 150 i.e 2<3 so value=150))
SUB_ID SUM(values) Remarks
1 300 (100+100+100)
2 400 (150+150+100)
3 450 (150+200+100)
This can be easily done in PL/SQL . Can we use SQL for the same using Model Clause or any other options
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableA ( ID, SUB_ID, Value ) AS
SELECT 100, 1, 100 FROM DUAL
UNION ALL SELECT 100, 2, 150 FROM DUAL
UNION ALL SELECT 101, 1, 100 FROM DUAL
UNION ALL SELECT 101, 2, 150 FROM DUAL
UNION ALL SELECT 101, 3, 200 FROM DUAL
UNION ALL SELECT 102, 1, 100 FROM DUAL
Query 1:
WITH sub_ids AS (
SELECT LEVEL AS sub_id
FROM DUAL
CONNECT BY LEVEL <= ( SELECT MAX( SUB_ID ) FROM TableA )
),
max_values AS (
SELECT ID,
MAX( VALUE ) AS max_value
FROM TableA
GROUP BY ID
)
SELECT s.SUB_ID,
SUM( COALESCE( a.VALUE, m.max_value ) ) AS total_value
FROM sub_ids s
CROSS JOIN
max_values m
LEFT OUTER JOIN
TableA a
ON ( s.SUB_ID = a.SUB_ID AND m.ID = a.ID )
GROUP BY
s.SUB_ID
Results:
| SUB_ID | TOTAL_VALUE |
|--------|-------------|
| 1 | 300 |
| 2 | 400 |
| 3 | 450 |
Try this
SELECT SUB_ID,SUM(values),
(SELECT DISTINCT SUBSTRING(
(
SELECT '+'+ CAST(values AS VARCHAR)
FROM table_Name AS T2
WHERE T2.SUB_ID = d.SUB_ID
FOR XML PATH ('')
),2,100000)[values]) as values
FROm table_Name d
GROUP BY SUB_ID
How about something like this:
select max_vals.sub_id, sum(nvl(table_vals.value,max_vals.max_value)) as sum_values
from (
select all_subs.sub_id, t1.id, max(t1.value) as max_value
from your_table t1
cross join (select sub_id from your_table) all_subs
group by all_subs.sub_id, t1.id
) max_vals
left outer join your_table table_vals
on max_vals.id = table_vals.id
and max_vals.sub_id = table_vals.sub_id
group by max_vals.sub_id;
The inner query gets you a list of all sub_id/id combinations and their fall-back values. The out query uses an nvl to use the table value if it exists and the fall-back value if it doesn't.