SQL Implementing 'IN' operator with 'OR' - sql

I am working on a legacy system which has a custom java implementation for generating SQL queries. That doesn't support 'IN' operation.
To implement 'IN' I have written something like
SELECT * from Q
WHERE IS_HIDDEN = 0 AND ID = 1
OR ID = 2 OR ID = 3 AND IS_DELETED = 0;
I know that the one like below would have been fine.
SELECT * from Q
WHERE IS_HIDDEN = 0 AND (ID = 1
OR ID = 2 OR ID = 3) AND IS_DELETED = 0 ;
Both these return the same result but I'm not too confident about SQL operator priorities. I had read that AND takes precedence
Is it safe to assume that both the SQL statemets are equivalent.
The actual query that I wanted to write is
SELECT * from Q
WHERE IS_HIDDEN = 0 AND ID IN(1, 2, 3) AND IS_DELETED = 0;
The DB in question is oracle 10g.
Update: The reason that this was working is because the oracle CBO rearranges the subclauses in the where clause.

No your queries are not the same
SELECT * from Q
WHERE IS_HIDDEN = 0 AND ID = 1
OR ID = 2 OR ID = 3 AND IS_DELETED = 0;
is like
SELECT * FROM Q WHERE IS_HIDDEN = 0 AND ID = 1
UNION
SELECT * FROM Q WHERE ID = 2
UNION
SELECT * FROM Q WHERE ID = 3 AND IS_DELETED = 0
when you use the parentheses for your ORs then you have the same like the IN-Clause
You can try it: SQLFiddle

You first query is equal to the IN. You should use that:
Your second query is like this:
SELECT * from Q
WHERE (IS_HIDDEN = 0 AND ID = 1) OR ID = 2 OR (ID = 3 AND IS_DELETED = 0);
If IS_HIDDEN is 1 or DELETED Is 1, but ID is 2, your query will still give you records. Try it..

Related

Remove parameters from SQL Query in with Regex / LogStash

A 3rd party system I use logs all SQL queries along with rowcount & response time which I then send to Logstash/Elastic to calculate metrics. As this system doesn't use bind variables, and there are 10's of millions of queries a day, I need to be able to rollup the data, which I can't do if the majority of queries are unique. I need a way to replace the SQL query parameters with '?' as Oracle would do via Cursor Sharing.
i.e.
replace
'SELECT * FROM table_name WHERE id = 123'
with
'SELECT * FROM table_name WHERE id = ?'
I have access to Ruby scripting magic in Logstash, but unfortunately all of the google results for 'sql regex' or similar return results of how to use regular expressions in SQL, not the other way round. Before I go crafting a regular expression parser, I thought I would check in here to see if others have tried to solve a similar problem.
FYI, have looked at implementing a solution using a Ruby SQL AST library such as https://github.com/lfittl/pg_query but plugging Ruby libraries in to Logstash becomes more of a problem of writing a custom Filter plugin to do the work, which may be the answer, but i'm hoping I'm missing something obvious.
I am not a logstash/ruby developer/user, but in terms of regular expression you may try this one:
(=\s\W\w+\W|=\s\d+)
You can test this here
SELECT * FROM Table1 WHERE Column1 = 1
SELECT * FROM Table1 WHERE Column1 = 'abc'
SELECT * FROM Table1 WHERE (Column1 = 'abc' OR Column2 = 1)
SELECT * FROM Table1 WHERE (Column1 = 'abc' AND Column2 = 1) OR Column2 = 'zxy'
SELECT * FROM Table1 WHERE (Column1 = 'abc' AND Column2 = 1) OR Column2 = 'zxy' AND
Column3 = 2
SELECT * FROM Table1 WHERE Column1 = 1 AND Column2 = 2
Expected Results:
Match 1
Full match = 1
Group 1. = 1
Match 2
Full match = 'abc'
Group 1. = 'abc'
Match 3
Full match = 'abc'
Group 1. = 'abc'
Match 4
Full match = 1
Group 1. = 1
Match 5
Full match = 'abc'
Group 1. = 'abc'
Match 6
Full match = 1
Group 1. = 1
Match 7
Full match = 'zxy'
Group 1. = 'zxy'
Match 8
Full match = 'abc'
Group 1. = 'abc'
Match 9
Full match = 1
Group 1. = 1
Match 10
Full match = 'zxy'
Group 1. = 'zxy'
Match 11
Full match = 2
Group 1. = 2
Match 12
Full match = 1
Group 1. = 1
Match 13
Full match = 2
Group 1. = 2
Based on these results you can create a function to replace the value of '= 2' to '= ?'.
Hope that it at least gives you a starting point.

Union different tables with differents columns and data

My problem is that I have 4 differents SELECT with
SELECT COUNT (*) AS regular
WHERE experience = 1 AND bl = 1
SELECT COUNT (*) AS rptmm
WHERE experience = 1 AND bl = 0
SELECT COUNT (*) AS new
WHERE experience = 0 AND bl = 0
SELECT COUNT (*) AS rptss
WHERE experience = 0 AND bl = 1
I want that the results appear together whith the respective names like:
regular rptmm new rptss
10 5 2 6
Firstly, I'd suggest not to use Count()*. There are many answers on this site explaining why so I am not going to repeat it.
Instead, I'd suggest you to use a query like this:
SELECT (SELECT COUNT (tab.someColumnName)
FROM TableName tab
WHERE tab.experience = 1 AND tab.bl = 1) AS 'Regular',
(SELECT COUNT (tab.someColumnName)
FROM TableName tab
WHERE tab.experience = 1 AND tab.bl = 0) AS 'rptmm',
(SELECT COUNT (tab.someColumnName)
FROM TableName tab
WHERE tab.experience = 0 AND tab.bl = 0) AS 'New',
(SELECT COUNT (tab.someColumnName)
FROM TableName tab
WHERE tab.experience = 0 AND tab.bl = 1) AS 'rptss'
Hope this helps!!!
Just put UNION ALL between your four statements you will get four rows with each count on its own row. However, you will lose the column name. You could also use join to get one row with four columnes. Just put the keyword join between each sql statement.
SELECT COUNT (*) AS regular
WHERE experience = 1 AND bl = 1
JOIN
SELECT COUNT (*) AS rptmm
WHERE experience = 1 AND bl = 0
JOIN
SELECT COUNT (*) AS new
WHERE experience = 0 AND bl = 0
JOIN
SELECT COUNT (*) AS rptss
WHERE experience = 0 AND bl = 1
You could create a temp table to hold all of this data for you: Replace Name1, Name2, Name3,Name4 with whatever you want to call them. These will be the column headers.
CREATE TABLE #Temp(
NAME1 INT
,NAME2 INT
,NAME3 INT
,NAME4 INT
)
INSERT INTO #Temp
(NAME1)
SELECT COUNT(*) AS regular
WHERE experience = 1 AND bl = 1
INSERT INTO #Temp
(NAME2)
SELECT COUNT(*) AS regular
WHERE experience = 1 AND bl = 0
INSERT INTO #Temp
(NAME3)
SELECT COUNT(*) AS regular
WHERE experience = 0 AND bl = 0
INSERT INTO #Temp
(NAME4)
SELECT COUNT(*) AS regular
WHERE experience = 0 AND bl = 1*
SELECT * FROM #Temp

Join Two Select Statements With Different Columns And Condition

I'm not good at asking question, so i'll give an example of what i want to have.
if i = 1 and xi = 0 then
select a,b,c,d,e,f,g where z = 1
elseif i=0 and xi = 1 then
select a,c,f,h,l,n where w = var
elseif i=1 and xi=1 then
select a,b,c,d,e,f,g, where z = 1
union all
select a,c,f,h,l,n where w = var
end if
How can I join the 2 select statement if their columns are not equal and they both have a unique condition?
Based on the conditions you can create derived tables to fetch desired columns and then to get a union of the two tables add null values in column list of derived tables which have less number of columns:
Pseudo code:
select * from
(select a,b,c,d,e,f,g
where z = 1
and 1 = case when i = 1 and xi = 0 then 1
when i = 1 and xi = 1 then 1
else 0
end) as T1
union all
(select a,c,f,h,l,n ,null -- add null value to equate number of columns
where w = var
and 1 = case when i=0 and xi = 1 then 1
when i=1 and xi = 1 then 1
else 0
end) as T2
Hope this helps!!!
If it is not a requirement not to use dynamic sql I will opt for that one.
Another idea will be to use user defined function returnin tables.
So you encapsulate there the logic...

Sql query confusion

I'm stumped on a sql query.
In a query, I have a result set like so:
FooId Name Value SourceLevel SourceId RecordId
-----------------------------------------------------------------
1 'Foo' 10 1 0 1
1 'Foo' 25 3 1 2
2 'Bar' 33 1 0 3
To that query, I pass parameters #Level1Level, #Level2Level, #Level3Level, and #Level1Id, #Level2Id, #Level3Id
(no, these aren't real names, but they illustrate my point).
My query is trying to do a filter like this:
WHERE ((SourceLevel = #Level1Level AND SourceId = #Level1Id)
OR (SourceLevel = #Level2Level AND SourceId = #Level2Id)
OR (SourceLevel = #Level3Level AND SourceId = #Level3Id))
If I pass in parameters like so:
#Level1Level = 1, #Level2Level = 2, #Level3Level = 3
#Level1Id = 0, #Level2Id = 3, #Level3Id = 2
I would want recordIds 1 and 3 back.
But, if I pass in parameters like so:
#Level1Level = 1, #Level2Level = 2, #Level3Level = 3
#Level1Id = 0, #Level2Id = 3, #Level3Id = 1
I would want recordIds 2 and 3 back. Unfortunately, in the second case, I'm getting all 3 records back, which makes sense, because of the OR in my where clause. I can't figure out how to limit my result set to say "only choose SourceLevel 1 if I haven't already matched on SourceLevel 2 or 3".
Anyone have any thoughts, assuming this makes any sense?
To clarify: I want each FooId from my result set, but only the most specific FooId available, based on the SourceLevel parameters passed in.
Here's what I ended up doing:
WHERE
(
(SourceLevel = #Level3Level AND SourceId = #Level3Id)
OR
(
SourceLevel = #Level2Level AND SourceId = #Level2Id
AND NOT EXISTS (SELECT 'X' FROM SourceTable WHERE SourceLevel = #Level3Level And SourceId = #Level3Id AND FooId = SourceTable.FooId)
)
OR
(
SourceLevel = #Level1Level AND SourceId = #Level1Id
AND NOT EXISTS (SELECT 'X' FROM SourceTable WHERE SourceLevel = #Level3Level And SourceId = #Level3Id AND FooId = SourceTable.FooId)
AND NOT EXISTS (SELECT 'X' FROM SourceTable WHERE SourceLevel = #Level2Level And SourceId = #Level2Id AND FooId = SourceTable.FooId)
)
)
This seems to do the filtering I was after...sorry that the question was so confusing. :)
(SourceLevel = 1 AND SourceId = 0) match 1 and 3 records
(SourceLevel = 2 AND SourceId = 3) no matches
(SourceLevel = 3 AND SourceId = 1) match 2 record
All three records will be returned by the query.
I don't see exactly the point and why it's a sql question. But if you want SourceLevel 1 (i call it SL1 now) only if you haven't yet SL2 or SL3...
why don't you try with this:
SL2 OR SL3 OR (NOT SL2 AND NOT SL3 AND SL1)

SQL - "IF" in Where Clause

I'm trying to write a stored procedure that will have 6 bit value flags as parameters and a couple of other values.
The pseudo sql I want to write is something like:
SELECT *
FROM theTable
WHERE
IF #flagA = 1 THEN theTable.A = 1
IF #flagB = 1 THEN theTable.B = 1
IF #flagC = 1 THEN theTable.CValue = #cValue
etc
Any ideas how I can do this in SQL or am I best reverting to building the SQL in C# (where this SP will be called from)?
SELECT *
FROM theTable
WHERE
(#flagA = 0 or (#flagA = 1 AND theTable.A = 1 ))
and (#flagB = 0 or (#flagB = 1 AND theTable.B = 1 ))
and (#flagC = 0 or (#flagC = 1 AND theTable.CValue = #cValue ))
Note: I am assuming your bit flags are non-nullable. If that is not the case, you will need to use ISNULL.