Ok. I'm about to give up on this one.
When I run the following query, I get "Error Code: 1305. FUNCTION statpurchase.playerId does not exist". I don't get a line number, but I strongly suspect the COUNT(DISTINCT(WHEN clauses.
The query is attempting to compute the percent of unique playerIds who make a purchase in a given day for a range of time. statpurchase.playerId is a valid column name.
It's a poor man who blames his tools, but I suspect its possibly a parser error similar to this one.
delimiter $$
CREATE PROCEDURE `percentUniquesPurchasing`(in startTime datetime, in endTime datetime, in placeId int)
BEGIN
declare total_uniques int;
declare iOS_uniques int;
declare desktop_uniques int;
declare i datetime;
set i = startTime;
CREATE TEMPORARY TABLE results (
theday datetime,
total float,
iOS float,
desktop float
);
while(i < endTime + INTERVAL 1 DAY) do
select count(distinct(statplaysession.playerId)),
count(distinct(case when touchInterface = 1 then statplaysession.playerId else null end)),
count(distinct(case when touchInterface = 0 then statplaysession.playerId else null end))
into
total_uniques, iOS_uniques, desktop_uniques
from rbxstats.statplaysession
where
statplaysession.start > i and
statplaysession.start < i + INTERVAL 1 DAY and
statplaysession.placeId = placeId;
insert into results (theday, total, iOS, desktop)
select i,
if(total_uniques > 0, count(distinct(statpurchase.playerId)) / total_uniques, 0),
if(iOS_uniques > 0, count(distinct(statpurchase.playerId(case when touchInterface = 1 then statpurchase.playerId end))) / iOS_uniques, 0),
if(desktop_uniques > 0, count(distinct(statpurchase.playerId(case when touchInterface = 0 then statpurchase.playerId end))) / desktop_uniques,0)
from rbxstats.statpurchase where
statpurchase.timestamp > i and
statpurchase.timestamp < i + INTERVAL 1 DAY and
statpurchase.placeId = placeId;
set i = i + INTERVAL 1 DAY;
end while;
select * from results;
drop temporary table results;
END$$
on these 2 lines you try to use statpurchase.playerId as a function, which it doesnt seem to be, its a column in your table
if(iOS_uniques > 0, count(distinct(statpurchase.playerId(case when
if(desktop_uniques > 0, count(distinct(statpurchase.playerId(case when
This is what I would like to be able to do:
SET #interval_type := MONTH;
SELECT '2012-01-01' + INTERVAL 6 #interval_type;
+------------+
|'2012-06-01'|
+------------+
And of course that doesn't work and there is no "interval" data type in MySQL.
I want to be able to store an interval value and an interval type in a table so that i can have the database quickly do the math naturally without having to write a big switch statement, ala
... ELSE IF (type = 'MONTH') { SELECT #date + INTERVAL #value MONTH; } ...
Is this supported in any way in MySQL or do you have a clever hack for this?
Thanks; you rock.
This solution may come handy to somebody implementing the job queue for cron or something similar.
Let us suppose we have a reference date (DATETIME) and interval of repetition. We would like to store both values in database and get the quick comparison whether it's already time to execute and include job into execution queue or not.
The interval could be non trivial e.g. (1 YEAR 12 DAYS 12 HOUR) and is controlled by wise user (admin) so that user is not going to use values exceeding the range of regular DATETIME data type or otherwise the conversion must be implemented first. (18 MONTH -> 1 YEAR 6 MONTH).
We can use then DATETIME data type for storing both values reference date and interval. We can define stored function using:
DELIMITER $$
CREATE DEFINER=`my_db`#`%` FUNCTION `add_interval`(`source` DATETIME, `interval` DATETIME) RETURNS datetime
BEGIN
DECLARE result DATETIME;
SET result = `source`;
SET result=DATE_ADD(result, INTERVAL EXTRACT(YEAR FROM `interval`) YEAR);
SET result=DATE_ADD(result, INTERVAL EXTRACT(MONTH FROM `interval`) MONTH);
SET result=DATE_ADD(result, INTERVAL EXTRACT(DAY FROM `interval`) DAY);
SET result=DATE_ADD(result, INTERVAL EXTRACT(HOUR FROM `interval`) HOUR);
SET result=DATE_ADD(result, INTERVAL EXTRACT(MINUTE FROM `interval`) MINUTE);
SET result=DATE_ADD(result, INTERVAL EXTRACT(SECOND FROM `interval`) SECOND);
RETURN result;
END
We can then make DATETIME arithmetic using this function e.g.
// test solution
SELECT add_interval('2014-07-24 15:58:00','0001-06-00 00:00:00');
// get job from schedule table
SELECT job FROM schedule WHERE add_interval(last_execution,repetition)<NOW();
// update date of executed job
UPDATE schedule SET last_execution=add_interval(last_execution,repetition);
You can solve this problem using prepared statements, considering there is no language construct available for use. The benefit here being you get the performance and flexibility that you want; this could easily be placed in a stored procedure or function for added value:
SET #date = '2012-01-01';
SET #value = 6;
SET #type = 'MONTH';
SET #q = 'SELECT ? + INTERVAL ? ';
SET #q = CONCAT(#s, #type);
PREPARE st FROM #q;
EXECUTE st USING #date, #value;
Alternatively, depending on your database / software architecture and the type of date/time intervals you are thinking of, you could simply this problem by using a time-scale interval:
SELECT #date + INTERVAL #value SECOND
1 second - 1
1 minute - 60
1 hour - 3600
1 day - 86400 (24 hours)
1 week - 604800 (7 days)
1 month - 2419200 (4 weeks)
Here's the simplistic approach. It works reasonably fast. You can change the order of the switch statements to optimize for speed if you feel that you will be hitting some more often then others. I have not benched this against Chris Hutchinson's solution. I ran into problems trying to wrap it into a nice function because of the dynamic SQL. Anyway, for posterity, this is guaranteed to work:
CREATE FUNCTION AddInterval( date DATETIME, interval_value INT, interval_type TEXT )
RETURNS DATETIME
DETERMINISTIC
BEGIN
DECLARE newdate DATETIME;
SET newdate = date;
IF interval_type = 'YEAR' THEN
SET newdate = date + INTERVAL interval_value YEAR;
ELSEIF interval_type = 'QUARTER' THEN
SET newdate = date + INTERVAL interval_value QUARTER;
ELSEIF interval_type = 'MONTH' THEN
SET newdate = date + INTERVAL interval_value MONTH;
ELSEIF interval_type = 'WEEK' THEN
SET newdate = date + INTERVAL interval_value WEEK;
ELSEIF interval_type = 'DAY' THEN
SET newdate = date + INTERVAL interval_value DAY;
ELSEIF interval_type = 'MINUTE' THEN
SET newdate = date + INTERVAL interval_value MINUTE;
ELSEIF interval_type = 'SECOND' THEN
SET newdate = date + INTERVAL interval_value SECOND;
END IF;
RETURN newdate;
END //
It comes with this equally simplistic benchmark test:
CREATE FUNCTION `TestInterval`( numloops INT )
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE date DATETIME;
DECLARE newdate DATETIME;
DECLARE i INT;
SET i = 0;
label1: LOOP
SET date = FROM_UNIXTIME(RAND() * 2147483647);
SET newdate = AddInterval(date,1,'YEAR');
SET i = i+1;
IF i < numloops THEN
ITERATE label1;
ELSE
LEAVE label1;
END IF;
END LOOP label1;
return i;
END //
I have a field of time Timestamp in my MySQL database which is mapped to a DATE datatype in my bean. Now I want a query by which I can fetch all records in the database for which the difference between the current timestamp and the one stored in the database is > 20 minutes.
How can I do it?
What i want is:
SELECT * FROM MyTab T WHERE T.runTime - now > 20 minutes
Are there any MySQL functions for this, or any way to do this in SQL?
If you have MySql version above 5.6 you could use TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2) something like
select * from MyTab T where
TIMESTAMPDIFF(MINUTE,T.runTime,NOW()) > 20
MySql version >=5.6
I am using below code for today and database date.
TIMESTAMPDIFF(MINUTE,T.runTime,NOW()) > 20
According to the documentation, the first argument can be any of the following:
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
ROUND(time_to_sec((TIMEDIFF(NOW(), "2015-06-10 20:15:00"))) / 60);
Try this one:
select * from MyTab T where date_add(T.runTime, INTERVAL 20 MINUTE) < NOW()
NOTE: this should work if you're using MySQL DateTime format. If you're using Unix Timestamp (integer), then it would be even easier:
select * from MyTab T where UNIX_TIMESTAMP() - T.runTime > 20*60
UNIX_TIMESTAMP() function returns you current unix timestamp.
You can try this:
SELECT * FROM MyTab T WHERE CURRENT_TIMESTAMP() > T.runTime + INTERVAL 20 MINUTE;
The CURRENT_TIMESTAMP() is a function and returns the current date and time. This function works From MySQL 4.0
If you have MySql version prior than 5.6 you don't have TIMESTAMPDIFF. So,I wrote my own MySql function to do this. Accets %i or %m for minutes and %h for hours. You can extend it.
Example of usage:
SELECT MYTSDIFF('2001-01-01 10:44:32', '2001-01-01 09:50:00', '%h')
Here goes the function. Enjoy:
DROP FUNCTION IF EXISTS MYTSDIFF;
DELIMITER $$
CREATE FUNCTION `MYTSDIFF`( date1 timestamp, date2 timestamp, fmt varchar(20))
returns varchar(20) DETERMINISTIC
BEGIN
declare secs smallint(2);
declare mins smallint(2);
declare hours int;
declare total real default 0;
declare str_total varchar(20);
if date1 > DATE_ADD( date2, interval 30 day) then
return '999999.999'; /* OUT OF RANGE TIMEDIFF */
end if;
select cast( time_format( timediff(date1, date2), '%s') as signed) into secs;
select cast( time_format( timediff(date1, date2), '%i') as signed) into mins;
select cast( time_format( timediff(date1, date2), '%H') as signed) into hours;
set total = hours * 3600 + mins * 60 + secs;
set fmt = LOWER( fmt);
if fmt = '%m' or fmt = '%i' then
set total = total / 60;
elseif fmt = '%h' then
set total = total / 3600;
else
/* Do nothing, %s is the default: */
set total = total + 0;
end if;
select cast( total as char(20)) into str_total;
return str_total;
END$$
DELIMITER ;
Best way to create on the fly, date ranges, for use with report.
So I can avoid empty rows on my report if there's no activity for a given day.
Mostly to avoid this issue: What is the most straightforward way to pad empty dates in sql results (on either mysql or perl end)?
My advice is: don't make your life harder, make it easier. Just create a table with one row for each calendar day, having as many rows as you think you reasonably need to last. In datawarehousing, this is the common solution, and it is so widely implemented this way that a dwh that doesn't have it, has a code smell.
Many people used to dealing with more traditional oltp/data entry apps feel a natural revulsion against this idea, because the feel the can generate the data anyway, and therefore it shouldn't be stored. But if you do create a table like that, you can adorn it with many useful attributes, such as whether it's a holdiday or a weekend, and you can store many common date representations (iso, european, us format etc) inside it, which can save you a ton of time when creating reports (since you don't have to bother figuring out how the date formatting works in each reporting tool you come by. Or you can go a step further and update your date table everyday to mark flags for the current day, current week, current month, current year, etc - all kinds of useful tools that make it much, much easier to build reports that need to work against some date range.
MySQL sample code as per request in comment:
delimiter //
DROP PROCEDURE IF EXISTS p_load_dim_date
//
CREATE PROCEDURE p_load_dim_date (
p_from_date DATE
, p_to_date DATE
)
BEGIN
DECLARE v_date DATE DEFAULT p_from_date;
DECLARE v_month tinyint;
CREATE TABLE IF NOT EXISTS dim_date (
date_key int primary key
, date_value date
, date_iso char(10)
, year smallint
, quarter tinyint
, quarter_name char(2)
, month tinyint
, month_name varchar(10)
, month_abbreviation varchar(10)
, week char(2)
, day_of_month tinyint
, day_of_year smallint
, day_of_week smallint
, day_name varchar(10)
, day_abbreviation varchar(10)
, is_weekend tinyint
, is_weekday tinyint
, is_today tinyint
, is_yesterday tinyint
, is_this_week tinyint
, is_last_week tinyint
, is_this_month tinyint
, is_last_month tinyint
, is_this_year tinyint
, is_last_year tinyint
);
WHILE v_date < p_to_date DO
SET v_month := month(v_date);
INSERT INTO dim_date(
date_key
, date_value
, date_iso
, year
, quarter
, quarter_name
, month
, month_name
, month_abbreviation
, week
, day_of_month
, day_of_year
, day_of_week
, day_name
, day_abbreviation
, is_weekend
, is_weekday
) VALUES (
v_date + 0
, v_date
, DATE_FORMAT(v_date, '%y-%c-%d')
, year(v_date)
, ((v_month - 1) DIV 3) + 1
, CONCAT('Q', ((v_month - 1) DIV 3) + 1)
, v_month
, DATE_FORMAT(v_date, '%M')
, DATE_FORMAT(v_date, '%b')
, DATE_FORMAT(v_date, '%u')
, DATE_FORMAT(v_date, '%d')
, DATE_FORMAT(v_date, '%j')
, DATE_FORMAT(v_date, '%w') + 1
, DATE_FORMAT(v_date, '%W')
, DATE_FORMAT(v_date, '%a')
, IF(DATE_FORMAT(v_date, '%w') IN (0,6), 1, 0)
, IF(DATE_FORMAT(v_date, '%w') IN (0,6), 0, 1)
);
SET v_date := v_date + INTERVAL 1 DAY;
END WHILE;
CALL p_update_dim_date();
END;
//
DROP PROCEDURE IF EXISTS p_update_dim_date;
//
CREATE PROCEDURE p_update_dim_date()
UPDATE dim_date
SET is_today = IF(date_value = current_date, 1, 0)
, is_yesterday = IF(date_value = current_date - INTERVAL 1 DAY, 1, 0)
, is_this_week = IF(year = year(current_date) AND week = DATE_FORMAT(current_date, '%u'), 1, 0)
, is_last_week = IF(year = year(current_date - INTERVAL 7 DAY) AND week = DATE_FORMAT(current_date - INTERVAL 7 DAY, '%u'), 1, 0)
, is_this_month = IF(year = year(current_date) AND month = month(current_date), 1, 0)
, is_last_month = IF(year = year(current_date - INTERVAL 1 MONTH) AND month = month(current_date - INTERVAL 1 MONTH), 1, 0)
, is_this_year = IF(year = year(current_date), 1, 0)
, is_last_year = IF(year = year(current_date - INTERVAL 1 YEAR), 1, 0)
WHERE is_today
OR is_yesterday
OR is_this_week
OR is_last_week
OR is_this_month
OR is_last_month
OR is_this_year
OR is_last_year
OR IF(date_value = current_date, 1, 0)
OR IF(date_value = current_date - INTERVAL 1 DAY, 1, 0)
OR IF(year = year(current_date) AND week = DATE_FORMAT(current_date, '%u'), 1, 0)
OR IF(year = year(current_date - INTERVAL 7 DAY) AND week = DATE_FORMAT(current_date - INTERVAL 7 DAY, '%u'), 1, 0)
OR IF(year = year(current_date) AND month = month(current_date), 1, 0)
OR IF(year = year(current_date - INTERVAL 1 MONTH) AND month = month(current_date - INTERVAL 1 MONTH), 1, 0)
OR IF(year = year(current_date), 1, 0)
OR IF(year = year(current_date - INTERVAL 1 YEAR), 1, 0)
;
//
delimiter ;
Using p_load_dim_date you uinitially load the dim_date table with say 25 years of data. And daily, prefereabluy round midnight, you run p_update_dim_date. Then you can use the flag fields is_today, is_yesterday, is_this_week, is_last_week and so on to select common ranges. Of course, you should amend this code to suit your particular needs but this is the idea. So no generaging ranges on the fly, you just preload for a long enough period of time ahead. For the time of day, a similar design can be set up - you should be able to manage that yourself going by this code.
For even fancier date dimensions that take care of holidays, and localized names for month and days, you can take a look at:
http://rpbouman.blogspot.com/2007/04/kettle-tip-using-java-locales-for-date.html
and
http://rpbouman.blogspot.com/2010/01/easter-eggs-for-mysql-and-kettle.html
I've recently done some research to find and evaluate possible options. http://www.freeportmetrics.com/devblog/2012/11/02/how-to-quickly-add-date-dimension-to-pentaho-mondrian-olap-cube/.
You can use:
kettle
degenerated dimensions
lucidb build-in function
up-coming Mondrian built-in function
your own custom script to generate SQL
mysql script mentioned earlier
Please check the blog post for more details. It also contains improved version of Roland's sql script that will automatically calculate date range for given column and join it with date dimension.
There is no straightforward way to do that in MySQL. Your best bet is to generate a daterange array in your server-side language of choice, and then pull data from the database and merge the resulting array with your daterange array using the date as a key.
Which server side language are you using?
Edit:
Basically what you would do is (pseudocode):
// Create an array with all dates for a given range
dates = makeRange(startDate, endDate);
getData = mysqlQuery('SELECT date, x, y, z FROM a WHERE a AND b AND c');
while (r = fetchRowArray(getData)) {
dates[ date(r['date']) ] = Array ( x, y, z);
}
You end up with an array of dates you can loop through, with the dates that have or don't have activity data associated to them.
Can easily be modified to group / filter data by hours.
Try using a loop in a MySQL stored routine to create date ranges:
declare iterDate date;
set iterDate = startDate;
DROP TABLE IF EXISTS MyDates;
create temporary table MyDates (
theDate date
);
label1: LOOP
insert into MyDates(theDate) values (iterDate);
SET iterDate = DATE_ADD(iterDate, INTERVAL 1 DAY);
IF iterDate <= endDate THEN
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
select * from MyDates;
DROP TABLE IF EXISTS MyDates;
startDate and endDate constitute the endpoints of the range and are supplied as parameters to the routine.
I realise this is an old post but, to keep Stack Overflow a bit up-to-date, I feel the urge to respond.
With the new SEQUENCE engine in MariaDB, this is possible within a SELECT statement without any stored routine or temporary table:
SELECT
DATE_ADD(
CAST('2022-06-01' AS DATE),
INTERVAL `s1`.`seq` DAY
) AS `dates`
FROM `seq_0_to_364` AS `s1`;
Any interval will work as long as it is within the limits of BIGINT(20) UNSIGNED, as this is the limit of the SEQUENCE engine.