Using a case/if in the where clause SQL - mysql

So I have a list of items organized by date in two different categories, when switching the categories I sometimes run into an error that does not let the item go into the correct placement. The thing that is messing up the query is what I want to place in a case/if statement becuase it is only needed if there is an item with the same date, anyother time it throws off the whole query. Here is what I have, granted i know that that case does not work where it is or how it is, please work with me.
SELECT CASE WHEN COUNT(*)=0 THEN (SELECT MAX(Rotate)+1 FROM Table1 WHERE Vol=1)
ELSE MIN(o.Rotate) END as nRotate FROM Table1 o INNER JOIN Table2 s ON o.SID=s.ID
WHERE s.Date >='7/30/2004' And s.ID<>100 And o.Vol=1 and
Case s.DATE
When '7/30/2004' then s.Sales>'Doe, Jane'
End

You don't need case:
WHERE s.Date >='2004-07-30' And s.ID <> 100 And o.Vol = 1 and
(s.date <> '2004-07-30' or s.Sales > 'Doe, Jane')

Related

MySQL Return a 0 when row doesn't exist

Below is an incredibly complex and hard to read SQL statement we needed to solve our problem and we are wondering if there was an easier way to do this that we just overlooked or didn't know.
The Problem: We are creating a report that needs to sum sales data for a particular sales category and group that by category. The issue is that when there is an instance of one of the shifts not having any sales in that category it returns nothing. This doesn't work for us because this will cause data to not line up correctly with the shifts. So all we need is for the query to return a zero for the shift if no instances of it exist. We tried left joins, case statements, coalesce, unions etc... What finally got to work is the monster below. Any thoughts on how this could be done simpler would be greatly appreciated.
SELECT shift_seq,
amount
FROM ((SELECT t.shift_seq,
CASE
WHEN EXISTS (SELECT t.shift_seq,
i.amount
FROM a_item AS i,
a_ticket AS t
WHERE i.ticket = t.ticket
AND i.department = 11) THEN
Round(Sum(i.amount), 2)
ELSE 0.00
END AS Amount
FROM a_ticket AS t
LEFT JOIN a_item AS i
ON i.ticket = t.ticket
WHERE i.department = 11
GROUP BY t.shift_seq
ORDER BY t.shift_seq)
UNION
(SELECT t.shift_seq,
0 AS Amount
FROM a_ticket AS t)) AS a
GROUP BY shift_seq
ORDER BY shift_seq
To me it seems that WHERE i.department = 11 condition effectively turns your left join into an inner join because the criterion applies to the right hand side table. This makes your entire case expression superfluous, since the else branch will never execute.
Honestly, I think you only have to move the above criterion into the join condition and then just apply a simple sum() with coalesce() for the null values:
SELECT t.shift_seq,
coalesce(Sum(i.amount),0) AS Amount
FROM a_ticket AS t
LEFT JOIN a_item AS i ON i.ticket = t.ticket AND i.department = 11
GROUP BY t.shift_seq
As Shadow pointed out in the comments above the simpler solution was to move the WHERE i.department = 11 into the JOIN clause. This allowed us to cut it down to the below SQL code instead of our original mess. This resulted in a NULL where there were no sales for the department, but that NULL was easily handled in the PHP that was running this. Thanks again Shadow.
SELECT t.shift_seq,
ROUND(SUM(i.amount),2) AS Amount
FROM a_ticket AS t
LEFT JOIN a_item AS i
ON i.ticket = t.ticket
AND i.department =11
GROUP BY t.shift_seq
ORDER BY t.shift_seq

MySQL Query limiting results by sub table

I'm really struggling with this query and I hope somebody can help.
I am querying across multiple tables to get the dataset that I require. The following query is an anonymised version:
SELECT main_table.id,
sub_table_1.field_1,
main_table.field_1,
main_table.field_2,
main_table.field_3,
main_table.field_4,
main_table.field_5,
main_table.field_6,
main_table.field_7,
sub_table_2.field_1,
sub_table_2.field_2,
sub_table_2.field_3,
sub_table_3.field_1,
sub_table_4.field_1,
sub_table_4.field_2
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
WHERE sub_table_4.field_1 = '' AND sub_table_4.field_2 = '0' AND sub_table_2.field_1 != ''
The query works, the problem I have is sub_table_1 has a revision number (int 11). Currently I get duplicate records with different revision numbers and different versions of sub_table_1.field_1 which is to be expected, but I want to limit the result set to only include results limited by the latest revision number, giving me only the latest sub_table_1_field_1 and I really can not figure it out!
Can anybody lend me a hand?
Many Thanks.
It's always important to remember that a JOIN can be on a subquery as well as a table. You could build a subquery that returns the results you want to see then, once you've got the data you want, join it in the parent query.
It's hard to 'tailor' an answer that's specific to you problem, as it's too obfuscated (as you admit) to know what the data and tables really look like, but as an example:
Say table1 has four fields: id, revision_no, name and stuff. You want to return a distinct list of name values, with their latest version of stuff (which, we'll pretend varies by revision). You could do this in isolation as:
select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no;
(Note: see fiddle at the end)
That would return each individual name with the latest revision of stuff.
Once you've got that nailed down, you could then swap out
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
....with....
INNER JOIN (select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no) sub_table_1
ON sub_table_1.id = main_table.id
...which would allow a join with a recordset that is more tailored to that which you want to join (again, don't get hung up on the actual query I've used, it's just there to demonstrate the method).
There may well be more elegant ways to achieve this, but it's sometimes good to start with a simple solution that's easier to replicate, then simplify it once you've got the general understanding of the what and why nailed down.
Hope that helps - as I say, it's as specific as I could offer without having an idea of the real data you're using.
(for the sake of reference, here is a fiddle with a working version of the above example query)
In your case where you only need one column from the table, make this a subquery in your select clause instead of than a join. You get the latest revision by ordering by revision number descending and limiting the result to one row.
SELECT
main_table.id,
(
select sub_table_1.field_1
from sub_table_1
where sub_table_1.id = main_table.id
order by revision_number desc
limit 1
) as sub_table_1_field_1,
main_table.field_1,
...
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
WHERE sub_table_4.field_1 = ''
AND sub_table_4.field_2 = '0'
AND sub_table_2.field_1 != '';

Rewrite query with only 1 group by clause

This bellow query will result the post wise sum of like
SELECT
tblPost.Post,
SUM(tblPost.LikeCount),
CASE WHEN tblPost.Time
BETWEEN (SELECT
CONVERT(VARCHAR(10),
DATEADD(DD,DATEDIFF(DD 0,GETDATE()),-60),120)) AND CONVERT(date,GETUTCDATE())
THEN 'Last 60 Days'
ELSE 'More Than 1 Year'
END AS"date type"
FROM tblPost
INNER JOIN tblProfile ON (tblProfile.ID=tblPost.UID)
INNER JOIN tblWatchList ON (tblWatchList.ID=tblProfile.UID)
WHERE dbo.tblPost.Time BETWEEN (SELECT
CONVERT(VARCHAR(10),
DATEADD(DD,DATEDIFF(DD,0,GETDATE()), -60),120))AND CONVERT(date,GETUTCDATE())
GROUP BY tblPost.Post,tblPost.Time
This is my query and it is working fine but I want to rewrite this. How can I describe it here... in my query I am having two GROUP BY clauses (tblPost.Post,tblPost.Time) and exactly here I'm getting a problem. I want to rewrite this query such as a way that I can group my result only by tblPost.Post
Please help me.
Your WHERE clause already eliminates the two options you've presented for tblPost.Time - you explicitly state you're only ever going to retrieve "Last 60 days" so why are you bothering to have a whole CASE statement in the query?
And you're joining tables that aren't even represented. So start by cleaning your query up, use some aliases, and drop what you don't need:
SELECT P.Post, SUM(P.LikeCount)
FROM dbo.tblPost P
WHERE P.Time BETWEEN (SELECT CONVERT(VARCHAR(10), DATEADD(DD,DATEDIFF(DD,0,GETDATE()), -60),120)) AND CONVERT(date, GETUTCDATE())
GROUP BY P.Post

optimize Mysql: get latest status of the sale

In the following query, I show the latest status of the sale (by stage, in this case the number 3). The query is based on a subquery in the status history of the sale:
SELECT v.id_sale,
IFNULL((
SELECT (CASE WHEN IFNULL( vec.description, '' ) = ''
THEN ve.name
ELSE vec.description
END)
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
WHERE veh.id_sale = v.id_sale
AND vec.id_stage = 3
ORDER BY veh.id_record DESC
LIMIT 1
), 'x') sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
WHERE 1 =1
AND v.flag =1
AND v.id_quarters =4
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
the query delay 0.0057seg and show 1011 records.
Because I have to filter the sales by the name of the state as it would have to repeat the subquery in a where clause, I have decided to change the same query using joins. In this case, I'm using the MAX function to obtain the latest status:
SELECT
v.id_sale,
IFNULL(veh3.State3,'x') AS sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
LEFT JOIN (
SELECT veh.id_sale,
(CASE WHEN IFNULL(vec.description,'') = ''
THEN ve.name
ELSE vec.description END) AS State3
FROM t_record veh
INNER JOIN (
SELECT id_sale, MAX(id_record) AS max_rating
FROM(
SELECT veh.id_sale, id_record
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign AND vec.id_stage = 3
) m
GROUP BY id_sale
) x ON x.max_rating = veh.id_record
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
) veh3 ON veh3.id_sale = v.id_sale
WHERE v.flag = 1
AND v.id_quarters = 4
This query shows the same results (1011). But the problem is it takes 0.0753 sec
Reviewing the possibilities I have found the factor that makes the difference in the speed of the query:
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
If I remove this clause, both queries the same time delay... Why it works better? Is there any way to use this clause in the joins? I hope your help.
EDIT
I will show the results of EXPLAIN for each query respectively:
q1:
q2:
Interesting, so that little statement basically determines if there is a match between t_record.id_sale and t_sale.id_sale.
Why is this making your query run faster? Because Where statements applied prior to subSelects in the select statement, so if there is no record to go with the sale, then it doesn't bother processing the subSelect. Which is netting you some time. So that's why it works better.
Is it going to work in your join syntax? I don't really know without having your tables to test against but you can always just apply it to the end and find out. Add the keyword EXPLAIN to the beginning of your query and you will get a plan of execution which will help you optimize things. Probably the best way to get better results in your join syntax is to add some indexes to your tables.
But I ask you, is this even necessary? You have a query returning in <8 hundredths of a second. Unless this query is getting ran thousands of times an hour, this is not really taxing your DB at all and your time is probably better spent making improvements elsewhere in your application.

MySQL 'variable' not available in CASE

Basic problem is - I'm trying to use a sub-query's count as a variable in my WHERE CASE statement, but it doesn't appear to let me use it. I've got it somewhat working when I put the SELECT COUNT(id)... statement in the WHERE area, but - if I do that, I'll have to include it 3-4 different times instead of just once if I can put it in the SELECT.
Below is a modified example query that explains my problem. It's not the exact query I'm using, but it gives the same error as my much-longer/more complicated query:
Error:
[Err] 1054 - Unknown column 'matched_sections' in 'where clause'
Query:
SELECT
articles.id,
(SELECT COUNT(id) FROM site_areas_site_sections WHERE
site_areas_site_sections.site_area_id = 8) AS matched_sections
FROM
articles
LEFT JOIN
articles_site_sections ON articles_site_sections.article_id = articles.id
LEFT JOIN
site_areas_site_sections ON articles_site_sections.site_section_id =
site_areas_site_sections.site_section_id
WHERE
(CASE
WHEN
matched_sections > 0
THEN
site_sections.id = site_areas_site_sub_sections.site_sub_section_id
END
)
The problem is when MySQL starts to parse out your query, the SELECT clause is the last bit to be analyzed. So when it's going through the WHERE clause, matched_sections doesn't yet exists (because the SELECT clause has yet to be looked at.
Just a quick look at it, you can try something like this (although I think someone will be able to come up with something a little more elegant):
SELECT
articles.id,
matched_sections.count
FROM
articles
LEFT JOIN
articles_site_sections ON articles_site_sections.article_id = articles.id
LEFT JOIN
site_areas_site_sections ON articles_site_sections.site_section_id =
site_areas_site_sections.site_section_id,
(SELECT COUNT(id) as count FROM site_areas_site_sections WHERE
site_areas_site_sections.site_area_id = 8) matched_sections
WHERE
(CASE
WHEN
matched_sections.count > 0
THEN
site_sections.id = site_areas_site_sub_sections.site_sub_section_id
END
)
Due to the order in which the elements of a SQL statement are processed, matched_sections is not defined at the time the WHERE clause is evaluated.
You might want to prepopulate the count into a variable, and use that in your query.
DECLARE matched_sections INT;
SET matched_sections = (SELECT COUNT(id)
FROM site_areas_site_sections
WHERE site_areas_site_sections.site_area_id = 8);
As you have already been told, the error is related to the fact that the WHERE clause knows nothing about the values you are actually selecting, because it is evaluated before the SELECT clause.
In general, you can solve this problem by using the entire query as a derived table, in which case the matched_sections alias becomes available to the outer query:
SELECT
id,
matched_section
FROM (
SELECT
articles.id,
(SELECT COUNT(id) FROM site_areas_site_sections WHERE
site_areas_site_sections.site_area_id = 8) AS matched_sections
FROM
articles
LEFT JOIN
articles_site_sections ON articles_site_sections.article_id = articles.id
LEFT JOIN
site_areas_site_sections ON articles_site_sections.site_section_id =
site_areas_site_sections.site_section_id
) s
WHERE
CASE
WHEN matched_sections > 0
THEN …
END
If your original condition contains references to columns that are not meant to be retrieved, you will need to add them to the derived table's selection list so you can use them in the outer query's condition.
In cases where the subquery is not correlated with the main query (and it isn't in your example), the solutions by #Joe and by #Dirk are possibly better options than the above suggestion.