Multiple columns for the same WHERE IN set - sql

Let's suppose I want to find out all the people who are either parent or child of an specific set of people.
I could do something like:
SELECT *
FROM people P
WHERE
P.parent_id IN ('111', 'abc', '42', '1a2b3c') OR
P.child_id IN ('111', 'abc', '42', '1a2b3c')
Is there any way in which I could avoid writing the list twice (or more times if I were looking for more columns)?
I'm looking for something like:
(...) WHERE (P.parent_id OR P.child_id) IN ('111', 'abc', '42', '1a2b3c')
I'm using Oracle, but a plain SQL solution would be appreciated too.

Try this:
WITH search_ids (id) AS (
SELECT '111' FROM dual
UNION ALL SELECT 'abc' FROM dual
UNION ALL SELECT '42' FROM dual
UNION ALL SELECT '1a2b3c' FROM dual
)
SELECT * FROM people P
WHERE P.parent_id IN (SELECT id FROM search_ids)
OR P.child_id IN (SELECT id FROM search_ids)
;
The FROM dual bit is Oracle specific.
Happy playing
Marco

In terms of performance and universality, it's better to use the other approach than OR for such a case. In Oracle, there are SET operators which could help you very much. For example for your case, your query could look like this:
select *
from people p
where
exists (
(
select p.parent_id from dual
union all
select p.child_id from dual
) intersect (
select '111' from dual
union all
select 'abc' from dual
union all
select '42' from dual
union all
select '1a2b3c' from dual
)
)
Or using with clause:
with people_list (value) as (
select '111' from dual
union all
select 'abc' from dual
union all
select '42' from dual
union all
select '1a2b3c' from dual
)
select * from people p
where
exists (
(
select p.parent_id from dual
union all
select p.child_id from dual
) intersect (
select value from people_list
)
)
Benefits of such an approach:
It is better in terms of performance (as it was already mentioned, it is hard for optimizer to digest ORs in queries, especially when they are a bit more complex).
It is more universal - indeed, you can add as many columns as you want to be checked if they are in the given set of values.
Instead of selecting from dual, you can use any existing table in your schema.
This subquery integrates much easier into a complex query (and more efficiently as I already mentioned in the first point).

You are already using OR, so the query is hard to optimize. Hence, regular expressions are an option:
SELECT *
FROM people P
WHERE regexp_like('[' || P.parent_id || '][' || p.child_id || ']') regexp_like('\[111|abc|42|1a2b3c\]')

One way is with a hierarchical query (and then it's more general - you can use different cutoffs by LEVEL):
select *
from people
connect by level <= 2
and parent_id = prior child_id
start with child_id in ( ..... )
It would still be best to have the "search id's" in a separate table, and the IN condition as in (select search_id from helper_table) as shown in another answer.

CREATE TABLE #People(parent_id NVARCHAR(50),child_id NVARCHAR(50))
GO
INSERT INTO #People
( parent_id, child_id )
VALUES ( N'111', -- parent_id - nvarchar(50)
N'321331' -- child_id - nvarchar(50)
),( N'111', -- parent_id - nvarchar(50)
N'abc' -- child_id - nvarchar(50)
),( N'42', -- parent_id - nvarchar(50)
N'321331' -- child_id - nvarchar(50)
),( N'111', -- parent_id - nvarchar(50)
N'1a2b3c' -- child_id - nvarchar(50)
),( N'11dsdfs1', -- parent_id - nvarchar(50)
N'1a2sdfsdfsb3c' -- child_id - nvarchar(50)
)
;WITH CTE (Value) AS (
SELECT '111'
UNION SELECT 'abc'
UNION SELECT '42'
UNION SELECT '1a2b3c'
)
SELECT *
FROM #People p
WHERE EXISTS(
(SELECT p.parent_id
UNION
SELECT p.child_id
)
INTERSECT
SELECT value
FROM CTE
)

You can probably group them using something like below [Brevity: idea taken from SQL multiple columns in IN clause
WHERE (P.parent_id, P.child_id) IN (('111','111'), ('abc','abc'),('42','42'), ('1a2b3c','1a2b3c'));

Related

Oracle database - update table with random value from literal table, but change data per row

I want to randomly pick a data tuple from a literal table to fill in another table, and randomize the row to pick each time. With the below query, I fill all rows with the same data, so I think the random row is picked once and used everywhere.
So how can I get a random row for each row to update in the Oracle database?
update HISTORY h
set (ORGANIZATION_ID, COMPANY_ID) = (
select org_id, company_id from (
select * from (
select '3.11' as org_id, '11111111' as company_id from dual union
select '3.22.3' as org_id, '22222222' as company_id from dual union
...
select '3.44.5' as org_id, '33333333' as company_id from dual
) order by DBMS_RANDOM.RANDOM
) where rownum = 1
) where CODE = '1234567'; -- originally all were 3.88.4 and 88000004. 20707 rows
As per my similar answer to another question on picking random rows, you can do it but you need to add some seemingly irrelevant filters to force the SQL optimiser to not materialize the sub-query and to ensure the values are randomly generated for each row:
MERGE INTO HISTORY h
USING (
WITH data (org_id, company_id) AS (
select '3.11', '11111111' from dual union
select '3.22.3', '22222222' from dual union
select '3.44.5', '33333333' from dual
)
SELECT h.ROWID AS rid,
d.*
FROM history h
CROSS JOIN LATERAL (
SELECT *
FROM data
WHERE ROWNUM > 0 -- force a new random on each row
AND h.ROWID IS NOT NULL -- force the query to correlate
ORDER BY DBMS_RANDOM.VALUE() DESC
FETCH FIRST ROW ONLY
) d
WHERE h.code = '1234567'
) d
ON ( h.ROWID = d.RID )
WHEN MATCHED THEN
UPDATE
SET ORGANIZATION_ID = d.org_id,
COMPANY_ID = d.company_id;
Which, for the sample data:
CREATE TABLE history (organization_id, company_id, code) AS
SELECT '123456778', '88888888888', '1234567' FROM DUAL UNION ALL
SELECT '123456778', '88888888888', '1234567' FROM DUAL UNION ALL
SELECT '123456778', '88888888888', '1234567' FROM DUAL UNION ALL
SELECT '123456778', '88888888888', '1234567' FROM DUAL UNION ALL
SELECT '123456778', '88888888888', '1234567' FROM DUAL;
Then after the MERGE, the table may (randomly) contain:
ORGANIZATION_ID
COMPANY_ID
CODE
3.44.5
33333333
1234567
3.11
11111111
1234567
3.44.5
33333333
1234567
3.22.3
22222222
1234567
3.11
11111111
1234567
db<>fiddle here

SQL (Oracle) use two defined list in where-statement

I'm trying to create a query where I use two pre-defines lists with multiple values as filters in a where-stakement. This so I can re-use the query easily for many value-pairs en for many value-pairs.
On Stackoverflow i found:
WITH MyListOfValues(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column in (
select col1
from MyListOfValues);
Howerver it fails when i do something like:
WITH MyListOfValues1(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
AND
WITH MyListOfValues2(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column1 in (
select col1
from MyListOfValues1)
AND Column2 in (
select col1
from MyListOfValues2);
Does anyone has a solution?:) I'm doing this in a siting corporate enviroment I do not have rights (so far i know) to create my own tables.
Hope you can help me out! All help would be mutch appreciated:-)
You have a syntax error. The correct syntax for CTEs is:
WITH MyListOfValues1(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
),
MyListOfValues2(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column1 in (select col1 from MyListOfValues1) AND
Column2 in (select col1 from MyListOfValues2);
I would use exits instead
WITH MyListOfValues1(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
),
MyListOfValues2(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable dt
WHERE EXISTS (SELECT 1 FROM MyListOfValues1 WHERE col1 = dt.Column1) AND
EXISTS (SELECT 1 FROM MyListOfValues2 WHERE col1 = dt.Column2);
Use a collection and the MEMBER OF operator
SYS.ODCIVARCHAR2LIST is a built in VARRAY you can use:
SELECT *
FROM DatabaseTable
WHERE Column1 MEMBER OF SYS.ODCIVARCHAR2LIST( 'MyValue1', 'MyValue2', 'MyValue3' )
AND Column2 MEMBER OF SYS.ODCIVARCHAR2LIST( 'MyValue1', 'MyValue2', 'MyValue3' );
Or you can define your own collection:
CREATE TYPE StringList IS TABLE OF VARCHAR2(20);
SELECT *
FROM DatabaseTable
WHERE Column1 MEMBER OF StringList( 'MyValue1', 'MyValue2', 'MyValue3' )
AND Column2 MEMBER OF StringList( 'MyValue1', 'MyValue2', 'MyValue3' );
You can even pass the collections as bind variables:
SELECT *
FROM DatabaseTable
WHERE Column1 MEMBER OF :List1
AND Column2 MEMBER OF :List2;

SQL assigning incremental ID to subgroups

As the title says, I'm trying to add an extra column to a table which autoincrements everytime a different string in another column changes.
I would like to do this in a query.
Example:
MyCol GroupID
Cable 1
Cable 1
Foo 2
Foo 2
Foo 2
Fuzz 3
Fizz 4
Tv 5
Tv 5
The GroupID column is what I want to accomplish.
We can be sure that MyCol's strings will be the same in each subgroup (Foo will always be Foo, etc).
Thanks in advance
If I understand correctly, you can use dense_rank():
select t.*, dense_rank() over (order by col1) as groupid
from t;
You could make a temporal table with the distinct value of the MyCol and get the groupId throught the RowNumber of the temp table, and join the rownumbered result with your table.
This is a raw example in oracle:
WITH data AS
(SELECT 'Cable' MyCol FROM dual
UNION ALL
SELECT 'Cable' FROM dual
UNION ALL
SELECT 'Foo' FROM dual
UNION ALL
SELECT 'Foo' FROM dual
UNION ALL
SELECT 'Foo' FROM dual
UNION ALL
SELECT 'Fuzz' FROM dual
UNION ALL
SELECT 'Fizz' FROM dual
UNION ALL
SELECT 'Tv' FROM dual
UNION ALL
SELECT 'Tv' FROM dual
),
tablename AS
(SELECT * FROM data
),
temp AS
( SELECT DISTINCT mycol FROM tablename
),
temp2 AS
( SELECT mycol, rownum AS groupid from temp
)
SELECT tablename.mycol, temp2.groupid FROM temp2 JOIN tablename ON temp2.mycol = tablename.mycol
You could also check for a way to implement the tabibitosan method knowing that your column condition is string.

Can I have dynamic FROM Clause in SQL?

I'm currently working with my report parameter list of value that is dependent in another parameter.
I have come up with this idea, is there any possible way to for this to work?
WITH A AS (
SELECT DISTINCT columnA1 FROM Table1
UNION SELECT DISTINCT columnA2 FROM Table1
UNION SELECT DISTINCT columnA3 FROM Table1)
WITH B AS (SELECT DISTINCT columnB1 FROM Table1
UNION SELECT DISTINCT columnB2 FROM Table1
UNION SELECT DISTINCT columnB3 FROM Table1)
Select * from CASE WHEN (:PM_Parameter1 = 'A')
THEN A
ELSE B
END;
Assuming this is Oracle SQL, you can use a the EXISTS function to check for the parameter value, then combine the sets using UNION.
Try playing with this SQL:
select * from
(
select 'A' from dual
union
select 'B' from dual
)
where exists
(SELECT 'Y'
FROM dual
where 'parameter' = 'parameter'
)
union
select * from
(
select 'X' from dual
union
select 'Y' from dual
)
where exists
(SELECT 'Y'
FROM dual
where 'parameter' != 'parameter'
)
If you reverse both the conditions 'parameter' = 'parameter' and 'parameter' != 'parameter', it will return two different row sets.
I am sure this can be optimized again, hope it works out for you.

Which value(s) in WHERE CLAUSE LIST are not available in the table

I want to search which value(s) in MY WHERE CLAUSE LIST are not available in the table.
Table name is test
Column1
--------------
1
2
3
My query : I have a search list 2, 3, 4, 5 and I want to see which all are not in my database. When I query, I should get 4, 5 and NOT 1.
I do not want the list of values which are there in the table and not in where clause list(select * from test where column1 not in (2, 3, 4, 5)
Can someone please help ?
WITH my_list AS
(SELECT regexp_substr('2,3,4,5', '[^,]+', 1, LEVEL) AS search_val
FROM dual
CONNECT BY level <= regexp_count('2,3,4,5',',') + 1
)
SELECT *
FROM my_list
WHERE NOT EXISTS
(SELECT 'X' FROM YOUR_TABLE WHERE YOUR_COLUMN = search_val
);
Let's Convert the comma separated values into a view and then do what's needed.
You can do it as follows:
SELECT List FROM
(SELECT 2 as List
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5) T
WHERE List NOT IN
(SELECT Column1 FROM TableName)
In this case, I would do a simple select
select *
from test
where column1 in (2, 3, 4, 5)
and do the set operation in the host language (Java, C++, Perl, ...).
This seems far simpler than any SQL solution.
with cte as
(select 2 as val from dual
union all
select 3 from dual
union all
select 4 from dual
union all
select 5 from dual
union all
)
select * from cte as t1
where not exists
( select * from test as t2 where t1.val = t2.column1)
For a large number of values you might better create a temporary table, insert the rows and then use this instead of the common table expression.
Try below Query:
WITH MY_DATA_TABLE AS
(
SELECT regexp_substr('2,3,4,5', '[^,]+', 1, LEVEL) AS MY_DATA_VALUE
FROM dual
CONNECT BY level <= (length('2,3,4,5') - length(replace('2,3,4,5', ',')))
)
SELECT *
FROM MY_DATA_TABLE
WHERE NOT EXISTS
(SELECT 'TRUE' FROM TABLE_NAME WHERE TABLE_FIELD_VALUE = MY_DATA_VALUE
);
Your query with huge data would translate in ORACLE to:
WITH MY_DATA_TABLE AS
(
SELECT regexp_substr('1,4,5,8,9,12,13,14,20,39,43,48,51,54,55,57,61,65,68,75,78,80,81,82,91,92,96,99,‌​102,103,109,112,113,224,227,249,250,251,600,601,604,605,608,609,614,802', '[^,]+', 1, LEVEL) AS MY_DATA_VALUE
FROM dual
CONNECT BY level <= (length('1,4,5,8,9,12,13,14,20,39,43,48,51,54,55,57,61,65,68,75,78,80,81,82,91,92,96,99,‌​102,103,109,112,113,224,227,249,250,251,600,601,604,605,608,609,614,802') - length(replace('1,4,5,8,9,12,13,14,20,39,43,48,51,54,55,57,61,65,68,75,78,80,81,82,91,92,96,99,‌​102,103,109,112,113,224,227,249,250,251,600,601,604,605,608,609,614,802', ',')))
)
SELECT *
FROM MY_DATA_TABLE
WHERE NOT EXISTS
(SELECT 'TRUE' FROM TABLE_NAME WHERE TABLE_FIELD_VALUE = MY_DATA_VALUE
);