How, in Oracle, to SELECT FROM a table or another depending on certain criteria? - sql

I have a query that must choose from one table if certain criteria is meet or from another if the criteria is different.
In my case i need to select from Table_A if we are on Database_A or from Table_B if we are on Database_B, but the criteria could be a different one.
I want to do something like this:
SELECT
COLUMN_1, COLUMN_2, ..., COLUMN_N
FROM
(if database is Database_A then Table_A
but if database is Database_B then from Table B
else... it could be DUAL if any other situation, it will throw an error, but i can manage it)
WHERE
(my where criteria)
How can i do it with pure SQL not PL-SQL? I can use nested queries or WITH or similar stuff, but not "coding". I cannot create tables or views, i can only query the database for data.
I tried using CASE or other options, with no luck.

You may use the database name in the condition criteria and UNION operator to select from the right table.
SELECT COLUMN_1, COLUMN_2, ..., COLUMN_N
FROM
(
SELECT COLUMN_1, COLUMN_2, ..., COLUMN_N
FROM TableA
WHERE ora_database_name = 'DatabaseA'
UNION ALL
SELECT COLUMN_1, COLUMN_2, ..., COLUMN_N
FROM TableB
WHERE ora_database_name = 'DatabaseB'
UNION ALL
SELECT 'ERROR', null, ..., null
FROM DUAL
WHERE ora_database_name NOT IN ('DatabaseA', 'DatabaseB')
)
WHERE
(my where criteria)

If it is the case of selecting from one of two tables then something like this could help:
WITH
tab_a AS
( Select COL_1, COL_2, COL_3 From TABLE_A ), -- It is possible to do the filter here or in the main SQL or both
tab_b AS
( Select COL_1, COL_2, COL_3 From TABLE_B ) -- It is possible to do the filter here or in the main SQL or both
Select DISTINCT
CASE WHEN 1 = 1 THEN a.COL_1 ELSE b.COL_1 END "COL_1",
CASE WHEN 1 = 1 THEN a.COL_2 ELSE b.COL_2 END "COL_2",
CASE WHEN 1 = 1 THEN a.COL_3 ELSE b.COL_3 END "COL_3"
From
tab_a a
Inner Join
tab_b b ON(1 = 1)
Where
CASE WHEN 1 = 1 THEN b.COL_1 ELSE a.COL_2 END = 'XXX' -- filter could be set on the same columns or on different ones of the same type
You can put the where clause either in the cte definition (WITH clause) or in the main SQL or in both.
WHEN condition within CASE expresions should be put acording to your data context and that is the place that will select/filter the data from one or another table.
INNER JOIN ON condition may stay as is (1=1) for it creates cross link that will be solved by the DISTINCT keyword.
If both tables are not reachable from both databases then you shall have a separate scripts, one for each db. You can, though, check which is the active db and/or if there is a specific table.
-- to get the db name ...
Select NAME "DB_NAME" From V$database
-- ... to check if the table exist ...
Select TABLE_NAME from ALL_TABLES where TABLE_NAME = 'TABLE_A'
-- ... or some additional info besides the table name
Select
u.USER_ID, u.USERNAME,
t.TABLE_NAME, t.OWNER "TABLE_OWNER"
From ALL_USERS u
Inner Join ALL_TABLES t ON(t.OWNER = u.USERNAME)
Where TABLE_NAME = 'TABLE_A'

Related

Get different query back based on a condition in PostgreSQL

I'm having a hard time with sql and probably this will look stupid but it shows what I am trying to achieve.
SELECT
CASE WHEN ( ( SELECT 1 FROM table_1 WHERE = condition ) IS NULL ) THEN
SELECT 'No result'::varchar
ELSE
SELECT
val_1,
val_2,
val_3
FROM
table_1
END;
A null answer is not good in my situation, I can't just use the sub-query as the main. And even if I could that would still leave the question open if the two tables were NOT the same like:
SELECT
CASE WHEN ( ( SELECT 1 FROM table_1 WHERE = condition ) IS NULL ) THEN
SELECT 'No result'::varchar
ELSE
SELECT
val_1,
val_2,
val_3
FROM
table_2 --TABLE REPLACED!
END;
As CASE-WHEN only works for one column it would be horrifying to have 20 of them with the same condition. Any help is appreciated! Thanks!
So you want to SELECT the table_log and if the result is not NULL show it to the client and if it is NULL show a message?
I created a fake table for testing. What you are looking for is the last SELECT-statement:
DROP TABLE IF EXISTS table_log;
CREATE TEMP TABLE table_log (
id INTEGER
,log_info VARCHAR)
;
INSERT INTO table_log VALUES
(1, 'test_entry')
;
ANALYZE table_log;
SELECT
COALESCE(b.log_info, 'No changes done!') AS log_info
FROM
(SELECT 'Fake-Data') a
LEFT OUTER JOIN (SELECT * FROM table_log WHERE id = 1) b ON (1=1);
If the given id = 1, you get the result, if it is something else (because it is not in the test-table) the premade message is given.
Here is a link to the db<>fiddle.

How to extract the table name from a CREATE/UPDATE/INSERT statement in an SQL query?

I am trying to parse the table being created, inserted into or updated from the following sql queries stored in a table column.
Let's call the table column query. Following is some sample data to demonstrate variations in how the data could look like.
with sample_data as (
select 1 as id, 'CREATE TABLE tbl1 ...' as query union all
select 2 as id, 'CREATE OR REPLACE TABLE tbl1 ...' as query union all
select 3 as id, 'DROP TABLE IF EXISTS tbl1; CREATE TABLE tbl1 ...' as query union all
select 4 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
select 5 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
select 6 as id, 'UPDATE tbl3 SET col1 = ...' as query union all
select 7 as id, '/*some garbage comments*/ UPDATE tbl3 SET col1 = ...' as query union all
select 8 as id, 'DELETE tbl4 ...' as query
),
Following are the formats of the queries (we are trying to extract table_name ):
#1
some optional statements like drop table
CREATE some comments or optional statement like OR REPLACE TABLE table_name
everything else
#2
some optional statements like drop table
INSERT some comments INTO some comments table_name
#3
some optional statements like drop table
UPDATE some comments table_name
everything else
Regular Expression
To construct a suitable regex, let's start with the following relatively simple/readable version:
((CREATE( OR REPLACE)?|DROP) TABLE( IF EXISTS)?|UPDATE|DELETE|INSERT INTO) ([^\s\/*]+)
All the spaces above could be replaced with "at least one whitespace character", i.e. \s+. But we also need to allow comments. For a comment that looks like /*anything*/ the regex looks like \/\*.*\*\/ (where the comment characters are escaped with \ and "anything" is the .* in the middle). Given there could be multiple such comments, optionally separated by whitespace, we end up with (\s*\/\*.*\*\/\s*?)*\s+. Plugging this in everywhere there was a space gives:
((CREATE((\s*\/\*.*\*\/\s*?)*\s+OR(\s*\/\*.*\*\/\s*?)*\s+REPLACE)?|DROP)(\s*\/\*.*\*\/\s*?)*\s+TABLE((\s*\/\*.*\*\/\s*?)*\s+IF(\s*\/\*.*\*\/\s*?)*\s+EXISTS)?|UPDATE|DELETE|INSERT(\s*\/\*.*\*\/\s*?)*\s+INTO)(\s*\/\*.*\*\/\s*?)*\s+([^\s\/*]+)
One further refinement needs to be made: Bracketed expressions have been used for choices, e.g. (CHOICE1|CHOICE2). But this syntax includes them as capturing groups. Actually we only require one capturing group for the table name so we can exclude all the other capturing groups via ?:, e.g. (?:CHOICE1|CHOICE2). This gives:
(?:(?:CREATE(?:(?:\s*\/\*.*\*\/\s*?)*\s+OR(?:\s*\/\*.*\*\/\s*?)*\s+REPLACE)?|DROP)(?:\s*\/\*.*\*\/\s*?)*\s+TABLE(?:(?:\s*\/\*.*\*\/\s*?)*\s+IF(?:\s*\/\*.*\*\/\s*?)*\s+EXISTS)?|UPDATE|DELETE|INSERT(?:\s*\/\*.*\*\/\s*?)*\s+INTO)(?:\s*\/\*.*\*\/\s*?)*\s+([^\s\/*]+)
Online Regex Demo
Here's a demo of it working with your examples: Regex101 demo
SQL
The Google BigQuery documentation for REGEXP_EXTRACT says it will return the substring matched by the capturing group. So I'd expect something like this to work:
with sample_data as (
select 1 as id, 'CREATE TABLE tbl1 ...' as query union all
select 2 as id, 'CREATE OR REPLACE TABLE tbl1 ...' as query union all
select 3 as id, 'DROP TABLE IF EXISTS tbl1; CREATE TABLE tbl1 ...' as query union all
select 4 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
select 5 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
select 6 as id, 'UPDATE tbl3 SET col1 = ...' as query union all
select 7 as id, '/*some garbage comments*/ UPDATE tbl3 SET col1 = ...' as query union all
select 8 as id, 'DELETE tbl4 ...' as query
)
SELECT
*, REGEXP_EXTRACT(query, r"(?:(?:CREATE(?:(?:\s*\/\*.*\*\/\s*?)*\s+OR(?:\s*\/\*.*\*\/\s*?)*\s+REPLACE)?|DROP)(?:\s*\/\*.*\*\/\s*?)*\s+TABLE(?:(?:\s*\/\*.*\*\/\s*?)*\s+IF(?:\s*\/\*.*\*\/\s*?)*\s+EXISTS)?|UPDATE|DELETE|INSERT(?:\s*\/\*.*\*\/\s*?)*\s+INTO)(?:\s*\/\*.*\*\/\s*?)*\s+([^\s\/*]+)") AS table_name
FROM sample_data;
(The above is untested so please let me know in the comments if there are any issues.)
I think it really depends on your data, but you might find some success using an approach like this:
with data as (
select 1 as id, 'CREATE TABLE tbl1 ...' as query union all
select 2 as id, 'INSERT INTO tbl2 ...' as query union all
select 3 as id, 'UPDATE tbl3 ...' as query union all
select 4 as id, 'DELETE tbl4 ...' as query
),
splitted as (
select id, split(query, ' ') as query_parts from data
)
select
id,
case
when query_parts[safe_offset(0)] in('CREATE', 'INSERT') then query_parts[safe_offset(2)]
when query_parts[safe_offset(0)] in('UPDATE', 'DELETE') then query_parts[safe_offset(1)]
else 'Error'
end as table_name
from splitted
Of course this depends on the cleanliness and syntax in your query column. Also, if your table_name is qualified with project.table.dataset you would need to do further splitting.

Same query but different tables

I'm faced with a big query that is generated in a string and executed with "OPEN pCursor FOR vQuery" and I'm trying to get the query out of the string variable and as a proper "compilable" query.
I'm having this problem where a different table is query depending on a variable
vQuery := 'SELECT ...';
IF pVar = 1 Then
vQuery := vQuery || ' FROM table1';
ELSE
vQuery := vQuery || ' FROM table2';
END IF
vQuery := vQuery || ' WHERE ...';
The two tables have pretty much the same column name. Is there a way to have this as a single query
OPEN Pcursorout FOR
SELECT ... FROM CASE WHEN pVar = 1 THEN table1 ELSE table1 END WHERE ...;
Or I'm stuck at having two queries?
IF pVar = 1 Then
OPEN Pcursorout FOR SELECT ... FROM table1 WHERE ...;
ELSE
OPEN Pcursorout FOR SELECT ... FROM table2 WHERE ...;
END IF
The select and where part are large and exactly the same for both table.
You could use a UNION and use your variable pVar to only include the results from one query in the result set.
SELECT t1.col1, t1.col2, ..., t1.col10
FROM table1 t1
WHERE pVar = 1 and ...
UNION
SELECT t2.col1, t2.col2, ..., t2.col10
FROM table1 t2
WHERE pVar <> 1 and ...
This isn't exactly what you asked about -- not being required to have duplicate lists of columns for the two select statements -- but I think it might capture your intent. It will require that the columns selected by both queries have the same datatype so there will be a (somewhat weak) constraint that the columns of both query results are the same. For example, you won't be able to add a new column to one query but not the other.
Perhaps using UNION / UNION ALL to unite both queries? The requirement for using UNION/UNION ALL is that all SELECTs being united must return columns with the same names.
So if you have
SELECT t.f1,
t.f2,
t.f3
FROM t
WHERE ...
and your other query is
SELECT q.f1,
q.f2,
q.f3
FROM q
WHERE ...
you can have both running as a single SQL statement with UNION:
SELECT t.f1,
t.f2,
t.f3
FROM t
WHERE ...
UNION
SELECT q.f1,
q.f2,
q.f3
FROM q
WHERE ...
Keep in mind that if you need to return columns that exist in one table but not in the other, you can still use UNION, just return NULL and name the column correspondingly to the column name in the table that has it.
Its a bit of a kludge and you might need to look at the performance impact, but you could use an inline view that unions the two base tables, with a flag on each part that you then compare to your variable
SELECT ...
FROM (
SELECT 1 as var, table1.*
FROM table1
UNION ALL
SELECT 2 as var, table2.*
FROM table2
) t
WHERE t.var = pVar
AND ...;
Using an inline view means you don't have to duplicate the main select-list or the where clause etc. If the tables have different columns then you can (and maybe should anyway) only select the columns in the inner queries that will be referenced in the outer select-list.

Replacing concat operation in Where clause

We have a requirement of querying the ALL_TABLES view, based on a combination of schema name and table name.
There are two schemas "A" and "B" and they have same table "TAB1" in both of them, here my requirement is to select the table associated with schema A and not the schema B.
Currently, we are doing a concatenation operation on the table name and owner name for achieving it as shown below
There will be multiple owner and table name combinations available within a single query
select table_name from all_tables where concat(owner_name,table_name) in ('ATAB1','ATAB2','BTAB2','CTAB1')
select table_name from all_tables where concat(owner_name,table_name) not in ('ATAB1','ATAB2','BTAB2','CTAB1')
Here there are three schemas A, B and C with their respective table name combinations
How can we achieve the same result without using the CONCAT function ?
WHERE 0=1
OR (owner_name = 'A' AND table_name = 'T1')
OR (owner_name = 'B' AND table_name = 'T2')
OR (owner_name = 'A' AND table_name = 'T3')
The strange 0=1 is just to make the lines below syntactically identical for easy mainenance and/or code-generation. The optimizer removes it.
Oracle allows for multiple columns in an IN condition (see the documentation for some more examples).
select table_name
from all_tables
where (owner_name, table_name) in
(('A','TAB1'), ('A','TAB2'), ('B','TAB2'), ('C','TAB1'))
This would probably be equivalent to usr's answer in terms of performance.
You could arrange the string values you need to match against into a virtual table, then use that table in a join as a filter:
SELECT t.*
FROM all_tables t
INNER JOIN (
SELECT 'A' AS owner_name, 'TAB1' AS table_name FROM DUAL
UNION ALL SELECT 'A', 'TAB2' FROM DUAL
UNION ALL SELECT 'B', 'TAB2' FROM DUAL
UNION ALL SELECT 'C', 'TAB1' FROM DUAL
) s
ON t.owner_name = s.owner_name
AND t.table_name = s.table_name
;
I would expect this to give the query planner more room for optimisation than your present approach gives.

How to compare two tables column by column in oracle

I have two similar tables in oracle in two different databases.
For example : my table name is EMPLOYEE and primary key is employee id. The same table with same columns(say 50 columns are is avlbl in two databases and two databases are linked.
I want to compare these two tables column by column and find out which records are not matching. i want the specific column in each row in two tables that are not matching.
select *
from
(
( select * from TableInSchema1
minus
select * from TableInSchema2)
union all
( select * from TableInSchema2
minus
select * from TableInSchema1)
)
should do the trick if you want to solve this with a query
As an alternative which saves from full scanning each table twice and also gives you an easy way to tell which table had more rows with a combination of values than the other:
SELECT col1
, col2
-- (include all columns that you want to compare)
, COUNT(src1) CNT1
, COUNT(src2) CNT2
FROM (SELECT a.col1
, a.col2
-- (include all columns that you want to compare)
, 1 src1
, TO_NUMBER(NULL) src2
FROM tab_a a
UNION ALL
SELECT b.col1
, b.col2
-- (include all columns that you want to compare)
, TO_NUMBER(NULL) src1
, 2 src2
FROM tab_b b
)
GROUP BY col1
, col2
HAVING COUNT(src1) <> COUNT(src2) -- only show the combinations that don't match
Credit goes here: http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:1417403971710
It won't be fast, and there will be a lot for you to type (unless you generate the SQL from user_tab_columns), but here is what I use when I need to compare two tables row-by-row and column-by-column.
The query will return all rows that
Exists in table1 but not in table2
Exists in table2 but not in table1
Exists in both tables, but have at least one column with a different value
(common identical rows will be excluded).
"PK" is the column(s) that make up your primary key.
"a" will contain A if the present row exists in table1.
"b" will contain B if the present row exists in table2.
select pk
,decode(a.rowid, null, null, 'A') as a
,decode(b.rowid, null, null, 'B') as b
,a.col1, b.col1
,a.col2, b.col2
,a.col3, b.col3
,...
from table1 a
full outer
join table2 b using(pk)
where decode(a.col1, b.col1, 1, 0) = 0
or decode(a.col2, b.col2, 1, 0) = 0
or decode(a.col3, b.col3, 1, 0) = 0
or ...;
Edit
Added example code to show the difference described in comment.
Whenever one of the values contains NULL, the result will be different.
with a as(
select 0 as col1 from dual union all
select 1 as col1 from dual union all
select null as col1 from dual
)
,b as(
select 1 as col1 from dual union all
select 2 as col1 from dual union all
select null as col1 from dual
)
select a.col1
,b.col1
,decode(a.col1, b.col1, 'Same', 'Different') as approach_1
,case when a.col1 <> b.col1 then 'Different' else 'Same' end as approach_2
from a,b
order
by a.col1
,b.col1;
col1 col1_1 approach_1 approach_2
==== ====== ========== ==========
0 1 Different Different
0 2 Different Different
0 null Different Same <---
1 1 Same Same
1 2 Different Different
1 null Different Same <---
null 1 Different Same <---
null 2 Different Same <---
null null Same Same
Try to use 3rd party tool, such as SQL Data Examiner which compares Oracle databases and shows you differences.
Using the minus operator was working but also it was taking more time to execute which was not acceptable.
I have a similar kind of requirement for data migration and I used the NOT IN operator for that.
The modified query is :
select *
from A
where (emp_id,emp_name) not in
(select emp_id,emp_name from B)
union all
select * from B
where (emp_id,emp_name) not in
(select emp_id,emp_name from A);
This query executed fast. Also you can add any number of columns in the select query.
Only catch is that both tables should have the exact same table structure for this to be executed.
SELECT *
FROM (SELECT table_name, COUNT (*) cnt
FROM all_tab_columns
WHERE owner IN ('OWNER_A')
GROUP BY table_name) x,
(SELECT table_name, COUNT (*) cnt
FROM all_tab_columns
WHERE owner IN ('OWNER_B')
GROUP BY table_name) y
WHERE x.table_name = y.table_name AND x.cnt <> y.cnt;
Used full outer join -- But it will not show - if its not matched -
SQL> desc aaa - its a table
Name Null? Type
A1 NUMBER
B1 VARCHAR2(10)
SQL> desc aaav -its a view
Name Null? Type
A1 NUMBER
B1 VARCHAR2(10)
SQL> select a.column_name,b.column_name from dba_tab_columns a full outer join dba_tab_columns b on a.column_name=b.column_name where a.TABLE_NAME='AAA' and B.table_name='AAAV';
COLUMN_NAME COLUMN_NAME
A1 A1
B1 B1