Stored Procedure - Exclude where conditions in a separate table - sql

I am stuck in a situation and I need your help.
I have a stored procedure with a select statement which has some joins and many where condition foreach product settings. The structure looks like below.
SELECT *
FROM TABLE1 T1
INNER JOIN T2 ON T1.ID = T2.ID
INNER JOIN T3 ON T1.ID = T3.ID
WHERE T1.STATUS = 'UnResolved'
AND
(
T1.COL1 NOT IN ('X', 'Y', 'Z')
AND (T1.Product != 'TEST1' OR T2.COL2 = 'V2' OR T3.COL3 = 'V3')
AND (T1.Product != 'TEST2' OR T2.COL2 = 'V2' OR T3.COL3 = 'V3')
AND (T1.Product != 'TEST3' OR T2.COL4 = 'V4' OR T3.COL5 = 'V5')
AND (T1.Product != 'TEST4' OR T2.COL4 = 'V4' OR T3.COL5 = 'V5')
)
There are only two fixed possible settings for any product.
Setting 1 : OR T2.COL2 = 'V2' OR T3.COL3 = 'V3'
Setting 2 : OR T2.COL4 = 'V4' OR T3.COL5 = 'V5'
Every time a new product comes in we need to add a new where condition with its respective setting.
Now for the new requirement, we need to automate this process by using any config table.
Can someone please suggest below things
#. structure of table
#. modify select query such that we do not need to modify the existing conditions
#. we also need to take care that existing logic does not break
Many thanks in advance!!

Create table product_config with columns product, type, col1, col2. product should be
foreign key to T1.product.
For every product create row in it. type specifies which of col2, col3 or col4, col5 is used.
product type
TEST1 1
TEST2 1
TEST3 2
TEST4 2
Then query will look like this:
SELECT *
FROM TABLE1 T1
INNER JOIN PRODUCT_CONFIG C ON (T1.PRODUCT = C.PRODUCT)
INNER JOIN T2 ON T1.ID = T2.ID
INNER JOIN T3 ON T1.ID = T3.ID
WHERE T1.STATUS = 'UnResolved'
AND
(
T1.COL1 NOT IN ('X', 'Y', 'Z')
AND (C.TYPE != 1 OR T2.COL2 = 'V2' OR T3.COL3 = 'V3')
AND (C.TYPE != 2 OR T2.COL4 = 'V4' OR T3.COL5 = 'V5')
)

1 solution I have used before is to create a lookup table having your input parameters and the where condition used. For example you could create a table with the following columns:
PRODUCT_NAME
PRODUCT_TYPE
PRODUCT_PRICE
WHERE_CLAUSE
Using this fire a lookup query, say you have the name, type & price:
SELECT WHERE_CLAUSE FROM PRODUCT_CRITERIA WHERE PRODUCT_NAME = :PRODUCT_NAME AND PRODUCT_TYPE = :PRODUCT_TYPE AND PRODUCT_PRICE = :PRODUCT_PRICE
In oracle the WHERE_CLAUSE can be used to execute a dynamic query using 'EXECUTE IMMEDIATE'

Related

Error while using Case when Alias in join statement

SELECT T1.COL1
,CASE
WHEN T1.COL2 = 111
THEN 'A'
WHEN T1.COL2 = 222
THEN 'B'
ELSE 'C'
END AS DT
,T2.COL1
FROM TABLE1 T1
LEFT JOIN TABLE2 T2 ON T1.COL1 = DT;
error- invalid identifier DT
I want to use and verify the condition using case when alias in join condition which is giving error
NOTE - UPDATED CODE
SELECT T1.COL1
,CASE
WHEN T1.COL2 = 111
THEN 'A'
WHEN T1.COL2 = 222
THEN 'B'
ELSE 'C'
END AS DT
,T2.COL1
FROM TABLE1 T1
LEFT JOIN TABLE2 T2 ON T1.COL1 = CASE
WHEN T1.COL2 = 111
THEN 'A'
WHEN T1.COL2 = 222
THEN 'B'
ELSE 'C'
END;
THis one is working. Any other way other than this?
Unfortunately your new query still makes no sense. As noted previously, your join does not involve any column from TABLE2. Perhaps you obfuscated table names to the point you added this logic error by accident? Here is one way to avoid the huge effort to copy/paste the case expression code.
with cte as (
select *,
case COL2
when 111 then 'A'
when 222 then 'B'
else 'C' end as DT
from dbo.TABLE1
)
select ...
from cte left join dbo.TABLE2 as t2
on cte.Col1 = cte.DT
order by ... ;
If this case expression is commonly used, you could create a computed column and use it for your "join". That does not address the logic flaw but does address reusability.
If you place the assembly of the custom column in a table expression, then the new column gets officially named and can be used on any other expression.
For example:
select t1.*, T2.COL1
from ( -- this is a "table expression" named "t1"
SELECT T1.COL1
,CASE
WHEN T1.COL2 = 111
THEN 'A'
WHEN T1.COL2 = 222
THEN 'B'
ELSE 'C'
END AS DT
FROM TABLE1
) t1
LEFT JOIN TABLE2 T2 ON T2.COL1 = t1.DT;

SQL - Use different Select Statements based on column value

For instance,
Select field1
From table1
when table1.field1 = 'S'
then (select field1,2,3,4,5,6,.....
form table1,2,3,4,5,6,....(with joins))
when table1.field1 = 'O'
then (select field1,2,3,4,5,6,.....
from table1,2,3,4,5,6,.....(with join))
I think I got what you need. One possible solution is to create a view with hardcoded where clauses on it. This is the idea:
CREATE VIEW ConditionalSelect AS
SELECT ...fields...
FROM ...tables...
JOIN ...joins...
WHERE table1.field1 = 'S'
AND ....
UNION
SELECT ...fields...
FROM ...tables...
JOIN ...joins...
WHERE table1.field1 = 'O'
AND ....
Then you can do this:
SELECT *
FROM ConditionalSelect
WHERE field1 = 'S'
NOTE: Both SELECT MUST have the same columns, columns types and column names, either the VIEW won't compile.
You could just do something like this:
DECLARE #Field1 VARCHAR(10) =
(
SELECT Field1
FROM table1
);
IF(#Field1 = 'S')
BEGIN
SELECT *
FROM table1 t1
INNER JOIN table2 t2
ON t2.col1 = t1.col1
END
ELSE IF (#Field1 = 'O')
BEGIN
SELECT *
FROM table1 t1
INNER JOIN table2 t2
ON t2.col1 = t1.col1
END
Saves you from creating a view

Left join expression and amount of rows returned by Oracle

A query with left join is not returning records, although the where clause from the left table should find a single record. In this case, it should return a record with the fields from the left table containing values and from the right table null, since there is no match between them.
Apparently there is a problem with the use of case that references the right table on the join expression.
In SQL Server the same query worked as expected.
select
t1.Description, t2.Description
from
A t1
left join
B t2
on
t1.Id = t2.Id and
1 = case when (
t2.Id = t2.Id and
(select t3.Flag from C t3 where t3.ID_B = t2.Id) = 'S'
) then 1 else 0
end
where t1.Id = 1
Result: no rows returned.
Then I moved the expression t2.Id = t2.Id (that is here only to demonstrate the problem and should always return true, apparently) out of the case expression.
select
t1.Description, t2.Description
from
A t1
left join
B t2
on
t1.Id = t2.Id and
t2.Id = t2.Id and
1 = case when (
(select t3.Flag from C t3 where t3.ID_B = t2.Id) = 'S') then 1 else 0
end
where t1.Id = 1
Result: one row returned.
The queries above only serve to demonstrate the problem, are not useful in a real situation and not optimized.
I want to know if anyone knows any limitation of Oracle related to this case. So far we believe it is a bug.
Data used:
A: Id=1, Description=Item A1;
B: Id=1, Description=Item B1;
C: Id=1, Id_B=2, Flag=S.
CREATE TABLE t1 AS (SELECT 1 ID FROM dual);
CREATE TABLE t2 AS (SELECT 2 ID FROM dual);
CREATE TABLE t3 AS (SELECT 2 id_b, 's' flag FROM dual);
SELECT t1.*
FROM t1 LEFT JOIN t2
ON t1.ID = t2.ID
AND 1 = CASE WHEN t2.id = t2.id and (SELECT flag FROM t3 WHERE t3.id_b = t2.ID) = 's' THEN 1 ELSE 0 END
where t1.id = 1;
The output: no rows selected
The result looks strange, I suppose it can be a bug.
Oracle documentation only states
https://docs.oracle.com/cd/B28359_01/server.111/b28286/queries006.htm#SQLRF52337
You cannot compare a column with a subquery in the WHERE clause of any
outer join, regardless which form you specify.
By looking on the plan of the above query I can see that this condition:
AND 1 = CASE WHEN t2.id = t2.id and (SELECT flag FROM t3 WHERE t3.id_b = t2.ID) = 's' THEN 1 ELSE 0 END
Is interpreted as:
CASE WHEN (T2.ID(+)=T2.ID(+) AND (SELECT FLAG FROM T3 T3 WHERE T3.ID_B=:B1)='s') THEN 1 ELSE 0 END =1
and is calculated after the join.
I suppose that Oracle cannot calcuate the CASE until the join is performed (because of T2.ID(+)=T2.ID(+))
Your assumption that t2.id = t2.id is always true is wrong. If the value were NULL that would be treated as false. I don't believe that is relevant for this particular example, but just to clarify.
The question is how is a left join processed. The idea is simple. The on clause is processed. If there are no matches, then the row from the first table is kept. This is regardless of what is in the on clause. (This is a functional description; there are many possible implementations.)
Based on your sample data, Oracle is incorrect. One row should be returned. The SQL Server example should also return one row. I suspect that the data might be subtly different; I personally have never had issues with left joins in SQL Server (or Oracle).
Using SQLFiddle Oracle 11g R2 (thanks to Shannon Severance) your first query gives Record Count: 0 but by simply removing the CASE we get Record Count: 1. (Note the renaming of t2Description.)
create table A (ID number(38), Description varchar(10));
create table B (ID number(38), Description varchar(10));
create table C (ID number(38), ID_B number(38), Flag varchar(10));
insert into A values(1, 'Item A1');
insert into B values(2, 'Item B1');
insert into C values(1, 2, 'S');
select
t1.Description, t2.Description as t2d
from
A t1
left join
B t2
on
t1.Id = t2.Id and
t2.Id = t2.Id and
(select t3.Flag from C t3 where t3.ID_B = t2.Id) = 'S'
where t1.Id = 1
This suggests that it has something to do with CASE being miscalculated.
Note that in the ON t2.Id is at least sometimes (correctly) taken to be the value from the FROM cross product, not NULL which it is after the ON:
select
t1.Description, t2.Description as t2d
from
A t1
left join
B t2
on
-- for above data t2.id should be 1 here
t2.id is null
where t1.Id = 1
-- for above data t2.id should be null here
DESCRIPTION T2D
Item A1 (null)
I found this link: Outer Join Bug in Oracle 12c?

sql query to join 2 tables efficiently

The have the following 2 table2:
Table1(col1 integer, col2)
1 "This is a string"
2 "This is another string"
5 "This is yet another string"
3 "a"
4 "b"
6 "Some other string"
Table2(col3 integer, col4 integer, col5 integer)
1 2 5
3 4 6
Now I want to find all the values from Table2 where col4=2. This gives me col3=1 and col5=5. Now I want to join this result with Table1 such that I obtain the string values(col2) corresponding to these integers.
That is, I want the result as: "This is a string", "This is yet another string"
The SQL query which I wrote in postgresql is given below:
select d1.col2, d2.col2
from Table1 d1, Table1 d2
where (select col3, col5 from Table2 where col4=0);
However, the above query is giving me error. Can someone please help me write an efficient query for the same.
You could use an INNER JOIN with two conditions on the ON clause:
SELECT Table1.*
FROM
Table1 INNER JOIN Table2
ON Table1.col1 = Table2.col3 OR Table1.col1 = Table2.col5
WHERE
Table2.col4=2
Please see fiddle here.
Try
SELECT t2.col2, t3.col2
FROM Table1 AS t1
INNER JOIN Table2 AS t2 ON t1.col1 = t2.col3
INNER JOIN Table2 AS t3 ON t1.col1 = t2.col5
WHERE t1.col4 = 2
if you want your result as two rows with one column:
select t1.col2
from Table2 as t2
inner join Table1 as t1 on t1.col1 in (t2.col3, t2.col5)
where t2.col4 = 2;
-- output
-- 'This is a string'
-- 'This is yet another string'
if you want your result as one row with two columns:
select t13.col2, t15.col2
from Table2 as t2
inner join Table1 as t13 on t13.col1 = t2.col3
inner join Table1 as t15 on t15.col1 = t2.col5
where t2.col4 = 2
-- output
-- 'This is a string', 'This is yet another string'
sql fiddle demo
try it as a union
select col2 from table1 where col1 in (
select col3 from table2 where col4 = 2
union
select col5 from table2 where col4 = 2
)

Query regarding formation of SQL query

I have a huge table containing data of the form given below:
Id Col1 Col2 Col3
------------------------
a Fruit1 vitaminA vitaminB
b a price "30"
Now I want to retrieve all fruits containing vitaminA and vitaminB having price less than 30 in SQL. here 'a' is the id which is given to Fruit1. Fruit1 contains VitaminA and vitaminB. Now, the next row indicates that id 'a' has price 30.
My intent is to retrieve all fruits having vitaminA and vitaminB and having price less than 30. Is there a way in SQL by which I may answer this query?
You need to do a join for this:
select t.col1
from t join
t tprice
on t.id = tprice.col1 and
tprice.col2 = 'price'
where ((t.col2 = 'VitaminA' and t.col3 = 'VitaminB') or
(t.col2 = 'VitaminB' and t.col3 = 'VitaminA')
) and
(cast(tprice.col3 as int) <= 30)
This is a very arcane data structure. Can you explain where it comes from?
You will have to use a self-join on your table to get the result.
select t1.id
from yourtable t1
inner join yourtable t2
on t1.id = t2.col1
where
(
t1.col2 = 'vitaminA' and t1.col3 = 'vitaminB'
or t1.col2 = 'vitaminB' and t1.col3 = 'vitaminA'
)
and t2.col2 = 'price'
and cast(t2.col3 as int) < '30';
See SQL Fiddle with Demo
Or you can use a WHERE clause using EXISTS:
select t1.id
from yourtable t1
where
(
t1.col2 = 'vitaminA' and t1.col3 = 'vitaminB'
or t1.col2 = 'vitaminB' and t1.col3 = 'vitaminA'
)
and exists (select t2.col1
from yourtable t2
where t2.col2 = 'price'
and cast(t2.col3 as int) < 30
and t1.id = t2.col1)
See SQL Fiddle with Demo
As a side note, your current data structure is very difficult to work with. If possible you might want to consider restructuring your tables.