I use MariaDB and have a table where each row has a date and a score.
I want to first show the rows where the date is 3 days old or newer, sorted by the score - then show the rest (more than 3 days old) sorted by date.
Since my date is stored in unix time, it's fairly easy to have php calculate 3 days from before now and use that as my $scoreTimeLimit variable in the below:
Here are my two queries:
SELECT * FROM myTable WHERE myDate > $scoreTimeLimit ORDER BY myPopularityScore DESC
SELECT * FROM myTable WHERE myDate < $scoreTimeLimit ORDER BY myDate DESC
However, I would VERY much like to have only 1 query instead of two. Can it be done...?
This is a job for UNION.
SELECT * FROM (
SELECT 0 ord1, NOW() as ord2, *
FROM myTable WHERE myDate > NOW() - INTERVAL 3 DAY
UNION ALL
SELECT 1 ord1, myDate as ord2, *
FROM myTable WHERE myDate <= NOW() - INTERVAL 3 DAY
) a
ORDER BY ord1, ord2 DESC, myPopularityScore
The inner query gives you a single result set with a couple of extra columns added on to help you manage your sorting.
Related
I'm sorry if this sounds like a very basic question but for some reason, today I'm really having trouble getting my head round this. I have a database table with a date_added column in the format of 2014-09-30 20:39:17 and I have a web page with filter options for users. Basically I want to use variables to select different date ranges like so:
SELECT * FROM table WHERE date_added = /* EVERYTHING POSTED TODAY */
SELECT * FROM table WHERE date_added = /* EVERYTHING POSTED WITHIN LAST 7 DAYS */
SELECT * FROM table WHERE date_added = /* EVERYTHING POSTED WITHIN LAST 30 DAYS */
What would I need to put in to get those variables to work?
You can use CURDATE() and very simple INTERVAL arithmetic.
In the following examples assume that query was executed at 2014-10-21 22:25:28:
SELECT * FROM table WHERE date_added >= CURDATE()
-- >= 2014-10-21 00:00:00
SELECT * FROM table WHERE date_added >= NOW() - INTERVAL 24 HOUR
-- >= 2014-10-20 22:25:28
SELECT * FROM table WHERE date_added >= CURDATE() - INTERVAL 7 DAY
-- >= 2014-10-14
SELECT * FROM table WHERE date_added >= CURDATE() - INTERVAL 30 DAY
-- >= 2014-09-21
I have this table in my database:
INSERT INTO `shop_stats` (`date`, `value`) VALUES
('09/2014', 326),
('08/2014', 1007),
('07/2014', 1108),
('06/2014', 1027),
('05/2014', 895),
('04/2014', 650),
('03/2014', 683),
('02/2014', 563),
('01/2014', 499),
('12/2013', 568),
('11/2013', 522),
('10/2013', 371),
('09/2013', 347),
('08/2013', 376),
('07/2013', 418),
('06/2013', 567),
('05/2013', 357);
i need to find a way to display the last 12 months.
I tried this:
SELECT * FROM shop_stats ORDER BY date DESC LIMIT 12
But it doesn't work correctly.
Any suggestions ?
SELECT * FROM shop_stats WHERE date >= DATE_SUB(NOW(),INTERVAL 1 YEAR) LIMIT 12
Your "dates" are stored as strings, presumably with the month first. So, the following order by should work
order by right(date, 4), left(date, 2)
You need to put the year before the month for ordering purposes.
If you want the last twelve months, I would recommend:
where right(date, 4) * 12 + left(date, 2) >= year(now()) * 12 + month(now())
order by right(date, 4), left(date, 2)
The where statement converts the dates to a number of months, for both the "date" column in your data and for the current time.
You can simply use STR_TO_DATE like this
SELECT
*
FROM
shop_stats
order by
STR_TO_DATE(date, '%m/%Y') DESC LIMIT 12
Demo
I suppose your field date has a type string
So you try this:
SELECT * FROM shop_stats
ORDER BY SUBSTRING(date, 4, 4) desc,
substring(date, 1, 2) DESC LIMIT 12
Show Sql Fiddle
You can take a look at DATE_SUB
SELECT * FROM shop_stats where date >= DATE_SUB(now(), INTERVAL 12 MONTH) ORDER BY date
Edit:
How about converting the string to date & doing the appropriate date operations in the sql ?
SELECT DATE
,t1.value
FROM (
SELECT DATE
,STR_TO_DATE(CONCAT (
'01/'
,DATE
), '%d/%m/%Y') date_
,value
FROM shop_stats
) t1
WHERE t1.date_ >= DATE_SUB(now(), INTERVAL 12 MONTH)
ORDER BY t1.date_ DESC
http://sqlfiddle.com/#!2/2be05/8
select * from shop_stats where date >= (NOW() - INTERVAL 12 MONTH) ORDER BY date
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
Im having a little trouble constructing a query.
I have a table with 3 columns.
id - day - pageviews
What i basically want to do is get 8 id's from the table where the pageviews are the highest from the last 60 days.
The day column is a datetime mysql type.
Any help would be great, im having a little trouble figuring this one out.
Cheers,
Almost the same as TuteC posted, but you'll need a group by to get what you need...
SELECT id, SUM(pageviews) totalViews
FROM table
WHERE DATE_SUB(CURDATE(), INTERVAL 60 DAY) <= day
GROUP BY id
ORDER BY totalViews DESC
LIMIT 8
Do something like this:
SELECT id FROM table_name
WHERE DATE_SUB(CURDATE(),INTERVAL 60 DAY) <= day
ORDER BY pageviews DESC
LIMIT 8;
$sixtyDaysAgo = date('Y-m-d',strtotime('-60 days'));
$sql = "SELECT id
FROM table_name
WHERE day >= '$sixtyDaysAgo 00:00:00'
ORDER BY pageviews DESC
LIMIT 8";
If each row is a number of pageviews for that day, and you're looking for the highest total sum of 60 days' worth, then you'll need to total them all and then grab the top 8 from among those totals, like so:
$sql = "SELECT id
FROM (
SELECT id, SUM(pageviews) AS total_pageviews
FROM table_name
WHERE day >= '$sixtyDaysAgo 00:00:00'
GROUP BY id
) AS subselect
ORDER BY total_pageviews DESC
LIMIT 8";
I'm looking for a best practice advice how to speed up queries and at the same time to minimize the overhead needed to invoke date/mktime functions. To trivialize the problem I'm dealing with the following table layout:
CREATE TABLE my_table(
id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT,
important_data INTEGER,
date INTEGER);
The user can choose to show 1) all entries between two dates:
SELECT * FROM my_table
WHERE date >= ? AND date <= ?
ORDER BY date DESC;
Output:
10-21-2009 12:12:12, 10002
10-21-2009 14:12:12, 15002
10-22-2009 14:05:01, 20030
10-23-2009 15:23:35, 300
....
I don't think there is much to improve in this case.
2) Summarize/group the output by day, week, month, year:
SELECT COUNT(*) AS count, SUM(important_data) AS important_data
FROM my_table
WHERE date >= ? AND date <= ?
ORDER BY date DESC;
Example output by month:
10-2009, 100002
11-2009, 200030
12-2009, 3000
01-2010, 0 /* <- very important to show empty dates, with no entries in the table! */
....
To accomplish option 2) I'm currently running a very costly for-loop with mktime/date like the following:
for(...){ /* example for group by day */
$span_from = (int)mktime(0, 0, 0, date("m", $time_min), date("d", $time_min)+$i, date("Y", $time_min));
$span_to = (int)mktime(0, 0, 0, date("m", $time_min), date("d", $time_min)+$i+1, date("Y", $time_min));
$query = "..";
$output = date("m-d-y", ..);
}
What are my ideas so far? Add additional/ redundant columns (INTEGER) for day (20091212), month (200912), week (200942) and year (2009). This way I can get rid of all the unnecessary queries in the for loop. However I'm still facing the problem to very fastly calculate all dates that doesn't have any equivalent in database. One way to simply move the problem could be to let MySQL do the job and simply use one big query (calculate all the dates/use MySQL date functions) with a left join (the data). Would it be wise to let MySQL take the extra load? Anyway I'm reluctant to use all these mktime/date in the for loop. Since I have complete control over the table layout and code even suggestions with major changes are welcome!
Update
Thanks to Greg I came up with the following SQL query. However it still bugs me to use 50 lines of sql statements - build up with php - that maybe could be done faster and more elegantly otherwise:
SELECT * FROM (
SELECT DATE_ADD('2009-01-30', INTERVAL 0 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 1 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 2 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 3 DAY) AS day UNION ALL
......
SELECT DATE_ADD('2009-01-30', INTERVAL 50 DAY) AS day ) AS dates
LEFT JOIN (
SELECT DATE_FORMAT(date, '%Y-%m-%d') AS date, SUM(data) AS data
FROM test
GROUP BY date
) AS results
ON DATE_FORMAT(dates.day, '%Y-%m-%d') = results.date;
You definitely shouldn't be doing a query inside a loop.
You can group like this:
SELECT COUNT(*) AS count, SUM(important_data) AS important_data, DATE_FORMAT('%Y-%m', date) AS month
FROM my_table
WHERE date BETWEEN ? AND ? -- This should be the min and max of the whole range
GROUP BY DATE_FORMAT('%Y-%m', date)
ORDER BY date DESC;
Then pull these into an array keyed by date and loop over your data range as you are doing (that loop should be pretty light on CPU).
Another idea is not to use string inside the query. Transform the string parameter to datetime, on mysql.
STR_TO_DATE(str,format)
http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html