Sort SQL by range of date - php

I have a small self made gallery which im still working on it:
http://springbreak.enteratenorte.com
And the SQL structure is the following:
CREATE TABLE IF NOT EXISTS 'en_albums' (
'id' int(11) NOT NULL auto_increment,
'name' text,
'folder' text,
'description' text,
'hits' int(11) default NULL,
'datecreated' date default NULL,
PRIMARY KEY ('id')
) ENGINE=MyISAM AUTO_INCREMENT=372 DEFAULT CHARSET=latin1 AUTO_INCREMENT=372 ;
And I have around 300 albums, so I would to sort the most popular which is simple with this query:
SELECT * FROM en_albums ORDER BY hits DESC
But I would like to know how can I do the same thing, but only sort album not older than 6 month, or 1 month, or 1 year, this is a sample of the lines in my albums:
INSERT INTO 'en_albums'
('id', 'name', 'folder',
'description', 'hits', 'datecreated')
VALUES
(1, 'Friends Picture', 'en-fotosdeamigos',
'Pictures sent by our visitors', 1514, '2005-07-19');
Thanks for the help! :D

Assuming you only want to get albums created within a time period you can use
SELECT * FROM en_albums WHERE datecreated > DATE_SUB(NOW(),INTERVAL 6 MONTH) ORDER BY hits DESC
This will only return items which have a datecreated greater than 6 months ago. You can change the 6 and MONTH part to be what you want. You can replace MONTH with any of the following:
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR

There seem to be two answers to you question. #Tim took one answer (exclude albums older than your specified limit). This gives an alternative...
First, list all albums less than 6 months old
(For those albums, list those with the most hits first)
After those albums, cary on the list with all albums over 6 months old
(For those albums, list the most recent album first)
This does just that...
SELECT
*
FROM
en_albums
ORDER BY
CASE WHEN datecreated > DATE_SUB(NOW(), INTERVAL 6 MONTH) THEN 0 ELSE 1 END ASC,
CASE WHEN datecreated > DATE_SUB(NOW(), INTERVAL 6 MONTH) THEN hits ELSE 0 END DESC,
datecreated DESC
Any album less than 6 months old will be sorted by...
0, hits, datecreated
Any album more than 6 months old will be sorted by...
1, 0, datecreated

Related

Display payment made by mark every 3 month for the last 3 years

I have a table called payment it has date field, i have a customer called Mark who has been making payment every day for 3 years
Table: Payment
Fields: Name , Amountpaid, date
I want to display payment record made by mark every 3 month and also the total Amountpaid for 3 years
How i want the result to look like
First 3 months payment record table
total Amountpaid at the bottom of the table
second 3 months payment record table
total Amountpaid at the bottom of the table
Third 3 months payment record table
total Amountpaid at the bottom of the table
and so on for 3 years
Please do help out
It seems like you're looking for a SQL solution for this, but databases are for holding data, they aren't for formatting it into a report. To this end my advice would be: Don't try and do this in the database, do it in the front end code instead
It will be very simple to run a query like
SELECT * FROM payment WHERE
name = 'mark' and
`date` between date_sub(now(), interval 3 year) and now()
ORDER BY date
And then put the results into an HTML table usig a loop, and a variable that keeps track of the amount paid total. Every 3 months reset the variable. If you want MySQL to do a bit more data processing to help out you can do this:
SELECT * FROM
payment
INNER JOIN
(SELECT YEAR(`date`) + (QUARTER(`date`)/10) as qd, SUM(amountpaid) as qp FROM payment WHERE name = 'mark' GROUP BY YEAR(`date`), QUARTER(`date`)) qpt
ON
qpt.qd = YEAR(`date`) + (QUARTER(`date`)/10)
WHERE
name = 'mark' AND
`date` between date_sub(now(), interval 3 year) and now()
ORDER BY `date`
This will give all mark's data row by row and an extra two columns (that mostly repeat themselves over and over) showing the year and quarter (3 months) of the year like 2017.1, 2017.2, together with a sum of all payments made in that quarter. Formatting it in the front end now won't need a variable to keep a running total of the amount paid
This is about the limit of what you should do with formatting the data in the database (personal opinion). If, however, you're determined to have MySQL do pretty much all this, read on..
Ysth mentioned rollup, which is intended for summarising data.. such a solution would look like this:
SELECT
Name, `date`, SUM(amountpaid) as amountpaid
FROM
payment
WHERE
name = 'mark' AND
`date` between date_sub(now(), interval 3 year) and now()
GROUP BY
name,
YEAR(`date`) + (QUARTER(`date`)/10),
`date`
WITH ROLLUP
The only downside with this approach is you also get a totals row for all payments by mark. To suppress that, use grouping sets instead:
SELECT
Name, `date`, SUM(amountpaid) as amountpaid
FROM
payment
WHERE
name = 'mark' AND
`date` between date_sub(now(), interval 3 year) and now()
GROUP BY GROUPING SETS
(
(
name,
YEAR(`date`) + (QUARTER(`date`)/10),
`date`
),
(
name,
YEAR(`date`) + (QUARTER(`date`)/10)
)
)
You can use a group by on the year and month divided by 3 and truncated using floor
SELECT
EXTRACT(YEAR_MONTH FROM `date`),
SUM(`Amountpaid`)
FROM
`Payment`
WHERE
`Name` = 'Mark'
AND `date` >= DATE_SUB(NOW(), INTERVAL 3 YEAR)
GROUP BY
EXTRACT(YEAR FROM `date`),
FLOOR(EXTRACT(MONTH FROM `date`) / 3)
For the total you will need to iterate the result set and sum up the amounts paid, or if you want it as the final record you could do a UNION SELECT but this would be ineffecient, but for completeness it is below:
SELECT
EXTRACT(YEAR_MONTH FROM `date`),
SUM(`Amountpaid`)
FROM
`Payment`
WHERE
`Name` = 'Mark'
AND `date` >= DATE_SUB(NOW(), INTERVAL 3 YEAR)
GROUP BY
EXTRACT(YEAR FROM `date`),
FLOOR(EXTRACT(MONTH FROM `date`) / 3)
UNION SELECT
NULL,
SUM(`Amountpaid`)
FROM
`Payment`
WHERE
`Name` = 'Mark'
AND `date` >= DATE_SUB(NOW(), INTERVAL 3 YEAR)
This is for get summary per 3 months :
select year(date)*100+floor(month(date)/3) as period, sum(amountpaid)
from payment
where name = 'mark' and (date between '2014-01-01' and '2017-01-01')
group by year(date)*100+floor(month(date)/3)
order by period
And this is how to get summary 3 year :
select sum(amountpaid) from payment where name = 'mark' and (date between '2014-01-01' and '2017-01-01')
You can change the date between for your need

PHP / MysQl limit user posts in month - day - hours

I am working on a php script, I have an admin control panel to add users, and I need to add a few options like user monthly posts - user daily posts - user hourly posts, let's say I set user monthly post to 30 and user daily posts is 10 and user hourly post is 5, that will be:
The user can post only 5 posts per 1 hour and 10 posts per day from the monthly 30 posts limit, if user monthly post is used, he can't add posts in this month and the next month i want to automatically add another 30 posts!!
My user table name is (user):
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`monthly` int(2) unsigned NOT NULL,
`daily` int(10) unsigned NOT NULL,
`hourly` int(10) unsigned NOT NULL,
And my post table name is user_post:
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`created_dt` datetime NOT NULL,
`user` int(10) unsigned NOT NULL,
I need to know :
how to make the monthly user column updated monthly to 30 if I registered the user with 30 monthly post limit.
when user is logged in and he want to post, how to check if he have more monthly, daily, hourly posts remaining!
Can anyone help me to see how I can do that, thank you my friends
Add a "datetime posted" column to your "user posts" table. For this example, we'll call the new column `created_dt` (with a dataype of DATETIME. We'll also assume that the name of the table is `user_post`.
When a row is inserted, populate the new column with the current date and time.
When a user attempts to post another row, you could perform a check whether any limit has been exceeded.
SELECT SUM(1) AS cnt_past_month
, SUM(p.created_dt >= NOW() + INTERVAL -7 DAY) AS cnt_past_week
, SUM(p.created_dt >= NOW() + INTERVAL -1 HOUR) AS cnt_past_hour
FROM user_post p
WHERE p.user_id = ?
AND p.created_dt >= NOW() - INTERVAL 1 MONTH
You can then compare the values returned to the limits for the user, to see if any limits have been exceeded, or would be exceeded if another post is added.
For optimal performance of this query, you will want an index
ON user_post(userid, created_dt)
You could get the limits for the user within the query itself...
SELECT q.count_past_month
, m.limit_past_month
, q.count_past_week
, m.limit_past_week
, q.count_past_hour
, m.limit_past_hour
FROM ( SELECT p.userid
, SUM(1) AS cnt_past_month
, SUM(p.created_dt >= NOW() - INTERVAL 7 DAY) AS cnt_past_week
, SUM(p.created_dt >= NOW() - INTERVAL 1 HOUR) AS cnt_past_hour
FROM user_post p
WHERE p.user_id = ?
AND p.created_dt >= NOW() - INTERVAL 1 MONTH
) q
CROSS
JOIN ( SELECT MIN(l.limit_per_month)
, MIN(l.limit_per_week)
, MIN(l.limit_per_hour)
FROM user_limit l
WHERE l.user_id = ?
) m
With this approach, you won't need a bunch of unnecessary DML to increment counters, and reset counters. Any change you make to the limits for user would could take effect immediately.
And you could use a value of "0" to specify "no limit". Your logic for doing comparisons would need to take that into account.
That's how I would do it.
I could also do the comparisons of the count to the limit in the query itself, returning the "number of posts remaining" until the limit is exceeded.
SELECT m.limit_past_month-IFNULL(q.count_past_month,0) AS remaining_past_month
, m.limit_past_week -IFNULL(q.count_past_week ,0) AS remaining_past_week
, m.limit_past_hour -IFNULL(q.count_past_hour ,0) AS remaining_past_hour
FROM (
The mechanics of the "ban" (no posts allowed) and "unlimited" (no limits on posts) would need to be worked out. For example using 0 to represent a ban, and a NULL to represent "no limit".
With that, we'd know that when the query returns a column with value less than or equal to zero, it would mean that a limit has been exceeded (or would be exceeded by another post.) All other values (NULL or positive integer) in the column would mean a "next post" would be allowed.
before inserting a posts you can run a similar query like this query to find out your posts for hourly,daily,and monthly and check that in code and decide to allow/not allow user to add posts.
without knowing your schema i just created some possible names for columns from your post table. and of course your userid (1) should be replaced with the userid of the user you're trying to prevent adding posts.
SELECT SUM(CASE WHEN created_at > CURRENT_TIMESTAMP() - INTERVAL 1 HOUR THEN 1
ELSE 0
END) AS hourly_posts,
SUM(CASE WHEN created_at > CURRENT_TIMESTAMP() - INTERVAL 1 DAY THEN 1
ELSE 0
END) AS daily_posts,
SUM(CASE WHEN created_at > CURRENT_TIMESTAMP() - INTERVAL 1 MONTH THEN 1
ELSE 0
END) AS monthly_posts
FROM posts
WHERE userid = 1
I have updated Tin Tran's code:
SELECT SUM(CASE WHEN (created_at > CURRENT_TIMESTAMP() - INTERVAL 1 HOUR) and date_format(CURRENT_TIMESTAMP(),'%H') = date_format(created_at,'%H')) THEN 1
ELSE 0
END) AS hourly_posts,
SUM(CASE WHEN (created_at > CURRENT_TIMESTAMP() - INTERVAL 1 DAY and date_format(CURRENT_TIMESTAMP(),'%D') = date_format(created_at,'%D')) THEN 1
ELSE 0
END) AS daily_posts,
SUM(CASE WHEN (created_at > CURRENT_TIMESTAMP() - INTERVAL 1 MONTH and date_format(CURRENT_TIMESTAMP(),'%m') = date_format(created_at,'%m')) THEN 1
ELSE 0
END) AS monthly_posts
FROM posts
WHERE userid = 1

Efficient way to sum measurements / time series by given interval in php

I have a series of measurement data / time series in the same interval of 15 minutes. Furthermore, I have a given period (e.g. one day, current week, month, year, (...) and I need to summarize values by hour, day, month, (...).
E.g. summarize all values of the last month, by day.
My approach is to generate a temporary array with the needed interval per period in the first step. E.g. here in PHP (PHP is not that necessary, I would prefer Python or Javascript if it provides a faster method)
$this->tempArray = array(
'2014-10-01T00:00:00+0100' => array(),
'2014-10-02T00:00:00+0100' => array(),
'2014-10-03T00:00:00+0100' => array(),
'2014-10-04T00:00:00+0100' => array(),
(...)
'2014-10-31T00:00:00+0100' => array()
);
In the second step, I loop through each date/value pair (in this example 4*24*31, (96 per day)) and assign them to my temporary array. For each date, I override some values from the datetime object. In this example the hour and the minutes to match the keys in the temp array.
$insert = array(
'datetime' => $datetime,
'value' => $value
);
if ($interval == "d") {
$this->tempArray[date('Y-m-d\T00:00:sO', $datetime)][] = $insert;
}
At the last step, I loop through the temp array and summarize each array. As the result, I receive an array with 31 new date/values pairs, summarized by each day. This works fine. However is there a faster way or more efficient way? It takes nearly 0.5 seconds with this approach for one month. (If someone is interested in the source code, I will add a gist). The data are stored within a mysql database with 15 mio entries.
// Edit: I think the best way is to group this with mysql.
My current SQL query to fetch data from one year:
SELECT
FROM_UNIXTIME(PointOfTime)) as `date`,
value
FROM data
WHERE EnergyMeterId="0ca64479-bddf-4b91-9e35-bf81f4bfa84c"
and PointOfTime >= unix_timestamp('2013-01-01T00:00:00')
and PointOfTime <= unix_timestamp('2013-12-31T23:45:00')
order by `date` asc;
If the data lies in MySQL, then that is where I would implement my solution. It is trivial to use various MySQL date/time functions to aggregate this data. Let's take a simplistic example assuming a table structure like this:
id: autoincrement primary key
your_datetime: datetime or timestamp field
the_data: the data items you are trying to summarize
A query to summarize by day (most recent first) would look like this:
SELECT
DATE(your_datetime) as `day`,
SUM(the_data) as `data_sum`
FROM table
GROUP BY `day`
ORDER BY `day` DESC
If you wanted to limit it by some period of time (last 7 days for example) you can simply add a where condition
SELECT
DATE(your_datetime) as `day`,
SUM(the_data) as `data_sum`
FROM table
WHERE your_datetime > DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
GROUP BY `day`
ORDER BY `day` DESC
Here is another example where you specify a range of datetimes
SELECT
DATE(your_datetime) as `day`,
SUM(the_data) as `data_sum`
FROM table
WHERE your_datetime BETWEEN '2014-08-01 00:00:00' AND '2014-08-31 23:59:59'
GROUP BY `day`
ORDER BY `day` DESC
Sum by hour:
SELECT
DATE(your_datetime) as `day`,
HOUR(your_datetime) as `hour`
SUM(the_data) as `data_sum`
FROM table
WHERE your_datetime BETWEEN '2014-08-01 00:00:00' AND '2014-08-31 23:59:59'
GROUP BY `day`, `hour`
ORDER BY `day` DESC, `hour` DESC
Sum by month:
SELECT
YEAR(your_datetime) as `year`,
MONTH(your_datetime) as `month`
SUM(the_data) as `data_sum`
FROM table
GROUP BY `year`, `month`
ORDER BY `year` DESC, `month` DESC
Here is a reference to the MySQL Date/Time functions:
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-sub

curdate interval for a int field?

I want to select records from the last 3 years...
the following use to work when the table column type 'released_year' was a date
$query = 'SELECT
album.album_id,
album.title,
album.released_year,
FROM album
WHERE album.released_year >= ( CURDATE() - INTERVAL 3 YEAR )
ORDER BY album.released_year DESC, album.title';
but the table column type change and it's now a type smallint to handle only the 4 digits of a year.
How do i select records from the last 3 years now?
... album.released_year >= year(CURDATE()) -3 ...
ugly as you're not using real dates, but it will work for a while

Very specific MySQL query I want to improve

This is my scenario: I have a table that contains events, every event has a field called 'created' with the timestamp in which that event was created. Now I need to sort the events from newest to oldest, but I do not want MySQL to return them all. I need only the latest in a given interval, for example in a range of 24 hours (EDIT: I'd like to have a flexible solution, not only for a 24 hours range, but maybe every few hours). And I only need for the last 10 days. I have achieved that but i'm sure in the most inefficient ways possible, that is, something like that:
$timestamp = time();
for($i = 0; $i < 10; $i++) {
$query = "SELECT * FROM `eventos` WHERE ... AND `created` < '{$timestamp}' ORDER BY `created` DESC LIMIT 1";
$return = $database->query( $query );
if($database->num( $return ) > 0) {
$event = $database->fetch( $return );
$events[] = $event;
$timestamp = $timestamp - 86400;
}
}
I hope I was clear enough. Thanks,
Jesús.
If you have an index with created as the leading column, MySQL may be able to do a reverse scan. If you have a 24 hour period that doesn't have any events, you could be returning a row that is NOT from that period. To make sure you're getting a row in that period, you would really need to include a lower bound on the created column as well, something like this:
SELECT * FROM `eventos`
WHERE ...
AND `created` < FROM_UNIXTIME( {$timestamp} )
AND `created` >= DATE_ADD(FROM_UNIXTIME( {$timestamp} ),INTERVAL -24 HOUR)
ORDER BY `created` DESC
LIMIT 1
I think the big key to performance here is an index with created as the leading column, along with all (or most) of the other columns referenced in the WHERE clause, and making sure that index is used by your query.
If you need a different time interval, down to the second, this approach could be easily generalized.
SELECT * FROM `eventos`
WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL 0*{$nsecs} SECOND)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*{$nsecs} SECOND)
ORDER BY `created` DESC
LIMIT 1
From your code, it looks like the 24-hour periods are bounded at an arbitrary time... if the time function returns e.g. 1341580800 ('2012-07-06 13:20'), then your ten periods would all be from 13:20 on a given day to 13:20 the following day.
(NOTE: be sure that if your parameter is a unix timestamp integer, that this is being interpreted correctly by the database.)
It might be more efficient to pull the ten rows in a single query. If there is a guarantee that 'timestamp' is unique, then it's possible to craft such a query, but the query text will be considerably more complex than what you have now. We could mess with getting MAX(timestamp_) within each period, and then joining that back to get the row... but that's going to be really messy.
If I were going to try to pull all ten rows I would probably try going with a UNION ALL approach, not very pretty, but it least it could be tuned.
SELECT p0.*
FROM ( SELECT * FROM `eventos` WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL 0*24 HOUR)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*24 HOUR)
ORDER BY `created` DESC LIMIT 1
) p0
UNION ALL
SELECT p1.*
FROM ( SELECT * FROM `eventos` WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*24 HOUR)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -2*24 HOUR)
ORDER BY `created` DESC LIMIT 1
) p1
UNION ALL
SELECT p2.*
FROM ( SELECT * FROM `eventos` WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -2*24 HOUR)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -3*24 HOUR)
ORDER BY `created` DESC LIMIT 1
) p2
UNION ALL
SELECT p3.*
FROM ...
Again, this could be generalized, to pass in a number of seconds as an argument. Replace HOUR with SECOND, and replace the '24' with a bind parameter that has a number of seconds.
It's rather long winded, but it should run okay.
Another really messy and complicated way to get this back in a single result set would be to use an inline view to get the end timestamp for the ten periods, something like this:
SELECT p.period_end
FROM (SELECT DATE_ADD(t.t_,INTERVAL -1 * i.i_* {$nsecs} SECOND) AS period_end
FROM (SELECT FROM_UNIXTIME( {$timestamp} ) AS t_) t
JOIN (SELECT 0 AS i_
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9
) i
) p
And then join that to your table ...
ON `created` < p.period_end
AND `created` >= DATE_ADD(p.period_end,INTERVAL -1 * {$nsecs} SECOND)
And pull back MAX(created) for each period GROUP BY p.period_end, wrap that in an inline view.
And then join that back to your table to get each row.
But that is really, really messy, hard to understand, and not likely to be any faster (or more efficient) than what you are already doing. The most improvement you could make is the time it takes to run 9 of your queries.
Assuming you want the latest (having the greatest created date) event per day for the last 10 days.
so let's get the latest timestamp per day
$today = date('Y-m-d');
$tenDaysAgo = date('Y-m-d', strtotime('-10 day'));
$innerSql = "SELECT date_format(created, '%Y-%m-%d') day, MAX(created) max_created FROM eventos WHERE date_format(created, '%Y-%m-%d') BETWEEN '$today' and '$tenDaysAgo' GROUP BY date_format(created, '%Y-%m-%d')";
Then we can select all the events that match those created dates
$outerSql = "SELECT * FROM eventos INNER JOIN ($innerSql) as A WHERE eventos.created = A.max_created";
I haven't had a chance to test this, but the principles should be sound enough.
If you want to group by some other arbitrary number of hours you would change innerSql:
$fromDate = '2012-07-06' // or if you want a specific time '2012-07-06 12:00:00'
$intervalInHours = 5;
$numberOfIntervals = 10;
$innerSql = "SELECT FLOOR(TIMESTAMPDIFF(HOUR, created, '$fromDate') / $intervalInHours) as grouping, MAX(created) as max_created FROM eventos WHERE created BETWEEN DATE_SUB('$fromDate', INTERVAL ($intervalInHours * $numberOfIntervals) HOUR) AND '$fromDate' GROUP BY FLOOR(TIMESTAMPDIFF(HOUR, created, '$fromDate') / $intervalInHours)";
I'd add another column that is the date(not time) and then use MySQL "group by" to get the most recent for each date.
http://www.tizag.com/mysqlTutorial/mysqlgroupby.php/
This tutorial does just that, but by product type instead of date. This should help!
Do you want all of the events within the 10 days, or just one event per day within the 10 day period?
Either way, consider MySQL's date functions for assistance. It should help you get the date range you want.
Here's one that will get you the first event of the day for the last 10 days.
SELECT *
FROM eventos
WHERE created BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 10 DAY) AND DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
GROUP BY DATE(created)
ORDER BY MAX(created) DESC
LIMIT 10
Try this:
SELECT *
FROM eventos
WHERE created BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 10 DAY) AND DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
ORDER BY created DESC
LIMIT 10

Categories