Determine which values are missing from an Oracle database column - sql

I have a list of values and I want to query which values in that list DO NOT appear in a particular column in an Oracle database (not sure which version).
So for example if my list of values is A,B,C and I have a table as below :
--------
|COLUMN|
--------
| C|
| A|
--------
The result I would expect would be B.
So far my approach has been a SQL query similar to the below :
SELECT <<List of values SQL, not sure what goes here>>
EXCEPT
SELECT column FROM table
However I do not know what the SQL for the first statement looks like.
So far I came up with :
SELECT "A","B","C" FROM dual
But this doesn't have the desired effect as it creates 3 columns
Another point to mention is that in the actual problem there are around 100 entries in the list to search, not the three in the toy example above.

Maybe this helps:
WITH static_list AS (
SELECT 'A' AS v FROM dual UNION ALL
SELECT 'B' AS v FROM dual UNION ALL
SELECT 'C' AS v FROM dual
)
SELECT v FROM static_list
MINUS (SELECT column
FROM table);

Dirk's solution works fine. However, Regular Expressions can also be used for achieving this goal for Oracle 10g and above:
SELECT trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT 'A,B,C,D' str from dual)
CONNECT BY instr(str, ',', 1, level - 1) > 0
Minus
Select column from table

Related

Oracle Apex decoding checkbox created list of return values into display values for a report

Oracle Apex checkbox return values are saved in a table as colon delineated list:
01:02:03:04
And that is how they will appear in a report column, however I want to decode those values back into there display values:
Apple:Banana:Carrot:Durian
If this return values where from a select list I would use the following sql:
select display, return
from lov_list
where type = 'fruit'
However this obliviously returns an ORA-01722: invalid number error.
The first approach I tried was maybe nested replace functions:
replace(replace(replace(replace(fruit_column, '01', Apple), '02', 'Banana'), '03', 'Carrot'), '04', 'Durian')
This works, but is not "dynamic" as in the future I would like to be able to add new values to the lov_list table and not have to update or add more nested replace functions especially if I now have over 10 new values.
I theory what I would like to do is the following:
select replace(ft.fruit_column, ll.return, ll.display)
from fruit_table ft
left join lov_list ll
on ft.fruit_column like ('%'||ll.return||'%')
But this only works for same of the values:
Apple:02:03:04
02:Carrot
etc.
I've looked multiple replace functions that other users have created, but I don't think they'll work for particular problem, not "dynamically" anyways, unless I'm missing something in those functions and they in fact could work, I'm not sure.
Is they another approach I can take?
You should first split colon-delimited values into rows, then join those values with table that contains fruit names, and - finally - aggregate the result back into a colon-delimited list of NAMES.
SQL> with
2 chbox (val) as
3 (select '01:02:03' from dual),
4 fruits (id, name) as
5 (select '01', 'Apple' from dual union all
6 select '02', 'Banana' from dual union all
7 select '03', 'Carrot' from dual
8 ),
9 temp as
10 -- first split colon-delimited value into rows
11 (select level lvl,
12 regexp_substr(val, '[^:]+', 1, level) id
13 from chbox
14 connect by level <= regexp_count(val, ':') + 1
15 )
16 -- finally, join TEMP ID's with FRUITS; aggregate names
17 select listagg(f.name, ':') within group (order by t.lvl) result
18 from fruits f join temp t on t.id = f.id;
RESULT
------------------------------
Apple:Banana:Carrot
SQL>
Code you need begins at line #9 (just precede temp CTE name with the with keyword).
I don't quite get your data model, but I'm a fan of using the apex_string package to convert delimited strings into rows.
For example
select display, return
from lov_list
where type = 'fruit'
and return in (
select column_value
from apex_string.split('01:02:03:04', ':')
)

SQL Query to select a specific part of the values in a column

I have a table in a database and one of the columns of the table is of the format AAA-BBBBBBB-CCCCCCC(in the table below column Id) where A, B and C are all numbers (0-9). I want to write a SELECT query such that for this column I only want the values in the format BBBBBBB-CCCCCCC. I am new to SQL so not sure how to do this. I tried using SPLIT_PART on - but not sure how to join the second and third parts.
Table -
Id
Name
Age
123-4567890-1234567
First Name
199
456-7890123-4567890
Hulkamania
200
So when the query is written the output should be like
Output
4567890-1234567
7890123-4567890
As mentioned in the request comments, you should not store a combined number, when you are interested in its parts. Store the parts in separate columns instead.
However, as the format is fixed 'AAA-BBBBBBB-CCCCCCC', it is very easy to get the substring you are interested in. Just take the string from the fifth position on:
select substr(col, 5) from mytable;
You can select the right part of a column starting at the 4th character
SELECT RIGHT(Id, LEN(Id)-4) AS TrimmedId;
Another option using regexp_substr
with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ;
Demo
SQL> with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ; 2 3 4 5 6
RESULT
--------------------------------------------------------------------------------
4567890-1234567
7890123-4567890
SQL>

Create custom select query in oracle

This might be a weird question (and maybe not possible), but how do I create a select statement in oracle to select customs strings ?
For example, we can use the following:
SELECT 1, 2, 3, 4, 5
FROM DUAL
and it will select a table with 5 columns, with values 1 to 5 respectively. I tried selecting strings, but it won't work, since dual has a column of type VARCHAR2(1)...
I want to create a table with column names (for example, apples, bananas, oranges) and then pivot it to have one column column_name ad every row representing one of my fruits and then iterate to do my operations.
Note that I do not want to create a table nor a view, this data will only be used in this query, and it's not available elsewhere.
Is this what you want?
select 'apples' as colname from dual union all
select 'bananas' as colname from dual union all
select 'oranges' as colname from dual ;
You can put this logic into a CTE or subquery.

Make in (SQL) dynamic for incoming values

Is it possible for in statement to be dynamic? like dynamic comma separation
for example:
DATA=1
select * from dual
where
account_id in (*DATA);
DATA=2
select * from dual
where
account_id in (*DATA1,*DATA2);
FOR DATA=n
how will i make the in statement dynamic/flexible (comma) for unknown quantity.
select * from dual
where
account_id in (*DATAn,*DATAn+1,etc);
A hierarchical query might help.
acc CTE represents sample data
lines #9 - 11 are what you might be looking for; data is concatenated with level pseudocolumn's value returned by a hierarchical query
Here you go:
SQL> with acc (account_id) as
2 (select 'data1' from dual union all
3 select 'data2' from dual union all
4 select 'data3' from dual union all
5 select 'data4' from dual
6 )
7 select *
8 from acc
9 where account_id in (select 'data' || level
10 from dual
11 connect by level <= &n
12 );
Enter value for n: 1
ACCOU
-----
data1
SQL> /
Enter value for n: 3
ACCOU
-----
data1
data2
data3
SQL>
As I can see, You are using the number in Where clause, Substitution will be enough to solve your problem.
See the example below:
CREATE table t(col1 number);
insert into t values(1);
insert into t values(2);
insert into t values(3);
-- Substitution variable initialization
define data1=1;
define data2='1,2';
--
-- With data1
select * from t where col1 in (&data1);
Output:
-- With data2
select * from t where col1 in (&data2);
Output:
Hope, This will be helpful to you.
Cheers!!
The basic problem is not the listagg function but a major misconception that just because elements in a string list are comma separated that a string with commas in it is a list. Not So. Consider a table with the following rows.
Key
- Data1
- Data2
- Data1,Data2
And the query: Select * from table_name where key = 'wanted_key'; Now if all commas separate independent elements then what value in for "wanted_Key" is needed to return only the 3rd row above? Even with the IN predicate 'Data1,Data2' is still just 1 value, not 2. For 2 values it would have to be ('Data1','Data2').
The problem you're having with Listagg is not because of the comma but because it's not the appropriate function. Listagg takes values from multiple rows and combines then into a single comma separated string but not comma separated list. Example:
with elements as
( select 'A' code, 'Data1' item from dual union all
select 'A', 'Data2' from dual union all
select 'A', 'Data3' from dual
)
select listagg( item, ',') within group (order by item)
from elements group by code;
(You might also want to try 'Data1,Data2' as a single element. Watch out.
What you require is a query that breaks out each element separately. This can be done with
with element_list as
(select 'Data1,Data2,Data3' items from dual) -- get paraemter string
, parsed as
(select regexp_substr(items,'[^,]+',1,level) item
from element_list connect by regexp_substr(items,'[^,]+',1,level) is not null -- parse string to elements
)
The CTE "parsed" can now be used as table/view in your query.
This will not perform as well as querying directly with a parameter, but performance degradation is the cost of dynamic/flexible queries.
Also as set up this will NOT handle parameters which contain commas within an individual element. That would require much more code as you would have to determine/design how to keep the comma in those elements.

Is it possible to include dual table in a join query

Is it possible to include the DUAL table in a join query ?
Can anyone give me an example which includes the SYSTIMESTAMP from dual table.
One common use (for me) is to use it to make inline views to join on...
SELECT
filter.Title,
book.*
FROM
(
SELECT 'Red Riding Hood' AS title FROM dual
UNION ALL
SELECT 'Snow White' AS title FROM dual
)
AS filter
INNER JOIN
book
ON book.title = filter.title
[This is a deliberately trivialised example.]
Basically you can but there's no need to.
you can add the systimestamp pseudo column to whatever query you already have:
SELECT t.col1, t.col2, systimestamp
FROM your_table t
Will give same results as
SELECT t.col1, t.col2, d.st
FROM your_table t, (select systimestamp st from dual) d
Note that the dual table has only one line, so the cartessian product will not add rows to your original query.
Try this solution:
select (select SYSTIMESTAMP from dual ) as d
/*
Here you can add more columns from table tab
*/
from tab
There should be no need to, DUAL keyword is a way of saying that you're not querying a table, if you want to "join" DUAL with an other table, just query the other table including your columns that don't come from the table in the select clause.
EDIT : As the comments says, this statement is false, DUAL is a table.
I still think there is no point in including (from the question)
the DUAL table in a join
You CAN do this in Oracle. I found this question while trying to solve a similar problem. The trick is to wrap DUAL in a subquery, and return a static value which you can join on.
In my case, I wanted to see if a record meeting some conditions existed in a table before inserting a new record. If it existed, I wanted the ID column. If it did not exist, I wanted a new ID value from a sequence. I used DUAL to "fake" an outer join so that I would always get a row back. Then I used NVL() to return a sequence value if the result was null.
SELECT NVL(v.ID, REAL_SEQUENCE.nextval)
FROM (SELECT 1 as match FROM DUAL) d,
(SELECT 1 as match, ID
FROM REAL_TABLE
WHERE CONDITION = CRITERIA) v
WHERE d.match = v.match(+)