Where Clause 'drops' more rows than expected - sql

I have a SQL Server 2008 R2 query that was returning "hypothetically" 100 rows. I'm actually working with 7k - 8k rows.
The Where clause is something like this:
Where Col_a = 'Y'
And Col_b = 'N'
And Col_c = 'X'
and 25 of the rows had 'P' in Col_d.
I added:
And Col_d = 'P'
and the query returned the expected 25 rows.
Then I changed to
And Col_d <> 'P'
I expected to get 75 rows but I got only 50.
I thought adding "And Col_d <> 'P'" would only restrict the rows in which there is a 'P' in Col_d.
Why is that not the case and how do I figure out what else is getting dropped when I say And Col_d <> 'P'?
As I said - I am actually working with larger numbers so it is not that easy to eyeball it.
I'd appreciate any help.
Thanks!

As stated in the comment, null is a special case when it comes to comparisons.
Assume the following data.
id someVal
----
0 null
1 1
2 2
With a query:
select id
from table
where someVal = 1
would return id 1
select id
from table
where someVal <> 1
would return id 2
select id
from table
where someVal is null
would return id 0
select id
from table
where someVal is not null
would return both ids 1 and 2.
If you wanted nulls to be "counted" as values in a = <> comparison, it needs to be cast to something like:
select id
from table
where isNull(someVal, -1) <> 1
returns 0 and 2
Or you can change your ANSI Null setting.
What I want to do is only exclude the rows that have 'P' in Col_d
So in your specific case, because you want to treat null in Col_D as a non P row, your query could look like this:
select *
from someTable
Where Col_a = 'Y'
And Col_b = 'N'
And Col_c = 'X'
And isNull(Col_D, 'someArbitraryValue') <> 'P'
You have to do the above, because as I pointed out throughout the answer and in the links null does not compare the same way as values. You need to make the null something that is not null, (accomplished with isNull(Col_D, 'someArbitraryValue')) or change ANSI NULL setting in order to compare it as equal or not equal to some value.
Or as #Andrew pointed out magic numbers are bad (someArbitraryValue), so you could instead do:
select *
from someTable
Where Col_a = 'Y'
And Col_b = 'N'
And Col_c = 'X'
And (Col_D <> 'P' OR Col_D is null)
Normally I would do the directly above query, I was doing it the other way to mostly point out the differences in null comparison vs a value.

Related

Excluding a value when null is present in the column

I want to filter the table without the row c
column 1
column 2
a
100
b
200
c
50
null
200
Desired output
column 1
column 2
a
100
b
200
null
200
I tried
select *
from table
where column1 <> 'c'
But since I can compare with null, I'm getting the wrong output. How do I deal with this?
You need to handle the null as follows:
select * from table where column1 <> 'c' or column1 is null
Or you can use the coalesce function as follows:
select * from table where coalesce(column1,'cc') <> 'c'
Coalesce will replace the null value in column1 with the value provided as the second argument. I have used the value which is not equal to 'c' so records with column1 as null will pass this condition
ANSI SQL, DISTINCT predicate.
select *
from table
where column1 is distinct from 'c'
However, not supported by all dbms products.

Oracle SQL - A count that excludes completely a Key with the if not equal statement

Ok here goes:
MyTableName
KEY Value1
1 ABC
1 DEF
1 GHI
2 ABC
2 DEF
2 YUI
I have a child table that is linked via a Key (stated above)
I need to find records, linked to the parent table (i have no issues with my join) where value 1 meets conditions and does not meet certain conditions.
So, for my Example, I want to retrieve the value '1' from the "KEY" above, because my need is where value 1 = 'ABC' or value 1 = 'DEF' but value 1 != YUI,
so I would want '1' to come back, but not 2
SELECT KEY, count(*)
FROM MyTableName
WHERE (value1 = 'ABC'
OR value1 = 'DEF')
AND value1 != 'YUI'
GROUP BY KEY
HAVING count(KEY) = 2
The above statement, find both Key 1 and 2, where I need it only to find Key 1.
Can anyone help?
Use the HAVING clause with conditional counts:
select key
from mytablename
group by key
having count(case when value1 in ('ABC','DEF') then 1 end) > 0
and count(case when value1 = 'YUI' then 1 end) = 0;
(You can add where value1 in ('ABC','DEF','YUI'), which may speed up the query, because you are not interested in rows containing other values.)
(Some people prefer to include an explicit else branch: count(case when value1 = 'YUI' then 1 else null end) and others prefer sum(case when value1 = 'YUI' then 1 else 0 end). That's a matter of personal preference.)

Case expression with multiple results

I am looking for a way to actually create some duplication in my results in MS SQL Server. I understand that typically you are looking for ways to not create duplication, but in this examples I need all the individual rows returned.
I am working with a table with about 10 million rows and 33 columns. The table consists of an ID in the first column and the remainder of the columns have either 'Y' or 'NULL' in them - a HUGE majority of the columns are NULL.
Of the 10 million rows 8 million of them only have a single 'Y' per row with the remaining 2 million rows having more than one column with a 'Y' in a row.
For the rows with a single 'Y' a basic case expression works perfectly fine to create a single column of results .
Here is my problem though - I want two rows, one for each 'Y' if there is more than one 'Y' in a row.
Below is a small de-identified sample.
ID FLAG1 FLAG2 FLAG3 FLAG4 FLAG5
188 NULL NULL NULL NULL NULL
194 Y NULL NULL NULL NULL
200 Y NULL NULL NULL Y
I am attempting to use a Case Expression like this.
Select
ID
,Case
When [FLAG1] = 'Y'
Then 'FLAG1'
When [FLAG2] = 'Y'
Then 'FLAG2'
End as 'Service_Line'
What I want is a result that looks like this.
ID Service_Line
194 FLAG1
200 FLAG1
200 FLAG5
My problem is that the Case expression only returns the first result so I end up with this.
ID Service_Line
194 FLAG1
200 FLAG1
Is a Case Expression appropriate for what I am trying to accomplish or should I be trying to go about this some other way?
A case is not appropriate. A general SQL approach would be:
select id, 'FLAG1' as flag
from t
where flag1 = 'Y'
union all
select id, 'FLAG2' as flag
from t
where flag2 = 'Y'
union all
select id, 'FLAG3' as flag
from t
where flag3 = 'Y'
. . .
There are other (more efficient) methods, but those depend on the database you are using.
I should note: case is the right approach if you want the values in a single row:
select id,
concat( case when flag1 = 'Y' then 'FLAG1 ' else '' end,
case when flag2 = 'Y' then 'FLAG2 ' else '' end,
case when flag3 = 'Y' then 'FLAG3 ' else '' end,
. . .
) as flags
from t;
Of course, the syntax for concat() can vary among databases (concat() itself is ANSI standard). You can also trim off the last space.

Impala SQL, return value if a string exists within a subset of values

I have a table where the id field (not a primary key) contains either 1 or null. Over the past several years, any given part could have been entered multiple times with one, or both of these possible options.
I'm trying to write a statement that will return some value if there is ever a 1 associated with the select statement. There are lots of semi-duplicate rows, some with 1 and some with null, but if there is ever a 1, I want to return true, and if there are only null values, I want to return false. I'm not sure how to code this though.
If this is my SELECT part,id from table where part = "ABC1234" statement
part id
ABC1234 1
ABC1234 null
ABC1234 null
ABC1234 null
ABC1234 1
I want to write a statement that returns true, because 1 exists in at least one of these rows.
The closest I've come to this is by using a CASE statement, but I'm not quite there yet:
SELECT
a1.part part,
CASE WHEN a2.id is not null
THEN
'true'
ELSE
'false'
END AS id
from table.parts a1, table.ids a2 where a1.part = "ABC1234" and a1.key = a2.key;
I also tried the following case:
CASE WHEN exists
(SELECT id from table.ids where id = 1)
THEN
but I got the error subqueries are not supported in the select list
For the above SELECT statement, how do I return 1 single line that reads:
part id
ABC1234 true
You can use conditional aggregation to check if a part has atleast one row with id=1.
SELECT part,'True' id
from parts
group by part
having count(case when id = 1 then 1 end) >= 1
To return false when the id's are all nulls use
select part, case when id_true>=1 then 'True'
when id_false>=1 and id_true=0 then 'False' end id
from (
SELECT part,
count(case when id = 1 then 1 end) id_true,
count(case when id is null then 1 end) id_false,
from parts
group by part) t

Oracle: Get All Rows Except One

I am stuck with a simple query. What i want is to get all rows except one Kindly have a look at the following data.
COL_A COL_B
B D
B (null)
B C
G D
G (null)
G C
I want to get all rows except but B C. Kindly have a look at the sqlfiddle
I have tried to get the rows by anding col_A <> 'B' and col_B <> 'C' but it's not anding the operation. Your help will be much appreciated.
Thanks
One possible solution. Maybe not the most elegant:
select req_for col_A, doc_typ col_B
from a
where (req_for IS NULL OR doc_typ IS NULL)
OR (req_for,doc_typ)
NOT IN (select 'B','C' from dual);
Try
where not(col_A = 'B' and col_B = 'C')
or
where col_A <> 'B' or col_B <> 'C'
Your problem is the NULL values. Here is a concise way of expressing this in Oracle:
where (col_A || col_B) <> 'BC'
Oracle treats NULL values as the empty string in string concatenation.
Ah, negatives. Always causing trouble. With a SQL query like this you have to think about what you want to INCLUDE, not what you want to EXCLUDE.
If you do where nvl(doc_typ,'NA') <> 'C' and nvl(req_for, 'NA') <> 'B';, you aren't including any rows with doc_type of C, and you aren't including any rows with req_for of B.
You want to do where nvl(doc_typ,'NA') <> 'C' or nvl(req_for, 'NA') <> 'B';. This way a doc_type of C will still be included, as long as its req_for isn't also B.
Could you not just remove the unwanted row:
select req_for col_A, doc_typ col_B from a
where NOT (NVL(doc_typ,'NA') = 'C' AND nvl(req_for,'NA') = 'B');
select req_for col_A, doc_typ col_B from a
where req_for||doc_typ != 'BC';
select req_for col_A, doc_typ col_B from a
where case when req_for = 'B' and doc_typ='C' then 0 else 1 end > 0