How to query rows by month and group by column using mysql? - mysql

I have a database that has two columns - result and time
I'm trying to get a count of how many rows exist of each result in a particular month. There are only two options for result success and failure
I've managed to get a count of how many rows there are in each month, but I can't get the individual count of how many success and how many failure there were in each month.
Here is what I have:
SELECT result, MONTH(time) MONTH, COUNT(*) COUNT
FROM mytable
WHERE YEAR(time)=2017
GROUP BY MONTH(time);
I'm looking for a result that provides me with something like there were 12 successes and 8 failures in a particular month.
Any help would be appreciated.

Use conditional aggregation
SELECT result, MONTH(time) MONTH,
sum(result = 'success') as success_count,
sum(result = 'failure') as failure_count
FROM mytable
WHERE YEAR(time) = 2017
GROUP BY result, MONTH(time);

I would use the following query:
SELECT,
DATE_FORMAT(time, '%m %Y') AS month_year,
SUM(CASE WHEN result = 'success' THEN 1 ELSE 0 END) AS success_count,
SUM(CASE WHEN result = 'failure' THEN 1 ELSE 0 END) AS failure_count
FROM mytable
WHERE YEAR(time) = 2017
GROUP BY
DATE_FORMAT(time, '%m %Y')
Note that you should be aggregating by time period alone, and not by the result, which instead is part of the sum in the CASE expression.

Related

Mysql Frequency within date range

I have a MySQL DB as such:
Date Customer_ID
How can I turn it into:
Customer_ID | Count_Visits_Past_Week | Count_Visits_Past_Month | Count_Visits_Past_90days | Count_Total
Note : Count_Total =sum of the other three counts
Thanks
The first step is to determine the demarcation points for the specified date ranges.
There's several questions to answer here: did you want to compare just the DATE ('yyyy-mm-dd') and disregard any time component?
By "past week", does that mean within the last seven days, or does it mean so far since the previous Sunday, or does it mean the last last full week, from Sunday through Saturday.
For "past month", does that mean the previous whole month, from the first through the end of the month? Or does it mean that if the query is run on the 20th of the month, we want dates since the 20th of the previous month up until today? Or yesterday?
Once we know the points in time that begin and end each specified period, relative to today's date, we can build expressions that evaluate to those dates.
For example, "past week" could be represented as the most recent seven day period:
DATE(NOW())-INTERVAL 1 WEEK -thru- DATE(NOW())
And "past month" can be represented as the same "day of month" (e.g. 17th) of the immediately preceding month up until today:
DATE(NOW())-INTERVAL 1 MONTH -thru- DATE(NOW())
That's really the first step, to determine the begin and end dates of each specified period.
Once we have that, we can move on to building a query that gets a "count" of rows with a date column that falls within each period.
The "trick" is to use conditional tests in expressions in the SELECT list of the query. We'll use those conditional tests to return a 1 if the row is to be included in the "count", and return 0 or NULL if the row should be excluded.
I prefer to use the SUM() aggregate function to get the "count". But it's also possible to use COUNT() aggregate. (If we use COUNT(), we need to use an expression that returns NULL when the row is to be excluded. I prefer to return a 1 or 0; I think it makes debugging easier.
Here's an outline of what a "count" query would look like.
SELECT t.Customer_Id
, SUM(IF( <some_condition> ,1,0) AS Count_something
, SUM(IF( <other_condition> ,1,0) AS Count_something_else
FROM mytable t
GROUP BY t.Customer_Id
When <some_condition> is true, we return a 1, otherwise we return 0.
To test the conditional expressions, it's often easiest to avoid doing the aggregation, and just return the individual rows:
That way, we can see which individual rows are going to be included in each "count".
For example:
SELECT t.Customer_ID
, t.date
, IF(t.date BETWEEN DATE(NOW())-INTERVAL 1 WEEK AND DATE(NOW()),1,0)
AS visit_past_week
, IF(t.date BETWEEN DATE(NOW())-INTERVAL 1 MONTH AND DATE(NOW()),1,0)
AS visit_past_month
FROM mytable t
ORDER BY t.date, t.Customer_Id
That query doesn't return the "count", it just returns the results of the expressions, which can be useful in testing. And of course we want to test the expressions that return the beginning and ending date of each period:
SELECT DATE(NOW()) - INTERVAL 1 WEEK AS past_week_begin
, DATE(NOW()) AS past_week_end
With this approach, the same row can be included in multiple "counts" with one query and one pass through the table.
Note that the expressions inside the SUM() aggregate in the query below are taking advantage of a convenient shorthand, an expression evaluated as a boolean will return 1 if TRUE, 0 if false, or a NULL.
To use the COUNT aggregate, we need to insure that the expression being aggregated returns a non-NULL when the row is to be "counted", and a NULL when the row is to be excluded from the count. So we use the convenient NULLIF function to return NULL if the value returned by the expression is a zero.
SELECT t.Customer_ID
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 WEEK AND DATE(NOW()),0))
AS Count_Visits_Past_Week
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 MONTH AND DATE(NOW()),0))
AS Count_Visits_Past_Month
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 90 DAY AND DATE(NOW()),0))
AS Count_Visits_Past_90days
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 WEEK AND DATE(NOW()),0))
+ COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 MONTH AND DATE(NOW()),0))
+ COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 90 DAY AND DATE(NOW()),0))
AS Count_Total
FROM mytable t
GROUP BY t.Customer_Id
NOTE: NULLIF(a,b) is a convenient shorthand for IF a IS NULL THEN return b ELSE return a
Returning the Count_Total is a bit odd, since it's got the potential to "count" the same row multiple times... but the value it returns should match the total of the individual counts.
I think this will give you what you want.
select customer_id,
sum(case when splitter = 'week' then num_visits else 0 end) as visits_this_week,
sum(case when splitter = 'month' then num_visits else 0 end) as visits_this_month,
sum(case when splitter = '90days' then num_visits else 0 end) as visits_last_90days,
sum(num_visits) as total
from (select customer_id, 'week' as splitter, count(*) as num_visits
from tbl
where extract(week from date) = extract(week from sysdate())
and extract(year from date) = extract(year from sysdate())
group by customer_id
union all
select customer_id, 'month' as splitter, count(*) as num_visits
from tbl
where extract(month from date) = extract(month from sysdate())
and extract(year from date) = extract(year from sysdate())
group by customer_id
union all
select customer_id, '90days' as splitter, count(*) as num_visits
from tbl
where date between date_sub(sysdate(), interval 90 day) and
sysdate()) x
group by customer_id
sql fiddle example: http://sqlfiddle.com/#!2/a762c/12/0

Getting total using Union All

So here is my situation. I have 1 query that gets results grouped by date then I union all a second query that gets the totals (no grouping by date). My issue is I am calculating the average of fields and when I want to total up the average my numbers don't add up.
Here is my SQLFiddle
Here is my query:
SELECT
t.end,
SUM(CASE WHEN (t.start != t.end) THEN TIMESTAMPDIFF(DAY, t.start, t.end) ELSE 1 END) / COUNT(t.id) as averageTime
FROM store t
GROUP BY t.end
UNION ALL
SELECT
'Total',
SUM(CASE WHEN (t.start != t.end) THEN TIMESTAMPDIFF(DAY, t.start, t.end) ELSE 1 END) as averageTime
FROM store t
Right now the second query just gives the total not the total of the average. Any help is appreciated, thank you.
To clarify since there is some confusion...
I need to get the average per grouped date by how many items were in that group
timeDiff / count(t.id)
Since I am not grouping in the union all query it is doing it as a whole then dividing. I hope that makes more sense.
The first query is correct the data output is as follows:
1, 1.6667, 3 (Those are the averageTime values from the first query)
5.6667 (Should be the total row) Right now I have it out putting 10 that is the total before the first rows were averaged out.
THe first part of the query can probably be written as:
SELECT t.end,
AVG(CASE WHEN t.start != t.end THEN TIMESTAMPDIFF(DAY, t.start, t.end) ELSE 1
END) as averageTime
FROM store t
GROUP BY t.end;
Presumably, you want the averages of the averages -- rather than the overall average. I assume this because your query is calculating the overall average.
One way is the brute force way:
SELECT t.end,
AVG(CASE WHEN t.start != t.end THEN TIMESTAMPDIFF(DAY, t.start, t.end) ELSE 1
END) as averageTime
FROM store t
GROUP BY t.end
UNION ALL
SELECT 'Total', avg(AverageTime)
FROM (SELECT t.end,
SUM(CASE WHEN t.start != t.end THEN TIMESTAMPDIFF(DAY, t.start, t.end) ELSE 1
END) as averageTime
FROM store t
GROUP BY t.end
) t;
Your question is not clear. But, I suppose you need Average. As #samD mentioned,
the second part of you query can be written as
SELECT
'Total',
AVG(CASE WHEN (t.start != t.end) THEN TIMESTAMPDIFF(DAY, t.start, t.end) ELSE 1 END) as averageTime
FROM store t
You're mistakenly assuming that the COUNT(t.id) in the first query represents the total number of rows, whilst it is actually also affected by the GROUP BY clause. If you multiply the separate rows by their number of days the totals also add up. Remove the division by COUNT(t.id) and the totals will add up, and you will correctly have the grouped SUMs as expected.
I'm just not sure how that solves your problem since you're not really clear on that: you simply cannot add all averages together and expect them to add up to a global average, that's not how math works: the sum of averages of arbitrary sized subgroups is rarely equal to the straight unweighted average of all groups together.

mysql how to combine two select statement

I have a database with records of date-time and a measurement value.
I've been writing two separate queries, one to return the total count of all daily records between certain times of day for the previous month, and the same query but a count of only when the measurement value is below threshold. I then manually divide the theshold count by total count for each day, and I am able to get a % uptime or SLA.
So I have two questions:
1) Can I combine these two queries into one query. I found the Answer to #1, see below
2) Can I go ahead and do the math in the queries, so what I get returned is just a listing of each day, the count above, the count below, and the % above or below threshold...
Sample data and query are listed below.
TableA
hostname, date_time, value
Sample Query to return days from previous month, excluding weekend days.
SELECT
count(*),
DATE(date_time),
SUM(
CASE WHEN rssi_val < 100
THEN 1
ELSE 0
END
)
FROM TableA
WHERE hostname = 'hostA'
AND DATE(date_time) BETWEEN '2013-07-01' AND '2013-07-31'
AND TIME(date_time) BETWEEN '06:00:00' AND '18:00:00'
AND DAYOFWEEK(date_time) NOT IN (1, 7)
GROUP BY DATE(date_time);
So now I just want to know how to add a 4th column that gives the percent uptime/downtime.
Have you tried this ?
select count(*),
DATE(date_time),
SUM(CASE WHEN rssi_val<100 THEN 1 ELSE 0 END),
SUM(CASE WHEN rssi_val<100 THEN 1 ELSE 0 END)/count(*) as percentage
from TableA
where hostname='hostA'
and DATE(date_time) between '2013-07-01' and '2013-07-31'
and TIME(date_time) between '06:00:00' and '18:00:00'
and DAYOFWEEK(date_time) NOT IN (1,7)
group by DATE(date_time);

MySQL query group by with multiple sum values

If you review this SQL Fiddle, you will see that the resulting data is grouped by Year and Type which means that there are 2 rows for each year. I would like to only group on the year, and display the Sum of the A-type and the B-type on the same row.
Can someone please tell me how I would modify the sum statements in this query so that the first sum is only for the A-type, and the second sum is for the B-type? Thanks.
If you just want the two columns, it's fairly easy to do;
SELECT Year,
SUM(CASE WHEN Type='A' THEN Amt ELSE 0 END) TypeA,
SUM(CASE WHEN Type='B' THEN Amt ELSE 0 END) TypeB
FROM YourTable
GROUP BY Year
An SQLfiddle to test with.
...or a bit more verbose standard SQL;
How about this: http://sqlfiddle.com/#!2/85415/11
SELECT
SUM(IF(TYPE = 'A', Amt, 0)) AS A_amt,
SUM(IF(TYPE = 'B', Amt, 0)) AS B_amt,
YEAR,
TYPE
FROM YourTable
GROUP BY YEAR
Alternatively you could use a CASE statement which is standard SQL, but slightly longer so I opted for this version.

MySQL SUM IF field b = field a

SELECT
incMonth as Month,
SUM( IF(item_type IN('typ1', 'typ2') AND incMonth = Month, 1, 0 ) )AS 'Total Sales'
FROM tester
I just need the sum for the current month its looping through.
If I'm reading that right, you'd need to use a group-by or at least a where clause to restrict things to just the time range you're wanting:
SELECT incMonth AS Month, SUM(IF(item_type IN('typ1', 'typ2'), 1, 0)) AS 'Total Sales'
FROM tester
GROUP BY incMonth