Reputation: 19
I need to find records that equal 0 and have other records that are greater than 0. The result should have at least one record that equals 0 and must also have at least one record that is greater than 0. It is easier to explain it with visuals:
Name amount
a1 0
a1 100
a1 200
a2 0
a2 0
a2 200
a3 200
a3 0
a3 100
It should not look like:
Name amount
a5 100
a5 100
a5 200
a7 0
a7 0
a7 0
a6 200
a6 10
a6 100
I have tried this:
Select name, amount
from table1
where amount = '0' AND amount > '0'
Sorry if this question is a bit ambiguous, it's rather hard to explain.
Thanks in advance.
NB - Sorry if the question is not clear enough, wasn't sure how to word it.
Upvotes: 1
Views: 3100
Reputation: 1269753
I would phrase this as:
Select t1.name, t1.amount
from table1 t1
where (t1.amount = 0 and
exists (select 1 from table1 tt1 where tt1.name = t1.name and tt1.amount > 0
) or
(t1.amount > 0 and
exists (select 1 from table1 tt1 where tt1.name = t1.name and tt1.amount = 0
) ;
This can take advantage of an index on table1(name, amount)
. And the beauty of the additional comparison is that only one exists
clause needs to be evaluated for each row in the original table.
Upvotes: 1
Reputation: 8101
And since there are usually multiple ways to solve any given problem, here's another one.
The question you have is a pretty normal pattern. Select a data set that meets N conditions. In this case, there are just two, and both require that a certain type of row exists for given values. So another way to get there is to use the EXISTS
clause.
It's logically very similar to Jason's method using IN
clauses. Jason's answer, by the way, is deserving of being selected as THE answer. I'm just offering this up as an alternative approach to help you keep your options open.
An EXISTS
clause uses a "correlated subquery", which means that a value from the outer query, table1 as t1
, is used by the inner queries (inside the parenthesis, if that helps you picture it), usually in the WHERE
clause of the inner query.
So below, each of the EXISTS
clauses looks for any occurrence of the conditions in their WHERE
clauses; the names match and the amounts meet their criteria. If any occurrence is found, the query returns a result set, which the EXISTS
interprets as TRUE. If not, nothing comes back, which the EXISTS
interprets as FALSE. Note that the results of the inner query don't matter at all, just that the WHERE
clause is satisfied, or not. So I use SELECT 1
to show that the return value doesn't matter. You can put anything you want there, actually; even 1/0
, and it'll work just fine.
select
*
from
table1 as t1
where
exists (select 1
from table1 as t2
where t2.amount = 0
and t2.Name = t1.Name)
and
exists (select 1
from table1 as t3
where t3.amount > 0
and t3.Name = t1.Name);
See it in action here: Rextester demo
Upvotes: 1
Reputation: 222462
You can use window functions. Assuming that there are no negative values, you can do:
select name, amount
from (
select
t.*,
min(amount) over(partition by name) min_amount,
max(amount) over(partition by name) max_amount
from mytable t
) t
where min_amount = 0 and max_amount > 0
If there are negative values:
select name, amount
from (
select
t.*,
max(case when amount = 0 then 1 end) over(partition by name) has_zero_amount,
max(amount) over(partition by name) max_amount
from mytable t
) t
where has_zero_amount = 1 and max_amount > 0
I would expect that window functions would be more efficient that other solutions including several subqueries.
Upvotes: 1
Reputation: 29214
SELECT
will give you rows from the table and the WHERE
applies to those rowsto filter them. So your sample:
Select name, amount from table1 where amount = '0' AND amount > '0'
Will never return any rows because it only returns rows that have both amount = 0 and amount > 0 which is impossible. Also I hope those values are numeric, so you shouldn't use the single quotes around them (i.e. '0' should be plain 0)
GMB has a good way to do it with partition functions. The subquery reshapes the data into a new resultset that contains new columns 'min_amount' and 'max_amount' for all rows with the same id along with the other data for each row. You can then filter on those values, although you don't mention if negative values could be present.
Another way to do it would be to add the checks to your filter criteria:
select name, amount
from table1 a
where a.id in (select id from table1 where amount = 0)
and a.id in (select id from table1 where amount > 0)
This selects rows where id is in the list of ids with 0 as amount and the list of ids with amounts > 0.
Upvotes: 2