Select single column multiple times at differents point in time - mysql

I have a simple table with 4 columns - ID, Date, Category, Value.
I have 5 distinct categories that have certain values daily. I would like to select value column at different points in time and display result along with the appropriate category.
This is the code that I'm using:
select
Category,
case when date=DATE_SUB(CURDATE(),INTERVAL 1 DAY) then value else 0 end as Today,
case when date=DATE_SUB(CURDATE(),INTERVAL 1 MONTH) then value else 0 end as "Month Ago",
case when date=DATE_SUB(CURDATE(),INTERVAL 1 Year) then value else 0 end as "Year Ago"
from table
group by category
It's not working. I'm using mysql database but will run the query in SSRS through an ODBC connection.

The problem with your query is that, as written, the case statements need to be embedded in aggregation functions:
select Category,
avg(case when date=DATE_SUB(CURDATE(),INTERVAL 1 DAY) then value end) as Today,
avg(case when date=DATE_SUB(CURDATE(),INTERVAL 1 MONTH) then value end) as "Month Ago",
avg(case when date=DATE_SUB(CURDATE(),INTERVAL 1 Year) then value end) as "Year Ago"
from table
group by category
I chose "avg" since this seems reasonable if there are multiple values and the "value" column is numeric. You might prefer min() or max() to get other values.
Also, I removed the "else 0" clause, so you will see NULL rather than 0 when there is no value.

This type of query is best done with three separate queries:
SELECT 'Today' AS `When`, Category, value FROM `table`
WHERE date = DATE_SUB(CURDATE(),INTERVAL 1 DAY)
UNION ALL
SELECT 'Month Ago' AS `When`, Category, value FROM `table`
WHERE date = DATE_SUB(CURDATE(),INTERVAL 1 MONTH)
UNION ALL
SELECT 'Year Ago' AS `When`, Category, value FROM `table`
WHERE date = DATE_SUB(CURDATE(),INTERVAL 1 YEAR)

try something like this:
SELECT
t1.Category, t1.Value, t2.Value, t3.Value
FROM YourTable t1
LEFT OUTER JOIN YourTable t2 ON t1.Category=t2.Category
AND Date=DATE_SUB(CURDATE(),INTERVAL 1 Month)
LEFT OUTER JOIN YourTable t3 ON t1.Category=t3.Category
AND Date=DATE_SUB(CURDATE(),INTERVAL 1 Year)
WHERE Date=DATE_SUB(CURDATE(),INTERVAL 1 DAY)
this assumes that you have only one row per your interval. if you have multiple rows per interval, you need to decide which value you want to show for that interval (min, max, etc). you then need to aggergate your multiple rows. if this is the case the OP should provide some sample data and expected query output so testing is possible.

Related

Calculate average from data only stored in MySQL on change

We have a database where we only store data values and timestamp (as a row in MySQL) when the data value changes. There are therefore no fixed interval between the timestamps in the database. The table looks something like this:
MySQLTimestamp Data
2014-10-01 18:01 1
2014-10-03 16:13 2
2014-10-07 15:45 1
2014-10-09 10:08 3
THE PROBLEM: We want to calculate the average over time assuming that the data value continues to be i.e. 2 until the value changes on the next row in the database.
A simple AVG won't do the trick because it will only calculate the average between the number of rows. This would not take in to count that a value can continue to be the same value for a long period of time before the next row states a change in data value.
Would really appreciate your help!
Self join and calculate the duration of date or time as weight to Data.
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) as next_tm,
datediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
cross join
your_table t2
group by
t1.MySQLTimestamp,
t1.data
) as t
Use datediff to calculate days interval as weight. If you want hours or minutes as interval, you could use timediff and transfer the result to hours, minutes or seconds.
Here is the sql fiddle demo and the results:
AVG_OVER_DATE AVG_OVER_HOUR AVG_OVER_SEC
1.5 1.51887 1.5189
Another version by left join:
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(t2.MySQLTimestamp) as next_tm,
datediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
left join
your_table t2
on
t1.MySQLTimestamp<t2.MySQLTimestamp
group by
t1.MySQLTimestamp,
t1.data
) as t

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

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);

Return a zero for a day with no results

I have a query which returns the total of users who registered for each day. Problem is if a day had no one register it doesn't return any value, it just skips it. I would rather it returned zero
this is my query so far
SELECT count(*) total FROM users WHERE created_at < NOW() AND created_at >
DATE_SUB(NOW(), INTERVAL 7 DAY) AND owner_id = ? GROUP BY DAY(created_at)
ORDER BY created_at DESC
Edit
i grouped the data so i would get a count for each day- As for the date range, i wanted the total users registered for the previous seven days
A variation on the theme "build your on 7 day calendar inline":
SELECT D, count(created_at) AS total FROM
(SELECT DATE_SUB(NOW(), INTERVAL D DAY) AS D
FROM
(SELECT 0 as D
UNION SELECT 1
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
) AS D
) AS D
LEFT JOIN users ON date(created_at) = date(D)
WHERE owner_id = ? or owner_id is null
GROUP BY D
ORDER BY D DESC
I don't have your table structure at hand, so that would need adjustment probably. In the same order of idea, you will see I use NOW() as a reference date. But that's easily adjustable. Anyway that's the spirit...
See for a live demo http://sqlfiddle.com/#!2/ab5cf/11
If you had a table that held all of your days you could do a left join from there to your users table.
SELECT SUM(CASE WHEN U.Id IS NOT NULL THEN 1 ELSE 0 END)
FROM DimDate D
LEFT JOIN Users U ON CONVERT(DATE,U.Created_at) = D.DateValue
WHERE YourCriteria
GROUP BY YourGroupBy
The tricky bit is that you group by the date field in your data, which might have 'holes' in it, and thus miss records for that date.
A way to solve it is by filling a table with all dates for the past 10 and next 100 years or so, and to (outer)join that to your data. Then you will have one record for each day (or week or whatever) for sure.
I had to do this only for MS SqlServer, so how to fill a date table (or perhaps you can do it dynamically) is for someone else to answer.
A bit long winded, but I think this will work...
SELECT count(users.created_at) total FROM
(SELECT DATE_SUB(CURDATE(),INTERVAL 6 DAY) as cdate UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 5 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 4 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 3 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 2 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 1 DAY) UNION ALL
SELECT CURDATE()) t1 left join users
ON date(created_at)=t1.cdate
WHERE owner_id = ? or owner_id is null
GROUP BY t1.cdate
ORDER BY t1.cdate DESC
It differs from your query slightly in that it works on dates rather than date times which your query is doing. From your description I have assumed you mean to use whole days and therefore have used dates.

Different conditions in one select statement

How can you Select two columns and have each column test for it's own condition and not the other's ?
Let's say I have a select that Count every records in a table. In one column I want every records from this week, and in the second one I want all record since the beginning of the year.
I have two conditions but they each apply to a specific column :
WHERE date BETWEEN #Monday AND #SUNDAY /* Weekly */
WHERE date >= #JanuaryFirst /* Annual */
But can't just put it like this because I will only get this week's record in both columns. I thought I could use an IFcondition but I don't think I can simply say "If you are column A test for this, if not test for the second one".
Here is a version that doesn't yield multiple scans:
select vehicule,
weekly = SUM(CASE WHEN date BETWEEN #Monday AND #SUNDAY THEN 1 ELSE 0 END),
annual = SUM(CASE WHEN date >= #JanuaryFirst THEN 1 ELSE 0 END)
from dbo.tablename AS t
GROUP BY vehicule;
Or you could also try the slightly less verbose:
select vehicule,
weekly = COUNT(CASE WHEN date BETWEEN #Monday AND #SUNDAY THEN 1 END),
annual = COUNT(CASE WHEN date >= #JanuaryFirst THEN 1 END)
from dbo.tablename AS t
GROUP BY vehicule;
Use INNER SELECTS, like this:
select vehicule,
(select count(*) from tablename t1 where t1.vehicule = t.vehicule and date BETWEEN #Monday AND #SUNDAY) as 'Weekly',
(select count(*) from tablename t1 where t1.vehicule = t.vehicule and date >= #JanuaryFirst) as 'Annual'
from tablename t
If you want to avoid subqueries you can use:
select vehicule,
sum(case when date BETWEEN #Monday AND #SUNDAY then 1 else 0 end) as 'Weekly',
sum(case when date >= #JanuaryFirst then 1 else 0 end) as 'Annual'
group by vehicule