Group by Case Statement MySQL - mysql

I am trying to group by the CASE statement but not having much luck. I have an orders table that I am trying to group the orders by total value for the month and categorise them based on their value.
SELECT
CASE
WHEN sum(order_total_price) IS NULL
THEN 'Unknown'
WHEN sum(order_total_price) <= 1000
THEN 'Not more than 1,000'
WHEN sum(order_total_price) <= 2000
THEN 'Between 1,001 and 2000'
WHEN sum(order_total_price) <= 3000
THEN 'Between 2001 and 3000'
WHEN sum(order_total_price) <= 4000
THEN 'Between 3001 and 4000'
WHEN sum(order_total_price) <= 5000
THEN 'Between 4001 and 5000'
ELSE 'Over 5000'
END
AS total_sales,
COUNT(*) as total
FROM orders
WHERE YEAR(order_time)=2014 and MONTH(order_time)=07
GROUP BY total_sales

The shorthand for this is
....
GROUP BY 1
Using the output column position. Otherwise you'd have to repeat the entire case block in the GROUP BY clause.

Your query should be returning a single row. Luck doesn't have anything do with it. What matters is the specification. And apart from the SQL query, it's not clear what resultset you want returned.
I'm guessing (and this is just a guess) that you want to return multiple rows, one for each of the order size categories derived by the CASE expression.
Perhaps the resultset returned by a query something like this:
SELECT CASE
WHEN o.order_total_price IS NULL
THEN 'Unknown'
WHEN o.order_total_price <= 1000
THEN 'Not more than 1,000'
WHEN o.order_total_price <= 2000
THEN 'Between 1,001 and 2000'
WHEN o.order_total_price <= 3000
THEN 'Between 2001 and 3000'
WHEN o.order_total_price <= 4000
THEN 'Between 3001 and 4000'
WHEN o.order_total_price <= 5000
THEN 'Between 4001 and 5000'
ELSE 'Over 5000'
END AS order_value_category
, SUM(o.order_total_price) AS total_sales
, COUNT(*) AS count_sales
FROM orders o
WHERE o.order_time >= '2014-07-01'
AND o.order_time < '2014-07-01' + INTERVAL 1 MONTH
GROUP BY order_value_category
For performance, to enable MySQL to make use of a suitable index to satisfy the predicate on the order_time category, we'd need to reference the "bare" order_time column in a range scan predicate.
Note that the GROUP BY clause implies an ORDER BY clause; if you want the rows returned in order other than by the derived order_value_category column, you'd need to specify a suitable ORDER BY clause.

If you use GROUP BY, output rows are sorted according to the GROUP BY columns as if you had an ORDER BY for the same columns. To avoid the overhead of sorting that GROUP BY produces, add ORDER BY NULL:
SELECT a, COUNT(b) FROM test_table GROUP BY a ORDER BY NULL;
Relying on implicit GROUP BY sorting in MySQL 5.6 is deprecated. To achieve a specific sort order of grouped results, it is preferable to use an explicit ORDER BY clause. GROUP BY sorting is a MySQL extension that may change in a future release; for example, to make it possible for the optimizer to order groupings in whatever manner it deems most efficient and to avoid the sorting overhead.
http://academy.comingweek.com/sql-groupby-clause/

As Jim Garrison said,
GROUP BY 1
would be best.
As an alternative, below will also work if you are not comfortable with numbers in group by. Put your original query without GROUP BY as inner query. Use the count and group by in an outer query.
SELECT A.TOTAL_SALES, COUNT(1) FROM
(SELECT
CASE
WHEN sum(order_total_price) IS NULL
THEN 'Unknown'
WHEN sum(order_total_price) <= 1000
THEN 'Not more than 1,000'
WHEN sum(order_total_price) <= 2000
THEN 'Between 1,001 and 2000'
WHEN sum(order_total_price) <= 3000
THEN 'Between 2001 and 3000'
WHEN sum(order_total_price) <= 4000
THEN 'Between 3001 and 4000'
WHEN sum(order_total_price) <= 5000
THEN 'Between 4001 and 5000'
ELSE 'Over 5000'
END
AS total_sales
FROM orders
WHERE YEAR(order_time)=2014 and MONTH(order_time)=07
) AS A
GROUP BY A.total_sales

Related

SQL Combining Multiple SELECT Statements

I am trying to build an SQLite query that will collect statistics from a single table.
The table holds a log, of sorts, with several entries per day. I need to get a separate row for each day within the search parameters and then compile the totals of rows within those dates with certain boolean values.
Here is the query I have so far:
SELECT DATE(DateTime) AS SearchDate,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Total,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE NoMarket = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS NoMarkets,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE Complaint = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Complaints,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE Voicemail = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Voicemails
FROM CallRecords
WHERE DATE(DateTime) BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY SearchDate
And the output:
8/28/2017 175 27 11
8/29/2017 175 27 11
8/30/2017 175 27 11
8/31/2017 175 27 11
9/1/2017 175 27 11
As you can see, it is properly getting each individual date, but the totals for the columns is incorrect.
Obviously, I am missing something in my query, but I am not sure where. Is there a better way to perform this query?
EDIT: I have looked into several of the other questions with near-identical titles here, but I have not found anything similar to what I'm looking for. Most seem much more complicated than what I'm trying to accomplish.
It looks like you have a mess of columns in your CallRecords table with names like Complaint and Voicemail, each of which classifies a call.
It looks like those columns have the value 1 when relevant.
So this query should probably help you.
SELECT DATE(DateTime) AS SearchDate,
COUNT(*) AS Total,
SUM(NoMarket = 1) AS NoMarkets,
SUM(Complaint = 1) AS Complaints,
SUM(Voicemail = 1) AS Voicemails
FROM CallRecords
WHERE DateTime >= '2017-08-27'
AND DateTime < '2017-09-02' + INTERVAL 1 DAY
GROUP BY DATE(DateTime)
Why does this work? Because in MySQL a Boolean expression like Voicemail = 1 has the value 1 when it's true and 0 when it's false. You can sum those values up quite nicely.
Why is it faster than what you have? Because DATE(DateTime) BETWEEN this AND that can't exploit an index on DateTime.
Why is it correct for the end of your date range? Because DateTime < '2017-09-02' + INTERVAL 1 DAY pulls in all the records up until, but not including, midnight, on the day after your date range.
If you're using Sqlite, you need AND DateTime < date('2017-09-02', '+1 day'). The + INTERVAL 1 DAY stuff is slightly different there.
you can doing like this , although i wrote in SQL server
SELECT DATE(DateTime) AS SearchDate,
COUNT() AS TOTAL,
SUM(CASE WHEN NoMarket = 1 THEN 1 ELSE 0 END) AS NoMarkets,
SUM(CASE WHEN Complaint = 1 THEN 1 ELSE 0 END) AS Complaints,
SUM(CASE WHEN Voicemail = 1 THEN 1 ELSE 0 END) AS Voicemails
FROM CallRecords
WHERE DATE(DateTime) BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY SearchDate
SELECT DATE(DateTime) AS SearchDate, Total, NoMarkets, Complaints, Voicemails FROM
(SELECT COUNT() AS Total FROM CallRecords) CR
JOIN
(SELECT COUNT() AS NoMarkets FROM CallRecords WHERE NoMarket = 1) NM
ON CR.DateTime = NM.DateTime
JOIN
(SELECT COUNT() AS Complaints FROM CallRecords WHERE Complaint = 1) C
ON NM.DateTime = C.DateTime
JOIN
(SELECT COUNT() AS Voicemails FROM CallRecords WHERE Voicemail = 1) VM
ON C.DateTime = VM.DateTime
JOIN CallRecords CLR ON VM.DateTime=CLR.DateTime WHERE DATE(CLR.DateTime) >= '2017-08-27' AND DATE(CLR.DateTime) <= '2017-09-02'GROUP BY SearchDate;
This may Output correctly.

mysql USE CASE STATEMENT as variable

I have the following query with a quite large:
SELECT
DATE(added_on) 'Week Of',
COUNT(*) 'No. Updates',
(CASE WHEN COUNT(*) <= 500 THEN 6.75 WHEN COUNT(*) <= 750
THEN 6.30 WHEN COUNT(*) <= 1000 THEN 6.00 WHEN COUNT(*) <= 1250
THEN 5.50 ELSE 4.60 END
) Rate
Rate * COUNT(*) // HOW TO DO THIS??
FROM
Fox_title
GROUP BY
WEEK(added_on)
ORDER BY
added_on
How would I multiple the COUNT(*) * the Rate that I have from my CASE statement? Or do I have to write that CASE statement again?
Either repeat the case or use a subquery:
select t.*, t.Rate * `No. Updates`
from (SELECT DATE(min(added_on)) as `Week Of`, COUNT(*) as `No. Updates`,
(CASE WHEN COUNT(*) <= 500 THEN 6.75
WHEN COUNT(*) <= 750 THEN 6.30
WHEN COUNT(*) <= 1000 THEN 6.00
WHEN COUNT(*) <= 1250 THEN 5.50
ELSE 4.60
END) as Rate
FROM Fox_title
GROUP BY WEEK(added_on)
) t
ORDER BY `Week Of`;
I made a few other changes to your query. First, I changed the single quotes around the column aliases to back ticks. Single quotes should be used, in general, only for string constants. Back ticks are the MySQL method for enclosing identifiers.
I also changed date(addon) to date(min(addon)). This ensures that you will get the earliest date in the week. Otherwise, you get an arbitrary date.

Combining two count queries from same source

I have two queries that return a list of service items with a count. One is for the entire database, and the other is for a specified period. They work great individually, but I would like to optimize them into a single query.
The two queries are:
SELECT service_type, count(service_type) from qba_customers group by service_type order by count(service_type) desc
SELECT service_type, count(service_type) from qba_customers WHERE created_on BETWEEN '2013-01-01' AND '2013-06-30' group by service_type order by count(service_type) desc
I tried a few things unsuccessfully, below is what I thought would work initially:
SELECT service_type, COUNT(service_type) AS full_count, (count(service_type) WHERE created_on BETWEEN '2013-01-01' AND '2013-06-30') AS period_count FROM qba_customers GROUP BY service_type ORDER BY service_type DESC
Thanks in advance!
All aggregate functions, including COUNT, will only include a value if it's not NULL. To count just the rows that fall within a date range, use the CASE construct as the count argument. If the CASE returns a value, it's counted. If it doesn't return a value, it's not counted:
SELECT
service_type,
COUNT(service_type) AS GrandTotal,
COUNT(CASE WHEN created_on BETWEEN '2013-01-01' AND '2013-06-30' THEN 1 END) AS FirstHalf2013
FROM qba_customers
GROUP BY service_type
ORDER BY GrandTotal
For the FirstHalf2013 column, if created_on is within the target range the CASE returns a non-null value (I used 1 here, but it could be any non-null value). If created_on is not within the target range the CASE returns NULL which means the row is not counted.
Try it using the case statement:
SELECT service_type,
count(service_type) as full_count,
sum(case when created_on between '2013-01-01' and '2013-06-30' then 1 else 0 end) as period_count
FROM qba_customers
GROUP by service_type
ORDER by service_type desc

MySQL - Grouping with subquery

I am having trouble with a subquery and some grouping. The subquery is selecting from the whole table instead of just the individual groups...my code
SELECT SEC_TO_TIME(TIME_TO_SEC(call_start) - TIME_TO_SEC(call_start)%(30*60)) AS intervals,
COUNT(*) AS OFFERED,
SUM(agent_duration) AS AGENT_SUM,
SUM(TIME_TO_SEC(TIMEDIFF(dequeue_time, enqueue_time))) AS ANS_TIME_SUM,
COUNT(DISTINCT agent_username) AS UNIQUE_AGENTS,
(SELECT COUNT(*) FROM call_detail
WHERE TIME_TO_SEC(TIMEDIFF(dequeue_time, enqueue_time)) < 40) AS SLA,
SUM(queue_duration) AS TOTAL_QUEUE_TIME
FROM call_detail
WHERE DATE(call_start) = CURDATE()
GROUP BY intervals
My goal is to have that subquery just return the number of records where that TIMEDIFF result is less than 40 within that particular interval
Thanks.
I don't think you need a subquery for this. Just do conditional aggregation:
SELECT SEC_TO_TIME(TIME_TO_SEC(call_start) - TIME_TO_SEC(call_start)%(30*60)) AS intervals,
COUNT(*) AS OFFERED,
SUM(agent_duration) AS AGENT_SUM,
SUM(TIME_TO_SEC(TIMEDIFF(dequeue_time, enqueue_time))) AS ANS_TIME_SUM,
COUNT(DISTINCT agent_username) AS UNIQUE_AGENTS,
sum(case when TIME_TO_SEC(TIMEDIFF(dequeue_time, enqueue_time)) < 40 then 1 else 0 end) as SLA,
SUM(queue_duration) AS TOTAL_QUEUE_TIME
FROM call_detail
WHERE DATE(call_start) = CURDATE()
GROUP BY intervals;
You would use the subquery to get a total over all record, not the ones affected by the where clause or the group by.

SQL - Adding a count to select clause

I have this query to show a list of trending (most searched) names on my website:
SELECT name, COUNT(*) AS total_trends
FROM trending_names
WHERE dateTime BETWEEN '"&fromDate&"' AND '"&toDate&"' // -7 days to Now()
GROUP BY name
ORDER BY COUNT(*) DESC
LIMIT 10;
...and this is the kind of results I'm printing to screen:
(numbers represent quantity of searches made)
Angelina Jolie 31,293
Rihanna 26,722
Lindsay Lohan 18,351
Brad Pitt 11,901
I would now like to change the numbers to percentages; so I really need to be getting the total count of all trending names within the last 7 days, to calculate the correct percentage.
Is there a way I can add a total count to this query, without adding an additional query?
You can do in single query :
Try Below :
SELECT name, COUNT(*) AS total_trends,
sum(if(dateTime BETWEEN '"&fromDate&"' AND '"&toDate&"' ,1,0)) as total_last_7_days,
((sum(if(dateTime BETWEEN '"&fromDate&"' AND '"&toDate&"' ,1,0)) /COUNT(*) ) *100)
as percentage // if you want to get only percentage
FROM trending_names
GROUP BY name
ORDER BY COUNT(*) DESC
LIMIT 10;
You can use a subquery :
SELECT name, ((COUNT(*)*100)/(SELECT COUNT(*) FROM trending_names)) AS total_trends
FROM trending_names
WHERE dateTime BETWEEN '"&fromDate&"' AND '"&toDate&"' // -7 days to Now()
GROUP BY name
ORDER BY COUNT(*) DESC
LIMIT 10;
I know you aren't running SQL Server, but some readers might be interested to see this compact solution that's possible (SQL Server 2008 or later). I'm not sure many people know you can have a windowed aggregate that aggregates an aggregate.
select
name,
100.0*count(*)/sum(count(*)) over () as pct_trends
from trending_names
where dateTime between getdate()-7 and getdate()
group by name;
No, I don't think so. You do need to compute that total count on a separate (sub)select
SELECT name,(total_trends*100.0/sum_total_trends) pct_trends
FROM
(
SELECT name, COUNT(*) AS total_trends
FROM trending_names
WHERE dateTime BETWEEN '"&fromDate&"' AND '"&toDate&"' // -7 days to Now()
GROUP BY name
WITH ROLLUP
) A,
(
SELECT COUNT(*) AS sum_total_trends
FROM trending_names
WHERE dateTime BETWEEN '"&fromDate&"' AND '"&toDate&"' // -7 days to Now()
) B;