I have the following problem:
Scenario:
I've recomposed the table structure to indicate the problem and to hide the irrelevant details.
Let's say I have a table of items.
Item: item_id | buyer_id | unit_id | status_id
All the columns have a not null value referencing to corresponding table.
Other table that I have is a price-catalog with the following structure:
Price: price_id | buyer_id | unit_id | status_id | price
Here any of the referencing values may have the value "-1" meaning "All". So we could have the following rows in price table:
| price_id | buyer_id | unit_id | status_id | price |
| 1 | -1 | 1 | 2 | 15 |
| 2 | 1 | 1 | 2 | 25 |
| 3 | -1 | -1 | 3 | 13 |
Here price 1 would match items with any buyer_id and unit_id = 1, status_id = 2 and price 2 would math items with buyer_id = 1, unit_id = 1 and status_id = 2. Price 3 would match items with any buyer_id, any unit_id and status_id = 3.
If there are multiple prices matching an item the selection is done with the following logic: Take the price that has the most fields specified (smallest amount of -1 values). If there are multiple prices with the same amount of -1 values then we pick the one that has a value different from -1 in the buyer_id. If such doesn't exist we pick the one with "not -1" value at unit_id and last the one with "not -1" value at status.
We can assume that there are not multiple prices matching the exact same group of prices (all prices have a unique combination of buyer_id, unit_id and status_id.)
Problem:
Now I need to make query that selects the right price for each item in item-table. So far i got this:
SELECT item_id, price
FROM item
INNER JOIN price ON 1=1
AND (price.buyer_id = item.buyer_id OR price.buyer_id = -1)
AND (price.unit_id = item.unit_id OR price.unit_id = -1)
AND (price.status_id = item.status_id OR price.status_id = -1)
Basically this will get all the matching prices. So if we have item with buyer_id = 1, unit_id = 1 and status_id = 2 we would get two rows:
| item_id | price |
| 1 | 15 |
| 1 | 25 |
My first idea was to use GROUP BY, but I haven't figured out how to do it so that the price selected is the right one and not just a random/first(?) one.
EDIT: Tried to order the rows (ORDER BY buyer_id DESC, unit_id DESC, status_id DESC) and then group the results based on the ordered sub-query but it seems that the order by doesn't work that way.
So how can I select the right row in GROUP BY? Or what would be an alternative solution to get right prices with a single query (sub-queries are fine)?
Altering the table structure is not really an option in this particular case.
Update
I've been using the solution I submitted earlier but the amount of criteria that the price is selected by has gone up from three to five. This means that I currently have a list of 32 different combinations in my CASE WHEN structure. In case I would need to add another one the list will double again, so I'm still looking for a better solution.
For now the problem was solved as follows. It does the job and at least in the specific case perform fast enough, but I would hope a better solution exists. For now this is what does the trick.
Because I have quite a small group of possible combinations it is relatively easy to form a CASE clause to give numeric value for each combination to represent the order of the different combinations. Next we'll pick for each item_id the row that has the smallest selector value. This is done by Joining with simple group-identifier, max-value-in-group Sub-query. Here is a query to demonstrate the idea:
SELECT item_id, price
FROM (
SELECT item_id, price,
CASE
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 1
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 2
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 3
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 4
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 5
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 6
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 7
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 8
END AS selector
FROM item
INNER JOIN price ON 1=1
AND (price.buyer_id = item.buyer_id OR price.buyer_id = -1)
AND (price.unit_id = item.unit_id OR price.unit_id = -1)
AND (price.status_id = item.status_id OR price.status_id = -1)
) AS all_possible_prices
INNER JOIN (
SELECT item_id, MIN(selector)
FROM (
SELECT item_id
CASE
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 1
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 2
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 3
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 4
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 5
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 6
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 7
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 8
END AS selector
FROM item
INNER JOIN price ON 1=1
AND (price.buyer_id = item.buyer_id OR price.buyer_id = -1)
AND (price.unit_id = item.unit_id OR price.unit_id = -1)
AND (price.status_id = item.status_id OR price.status_id = -1) ) AS min_seeds
GROUP BY item_id
) AS min_selectors ON all_possible_prices.item_id = min_selectors.item_id
AND all_possible_prices.selector = min_selectors.selector
Related
I'm trying to write a query that will pull students that have passed tests 1 thru 3 AND failed test 4.
Students can retake tests so there may be failed records, followed by passed records for some tests, such is the case with student_id = 2 below.
Table setup like this -
test_id | student_id | status | completed_on
--------+------------+---------+------------
1 | 1 | passed | 2018-03-24
2 | 1 | passed | 2018-03-25
3 | 1 | passed | 2018-03-26
4 | 1 | failed | 2018-03-27
1 | 2 | failed | 2018-03-24
1 | 2 | passed | 2018-03-25
2 | 2 | passed | 2018-03-26
3 | 2 | passed | 2018-03-27
4 | 2 | failed | 2018-03-27
In this case the query should pull both student_id 1 and 2
I tried this but it obviously didn't work -
select *
from table
where (test_id = 1 and status = 'passed')
and (test_id = 2 and status = 'passed')
and (test_id = 3 and status = 'passed')
and (test_id = 4 and status = 'failed')
Demo
SELECT count(Z.Test_ID), Z.student_ID
FROM (SELECT distinct student_ID, test_ID, Status
FROM table) Z
WHERE (Z.Status = 'Passed' and Z.test_ID in (1,2,3,4))
OR (Z.status = 'Failed' and Z.test_ID = 4)
GROUP BY Z.Student_ID
HAVING count(Z.Test_ID) = 4;
This works by first ensuring we only have distinct records for each student, status, and test_ID. (derived table Z)
We then evaluate how many passes in tests 1,2,3,4 exist and existence of fails we have for test 4. if the count is anything other than 4 then we know either they didn't pass the tests 1-3 and fail 4 or they've passed test 4 as well.
I'm not claiming this is fast or most efficient, but it will do the job. Make sure you have the right indices on your table,
SELECT s1.student_id
FROM mytable s1
JOIN mytable s2 on s1.student_id=s2.student_id and s2.test_id=2 and s2.status='passed'
JOIN mytable s3 on s1.student_id=s3.student_id and s3.test_id=3 and s3.status='passed'
WHERE s1.test_id=1
AND s1.status='passed'
AND NOT EXISTS (
SELECT 1
FROM mytable s4
WHERE s4.student_id=s1.student_id
AND s4.test_id=4
AND s4.status='passed'
)
Another approach:
select distinct t1.student_id
from mytable t1
inner join
-- students passed all the 3 tests
(select student_id from mytable where test_id in (1, 2, 3)
and status = 'passed' group by student_id having count(distinct test_id) = 3 ) t2
on t1.test_id = 4 and t1.status = 'failed' and t1.student_id = t2.student_id
where not exists
(select 1 from mytable where student_id = t1.student_id and
status = 'passed' and test_id = 4)
PS. If a student passed a test (e.g. test 1) but later took it again and failed, the student would be considered as passed. Not sure if that is acceptable.
One approach you can use to meet your requirement is to basically make a pivot table. Any way you slice it you will probable need to use a subquery, here using the SUM function and CASE statements you can determine how many times each student passed a test.
Then, in your outer WHERE clause you can select only the rows where you got 1 or more 'passed' results for tests 1 through 3, and 0 'passed' results for test 4.
SQL Fiddle
SELECT student_id
FROM (SELECT student_id,
SUM(
CASE WHEN(test_id = 1 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test1",
SUM(
CASE WHEN(test_id = 2 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test2",
SUM(
CASE WHEN(test_id = 3 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test3",
SUM(
CASE WHEN(test_id = 4 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test4"
FROM TestResults
GROUP BY student_id) tr
WHERE Test1 > 0 AND Test2 > 0 AND Test3 > 0 AND Test4 = 0
Using this technique you can also determine how many times a student passed or failed a test. So for example, you can change the CASE statements to 'failed' and return the test columns in the outer query to see the number of times a student failed the test. For example:
SELECT student_id, Test1Fails
FROM (SELECT student_id,
SUM(
CASE WHEN(test_id = 1 AND result = 'failed')
THEN 1
ELSE 0
END) AS "Test1Fails"
FROM TestResults
GROUP BY student_id) tr
I'm trying to select the sum of the values in the isOK column for each Name separated, BUT only if isOK = 1 on Day = 2.
The query for the following example table tablename
Name | Day | isOK
char | int | int
-----------------
Flo | 1 | 1
Seb | 1 | 1
Tim | 1 | 0
Flo | 2 | 1
Seb | 2 | 0
Tim | 2 | 1
should give Flo: 2 and Tim: 1, but not Seb: 1, since his isOK on Day = 2 is 0.
I've tried using SUM(isOK) with IF constructs, but it's just not working. My alternative solution, to select all Name where isOK = 1 first and select the SUM(isOK) for each of the names is slow and seems in need of improvement.
I guess it's not that difficult, but I've been trying for hours now and I just can't combine my two queries into one.
One way to do this is to use a conditional expression together with a having clause like this:
select name, sum(isOk) ok_sum
from your_table
group by name
having sum(case when day = 2 and isOK = 1 then 1 else 0 end) > 0;
With your sample data the result would be:
name ok_sum
Flo 2
Tim 1
As MySQL evaluates boolean expressions as 1 or 0 it should be possible to reduce the condition to this:
having sum(day = 2 and isOK = 1) > 0;
Another way to do it would be to use a correlated subquery that makes sure there exists a row with Day = 2 and isOk = 1 for the Name:
select t1.name, sum(t1.isOk) ok_sum
from your_table t1
where exists (
select 1
from your_table t2
where t2.day = 2 and t2.isOK = 1 and t1.name = t2.name
)
group by t1.name
See this fiddle
TRY this :
SELECT
name, SUM(isok) AS isOk
FROM
table
GROUP BY `name`
HAVING SUM(`day` = 2 AND isok = 1) > 0;
SELECT x.name, SUM(y.isOK) total
FROM my_table x
JOIN my_table y
ON y.name = x.name
WHERE x.day = 2
AND x.isok=1
GROUP
BY x.name;
I have a column with two columns. one is TIMESTAMP and the other DIGITAL_BIT.
The value digital bit can be either 0 or 1 and changes a few times during the day. Every minute of the day is stored in this table. I would need to read somehow how many times a day this value changed from 0 to 1.
Is it possible to make a query that returns the count of this changes? What I have in mind is something like this:
select * from mytable where digital_bit = 1 and digital_bit (of previous row) = 0 order by timestamp
Can this be done with a query or do i have to process all data in my program?
Thanks
SAMPLE
timestamp | digital_bit
100000 | 0
100001 | 0
100002 | 1
100003 | 1
100004 | 0
100005 | 1
100006 | 0
100007 | 0
100008 | 1
the above should return 3 because for 3 times the value digital passed from 0 to 1. i need to count how often the value digital CHANGES from 0 to 1.
Here you go. This will get you a count of how many times digital_bit switched from 0 to 1 (in your example, this will return 3).
SELECT COUNT(*)
FROM mytable curr
WHERE curr.digital_bit = 1
AND (
SELECT digital_bit
FROM mytable prev
WHERE prev.timestamp < curr.timestamp
ORDER BY timestamp DESC
LIMIT 1
) = 0
SQLFiddle link
(Original answer relied on the timestamps being sequential: e.g. no jumps from 100001 to 100003. Answer has now been updated not to have that restriction.)
IF you have a result once per minte, you can simple join the table with itself, and
use timestamp+1 as well as leftbit != rightbit as join condition.
http://sqlfiddle.com/#!8/791c0/6
ALL Changes:
SELECT
COUNT(*)
FROM
test a
INNER JOIN
test b
ON
a.digital_bit != b.digital_bit
AND b.timestamp = a.timestamp+1;
Changes from 0 to 1
SELECT
COUNT(*)
FROM
test a
INNER JOIN
test b
ON
a.digital_bit = 0 AND
a.digital_bit != b.digital_bit
AND b.timestamp = a.timestamp+1;
Changes from 1 to 0
SELECT
COUNT(*)
FROM
test a
INNER JOIN
test b
ON
a.digital_bit = 1 AND
a.digital_bit != b.digital_bit
AND b.timestamp = a.timestamp+1;
Adapted from: How do I query distinct values within multiple sub record sets
select count(*)
from (select t1.*,
(select digital_bit
from table t2
where t2.timestamp < t1.timestamp
order by timestamp desc LIMIT 1
) as prevvalue
from table t1
) t1
where prevvalue <> digital_bit and digital_bit = 1;
This isn't likely to be efficient with a lot of data, but you can get all the rows and calculate a sequence number for them, then do the same again but with the sequence number offset by 1. Then join the 2 lots together where those calculated sequence numbers match but the first one has a digital bit of 0 and the other a digital bit of 1:-
SELECT COUNT(*)
FROM
(
SELECT mytable.timestamp, mytable.digital_bit, #aCount1:=#aCount1+1 AS SeqCount
FROM mytable
CROSS JOIN (SELECT #aCount1:=1) sub1
ORDER BY timestamp
) a
INNER JOIN
(
SELECT mytable.timestamp, mytable.digital_bit, #aCount2:=#aCount2+1 AS SeqCount
FROM mytable
CROSS JOIN (SELECT #aCount2:=0) sub1
ORDER BY timestamp
) b
ON a.SeqCount = b.SeqCount
AND a.digital_bit = 0
AND b.digital_bit = 1
EDIT - alternative solution and I would be interested to see how this performs. It avoids the need for adding a sequence number and also avoids a correlated sub query:-
SELECT COUNT(*)
FROM
(
SELECT curr.timestamp, MAX(curr2.timestamp) AS MaxTimeStamp
FROM mytable curr
INNER JOIN mytable curr2
ON curr.timestamp > curr2.timestamp
AND curr.digital_bit = 1
GROUP BY curr.timestamp
) Sub1
INNER JOIN mytable curr
ON Sub1.MaxTimeStamp = curr.timestamp
AND curr.digital_bit = 0
As I understood you have one query every minute. So you have no problem with performance.
You can add flag:
timestamp | digital_bit | changed
100000 | 0 | 0
100001 | 0 | 0
100002 | 1 | 1
100003 | 1 | 0
100004 | 0 | 1
100005 | 1 | 1
100006 | 0 | 1
100007 | 0 | 0
100008 | 1 | 1
And make check before insert:
SELECT digital_bit
FROM table
ORDER BY timestamp DESC
LIMIT 1
and if digital_bit is different insert new row with flag.
And then you just can take COUNT of flags:
SELECT COUNT(*)
FROM table
WHERE DATE BETWEEN (start, end)
AND changed = 1
Hope will see in answers better solution.
I have a table that has this structure:
table
id | site_id
-------------------
240 | 1
240 | 2
240 | 3
320 | 1
320 | 2
421 | 1
520 | 2
-------------------
300k records
Now i am trying to write a query to return a yes or a no for each record (id).
For example if the records with id 240 only have a site_id 1 then return 'Yes', if it has 2, 3 and so on return 'No'
I am not sure how to approach it but here is a result sample:
result_table
.-----------------------.
| id | result |
|-----------------------|
| 240 | No | -- has a site_id 1, 2 and 3
| 320 | No | -- has a site_id 1 and 2
| 421 | Yes | -- has a site_id 1 only
| 520 | No | -- has a site_id 2
'-----------------------'
Here is the query i have so far, but it seems to be incorrect
SELECT CASE WHEN count(id) > 1 THEN 'N' ELSE 'Y' END as Result
FROM table sm
WHERE sm.site_id IN (1)
AND sm.site_id NOT IN (2,3,4,5,6,7,8,9)
AND id = 240
UPDATE
SO Here is my full query, i added the answer from #gordon
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isTjoos, -- y or no
(select (case when count(site_id) = 2 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isUCOnly
-- isdlkonly -- y or no
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
I interpreted this question as you want ids that have only one value for site_id. I took the example in the question to be an example, with site_id = 1. To do this:
You want to use count(distinct):
select id, (case when count(distinct site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
A slightly more efficient version is to use min() and max(), assuming that the site_id is never NULL:
select id, (case when min(site_id) = max(site_id) then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
This is because min and max generally require a bit less processing than count(distinct).
If you want to check that the site_id is "1" and never anything else, then add the condition and min(site_id) = 1 to the when clause.
If you want to check that the site_id is 1 and there is exactly one row, then you can do:
select id, (case when count(site_id) = 1 and min(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
And, if you want to check that there is exactly one row:
select id, (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
SELECT
it,
CASE WHEN COUNT(CASE WHEN site_id THEN 1 END)=1
AND COUNT(CASE WHEN site_id!=1 THEN 1 END)=0 THEN 'Yes'
ELSE 'No'
END
FROM sm
GROUP BY it
Please see fiddle here.
SELECT ID, CASE WHEN EXTRA > 1 THEN 'No' ELSE 'Yes' END AS Result
FROM
(SELECT ID, Sum(site_id) AS Extra
from myTable
GROUP BY ID
) AS Test
EDIT: I suppose this should work in MySQL. I haven't worked on it though.
The idea is to SUM up the site_id. For records with only site_id = 1, the sum will be 1.
Your query seems overcomplicated. Just to start, why the IN(1) and NOT IN(2,3...9)? And why limit to a single ID (AND id = 240) when your "result sample" clearly doesn't want that? It does not make any sense. How about this?
SELECT CASE WHEN count(merchant_id) > 1 THEN 'N' ELSE 'Y' END as isTjoos
FROM site_merchant
GROUP BY site_id;
I would use a Having Count Statement. Something like that:
SELECT site_id
FROM site_merchant
HAVING (count(merchant_id) > 1)
GROUP BY site_id;
Here is the solution i found. I used #Gordons query to get started and what was missing was the site_id, and the group by was not needed:
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(*)>0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 1) as isTjoos,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 2) as isUCOnly,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 3) as isDLKonly
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
Thank you for the help.
I have a doubt on how variables work in mysql. As I read in their web looks like setting a variable will become visible to the next row.
My table is like:
A B C N
1 NULL NULL 4
1 NULL NULL 4
1 1 NULL 4
1 1 NULL 4
1 1 1 4
1 1 1 4
What I want is to return only the rows with C = 1. If no rows then return B = 1 and C is NULL if no rows A = 1 and B is NULL and C is NULL.
My idea was:
select N as number,
#var_c := case when (C = 1) then 1 else -1 end as myc,
#var_b := case when (#var_c < 0 and B = 1) then 1 else -1 end as myB,
#var_c := case when (#var_a < 0 and var_b < 0 and C = 1) then 1 else -1 end as myC
from (select #var_a := -1) r_a,
(select #var_b := -1) r_b,
(select #var_c := -1) r_c,
(select A, B, C, N from my_table order by A desc, B desc, C desc) rows
It should (I want to) return
number myA myB myC
4 -1 -1 1
4 -1 -1 1
4 -1 -1 -1
4 -1 -1 -1
4 -1 -1 -1
4 -1 -1 -1
With this and having myA > 0 or myB > 0 or myC > 0 would work.
But it is returning
number myA myB myC
4 1 -1 -1
4 1 -1 -1
4 -1 -1 1
4 -1 -1 1
4 -1 1 -1
4 -1 1 -1
Shouldn't Mysql keep the vars across the rows?
Regards.
First of all, do not use variables like this. You shouldn't expect that you can set a variable in a FROM clause and then access the value in the SELECT clause. That's not how variables work. Normally you should only use a variable in one place in the query, otherwise you can get undeterministic results.
Second of all, why don't you just issue three different queries? First for A = 1 and B is NULL and C is NULL, and if it doesn't return any rows, issue a query with the second condition set. And so forth.
And if you ultimately want to issue just a single query, you can try this:
SELECT N as number
FROM my_table
WHERE IF(EXISTS (SELECT A FROM my_table WHERE A = 1 and B is NULL and C is NULL),
A = 1 and B is NULL and C is NULL,
IF(EXISTS (SELECT A FROM my_table WHERE B = 1 and C is NULL), B = 1 and C is NULL, C = 1))
But it's very likely to kill performance. So better just use three queries instead of one.
UPD: There's another (yet similar) approach:
SELECT N as number
FROM my_table
WHERE (
A = 1
AND B is NULL
AND C is NULL
) OR (
B = 1
AND C is NULL
AND NOT EXISTS (SELECT A FROM my_table WHERE A = 1 and B is NULL and C is NULL)
) OR (
C = 1
AND NOT EXISTS (SELECT A FROM my_table WHERE A = 1 and B is NULL and C is NULL)
AND NOT EXISTS (SELECT A FROM my_table WHERE B = 1 and C is NULL)
)
From the other sample and it's comments, I would approach your query this way. The first query to pre-qualify if there are ANY "C = 1" values and store into a CCnt field. If none found, force the CCnt value to NULL. SECOND, do the same against the "B" column. Since each of these has a limit of 1, should be fast as long as it can FIND an entry of C=1 or B=1 respectively. if not, their values are NULL. Why do this way? The query is done ONCE per run of the query and the answer is done. It doesn't have to keep doing the query repeatedly as can be KILLER performance when used as sub-queries. Doing a count() with no group by will GUARANTEE as single row, so you won't have any Cartesian result that could result in duplicates.
Now that this preemptive querying is done, now add in your main table. At this point, the "HasCEntry.CCnt" value will either be a 1 or NULL.
"HasBEntry.CCnt" value will either be a 1 or NULL.
select
YT.*
from
( select IF( count(*) > 0, 1, NULL ) as CCnt
from YourTable
where C = 1
limit 1 ) HasCEntry,
( select IF( count(*) > 0, 1, NULL ) as BCnt
from YourTable
where B = 1
limit 1 ) HasBEntry,
YourTable YT
where
YT.C = 1
OR ( YT.B = 1 AND HasCEntry.CCnt IS NULL )
OR ( YT.A = 1 AND HasBEntry.BCnt IS NULL AND HasCEntry.CCnt IS NULL )
Now, following the WHERE clause. If there is even 1 record where C=1, then the
YT.C = 1 is all you need and its done... and the other "OR" conditions you don't care about, the record is coming along.
Now, say there are NO entries for C = 1. We know the "CCnt" value is NULL, so that will come into the first "OR" condition and testing against "B"... B Specifically = 1 AND the CCnt IS NULL... So, if there was an entry for "C=2" (3, 4 or any other value), we don't want it. We only want B = 1 AND C = NULL...
If C and B fail, then we fall into the "A" test. Likewise, A = 1 AND B is null AND C is NULL for same reasons as B above.
For a small table, you might not notice any significant performance difference from Shedal's answer. However, it COULD be a huge hit as your table grows.