I'm trying to make a website which gets some data from database and generate some statistics with this data.
The idea is to allow the user to pick a month and then only query data from that month. So, if the user picks January, query for data > 01/01/2011 and < 31/01/2011.
I thought about generating the starting data from the month and then add one month and subtract a day so I get the last day of the given month but I don't think that's the best approach and also don't know how to generate a full date from a given month.
Any ideas?
SQL centric way:
SELECT * FROM `foo` WHERE MONTH(`date`) = 3 AND YEAR(`date`) = 2011
Substitute the month and year numbers from your selection.
PHP centric way:
$month = 3;
$year = 2011;
$firstDay = "$year-$month-1";
$lastDay = "$year-$month-" . date('t', strtotime($firstDay));
$query = "SELECT * FROM `foo`
WHERE `date` >= '$firstDay'
AND `date` <= '$lastDay'";
Here is a lesson for you.
Most newbie programmers are looking for "the best", "most efficient" methods and stuff.
While they judge efficiency... by amount of the code! "Less code - more efficient!" - they think. And of course being wrong. Let's compare your "efficient" solution with "inefficient" one:
mysql> explain select * from Board where year(date) = 2011 and month(date) = 1;
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | Board | ALL | NULL | NULL | NULL | NULL | 18113 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
mysql> explain select * from Board where date > '2010-12-31' and date < '2011-02-01';
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | Board | range | date | date | 9 | NULL | 325 | Using where |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
Notice possible keys and rows parameters.
in the latter case a database going to get exact rows you need, in the former - its going to pick every row in the table, applying a function on the date and compare it with a constant.
Same goes for the solution from the poor guy who deleted his GOOD answer after your ignorant comment:
mysql> explain select * from Board where `date` > LAST_DAY(DATE_SUB('2011-01-15', INTERVAL 1 MONTH)) AND `date` < DATE_ADD(LAST_DAY('2011-01-15'), INTERVAL 1 DAY);
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | Board | range | date | date | 9 | NULL | 325 | Using where |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
Nuff said.
Selecting records for the last month:
select `id`
from `orders`
where `created_at` > LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
AND `created_at` < DATE_ADD(LAST_DAY(CURDATE()), INTERVAL 1 DAY)
and for the previous month:
select `id`
from `orders`
where `created_at` > LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 2 MONTH))
AND `created_at` < DATE_ADD(LAST_DAY(CURDATE() - INTERVAL 1 MONTH), INTERVAL 1 DAY)
etc.
If you are saving your data as DateTimeStamp, simply add the condition to your query which would only return results from specific month of specific year. You could use LIKE clause for this.
Related
I want to select the next upcoming birthdays in MYSQL.
My date is stored as: 02/19/1981 and not in a date field. I think it has to sort by day and month and not year but i can not find out how.
How can i do this? This is the query till now:
$sql = "SELECT * FROM wp_postmeta WHERE meta_key='_web_date' ORDER BY ....";
If it's possible for you change the date column to type date.
Otherwise try this:
SELECT month(str_to_date(birthdayColumn, "%m/%d/%Y")) as month, day(str_to_date(birthdayColumn, "%m/%d/%Y")) as day FROM yourTable order by month, day;
Result:
+-------+------+
| month | day |
+-------+------+
| 1 | 12 |
| 2 | 19 |
| 9 | 10 |
| 12 | 15 |
+-------+------+
You can use the php date() function. For example ate('Y-m-d',strtotime("+7 day")); then create a sql query which selects dates which are in the upcoming 7 days
This is a test environment.
CREATE TEMPORARY TABLE `birthdays` (
`id` int(4),
`name` VARCHAR(50),
`dob` CHAR(10)
) ENGINE=MEMORY;
INSERT INTO birthdays VALUES (1,'Alice', '02/19/1951'), (2,'Bob', '09/10/2015'), (3,'Carol', '12/15/2000'), (4,'Doug', '01/12/2011');
I created this function to get the next birthday. The logic may throw some interesting results over 29th Feb / 1st March.
DELIMITER $$
CREATE FUNCTION `next_birth_day`(d_dob DATE) RETURNS DATE
DETERMINISTIC
BEGIN
/* NOTE: this logic ignores the handling of leap years */
/* MySQL will happily construct invalid leap years and they are ordered
between 29/2 & 1/3 in this code. */
DECLARE d_today DATE;
DECLARE d_this_year_bday DATE;
DECLARE d_next_year_bday DATE;
SET d_today = DATE(NOW());
SET d_this_year_bday = CONCAT(YEAR(d_today), '-', MONTH(d_dob), '-', DAY(d_dob));
SET d_next_year_bday = CONCAT(YEAR(d_today)+1, '-', MONTH(d_dob), '-', DAY(d_dob));
RETURN IF( d_this_year_bday < d_today, d_next_year_bday, d_this_year_bday);
END
$$
DELIMITER ;
Then you can do a query and order by next_birth_day:
SELECT *, str_to_date(dob, "%m/%d/%Y") AS dob_dt,
next_birth_day(str_to_date(dob, "%m/%d/%Y")) AS next_bday
FROM birthdays
ORDER BY next_birth_day(str_to_date(dob, "%m/%d/%Y")) ASC
giving results like this:
+------+-------+------------+------------+------------+
| id | name | dob | dob_dt | next_bday |
+------+-------+------------+------------+------------+
| 3 | Carol | 12/15/2000 | 2000-12-15 | 2015-12-15 |
| 4 | Doug | 01/12/2011 | 2011-01-12 | 2016-01-12 |
| 1 | Alice | 02/19/1951 | 1951-02-19 | 2016-02-19 |
| 2 | Bob | 09/10/2015 | 2015-09-10 | 2016-09-10 |
+------+-------+------------+------------+------------+
I have a date column which holds either a date value or null. I use that to hold a product's expiration date. I want to write a query to fetch them in this order: Not yet expired, no expiration (null), and expired.
For example, assuming today is May 15:
prd_id prd_name Expiry Date
-----------------
1 name1 May 16
2 name2 May 17
3 name3 May 18
4 name4 May 21
5 namex null
6 namex null
7 namex null
8 namex May 14
9 namex May 12
(A null value denotes no expiration)
How would I do this?
You can try follwing Syntax:-
SELECT *
FROM YOUR_TABLE
ORDER BY CASE WHEN expire_date > CURDATE() THEN 1 END,
WHEN expire_date IS NULL THEN 2 Desc END,
WHEN expire_date < CURDATE() THEN 3 Desc END;
Essentially, you'll be joining three separate queries together:
SELECT * FROM (SELECT * FROM `product_entries` WHERE expires_on IS NOT NULL AND expires_on > CURDATE()
ORDER BY expires_on ASC) a
UNION ALL
SELECT * FROM (SELECT * FROM `product_entries` WHERE expires_on IS NULL) b
UNION ALL
SELECT * FROM (SELECT * FROM `product_entries` WHERE expires_on IS NOT NULL AND expires_on <= CURDATE()
ORDER BY expires_on DESC) c
The first one returns those that haven't expired, the second returns those without expiration, and the third one returns expired entries.
Keep in mind that you'd want the expires_on column to be indexed. Also, as you can see in the third query, I'm counting the current date as being expired. If you want the current day to count as not being expired, then change the <= to < in the third query, and > to >= in the first query.
Another alternative would to be to use a CASE clause (if you're not concerned about the order of each entry, so long as the non-expired are at the top, the perpetual products are in the middle, and the expired entries are at the bottom)
mysql> SELECT * FROM product_entries ORDER BY CASE
-> WHEN expires_on >= CURDATE() THEN 3
-> WHEN expires_on IS NULL THEN 2
-> WHEN expires_on < CURDATE() THEN 1
-> END DESC;
+----+-------------+
| id | expire_date |
+----+-------------+
| 9 | 2015-05-11 |
| 8 | 2015-05-06 |
| 7 | 2015-05-01 |
| 10 | NULL |
| 6 | 2015-04-26 |
| 5 | 2015-04-21 |
| 4 | 2015-04-16 |
| 3 | 2015-04-11 |
| 2 | 2015-04-06 |
| 1 | 2015-04-01 |
+----+-------------+
This may be a little confusing but please bear with me. Here's the thing:
I have a database that contains ~1000 records, as the following table illustrates:
+------+----------+----------+
| id | date | amount |
+------+----------+----------+
| 0001 | 14/01/15 | 100 |
+------+----------+----------+
| 0002 | 14/02/04 | 358 |
+------+----------+----------+
| 0003 | 14/05/08 | 1125 |
+------+----------+----------+
What I want to do is this:
Retrieve all the records beginning at 2014 and until yesterday:
WHERE `date` > '14-01-01' AND `date` < CURDATE()
But also get the sum of amount up to the current date, this is:
WHERE `date` < CURDATE()
I've already got this working by just selecting all the records based on the second condition, getting the sum, and then excluding those which don't match the first condition. Something like this:
SELECT `id`, `date`, `amount` FROM `table`
WHERE `date` < CURDATE()
And then:
$rows = fetchAll($PDOStatement);
foreach($rows as $row) {
$sum += $row->amount;
if (
strtotime($row->date) > strtotime('14-01-01') &&
strtotime($row->date) < strtotime(date('Y-m-d'))
) {
$valid_rows[] = $row;
}
}
unset $rows;
Is there a way to achieve this in a single query, efficiently? Would a transaction be more efficient than sorting out the records in PHP? This has to be SQL-standard compliant (I'll be doing this on MySQL and SQLite).
Update:
It doesn't matter if the result ends up being something like this:
+------+----------+----------+-----+
| id | date | amount | sum |
+------+----------+----------+-----+
| 0001 | 14/01/15 | 100 | 458 |
+------+----------+----------+-----+
| 0002 | 14/02/04 | 358 | 458 |
+------+----------+----------+-----+
| 0003 | 14/05/08 | 1125 | 458 |
+------+----------+----------+-----+
The worst case would be when the resulting set ends up being the same as the set that gives the sum (in this case appending the sum would be irrelevant and would cause an overhead), but for any other regular cases the bandwith save would be huge.
You can create a special record with your sum and add it at the end of your first query
SELECT * FROM `table` WHERE `date` > '14-01-01' AND `date` < CURDATE()
UNION
SELECT 9999, CURDATE(), SUM(`amount`) FROM `table` WHERE `date` < CURDATE()
Then you will have all your desired record and the record with id 9999 or whatever is your sum
This could be achieved by correlated subquery, something like below:
SELECT *, (SELECT SUM(amount) FROM t WHERE t.date < t1.date) AS PrevAmount
FROM t AS t1
WHERE `date` > '14-01-01' AND `date` < CURDATE()
However it is very unefficient if the number of records is large.
It's hackish, but:
> select * from foo;
+------+------+
| id | val |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
+------+------+
5 rows in set (0.02 sec)
> select * from foo
left join (
select sum(val)
from foo
where id < 3
) AS bar ON 1=1
where id < 4;
+------+------+----------+
| id | val | sum(val) |
+------+------+----------+
| 1 | 1 | 3 |
| 2 | 2 | 3 |
| 3 | 3 | 3 |
+------+------+----------+
Basically, do your summing in a joined subquery. That'll attach the sum result to every row in the outer table's results. You'll waste a bit of bandwidth sending that duplicated value out with every row, but it does get you the results in a "single" query.
EDIT:
You can get the SUM using a LEFT OUTER JOIN.
SELECT t1.`id`, t1.`date`, t2.sum_amount
FROM
`table` t1
LEFT OUTER JOIN
(
SELECT SUM(`amount`) sum_amount
FROM `table`
WHERE `date` < CURDATE()
) t2
ON 1 = 1
WHERE t1.`date` > STR_TO_DATE('01,1,2014','%d,%m,%Y') AND t1.`date` < CURDATE();
This will do what you want it to do...optimizing the subquery is the real challenge:
SELECT id,date,amount,(SELECT SUM(amount) FROM table) AS total_amount
FROM table
WHERE date BETWEEN '14-01-01' AND DATE_ADD(CURDATE(), INTERVAL -1 DAY)
how to get all the record from Mysql by supplying month in the query?
i am working in php project and i have a mysql table for posts in which all the posts are saved with their dates(not the publish date or post date but any random date).
now i am selecting the month from the front end and querying for the posts for the given month.
how to retrieve the posts that are in between that month ?
You could use MySQL's MONTH() function
SELECT * FROM tbl WHERE MONTH( date_col ) = 3
You can use MONTHName() function to check the data with the name of month you selected rom front-end.
SELECT * FROM tablename WHERE MONTHNAME(date_column) = "March"
you can fire this query
Select * from table where Date =>'$date1' and Date = '$date2'
to find data between any two dates
you can add where condition as
WHERE YEAR(col_name_of_date) = 2014 AND MONTH(Date) = 4";
or
WHERE YEAR(col_name_of_date) = YEAR(now()) AND MONTH(Date) = 4";
This will need the column col_name_of_date to be in DATE or DATETIME type.
Also you should avoid this kind of DATE() function since its a load to the mysql server.
Its better to construct the date parameter on PHP side before u pass them to query. If you
know the moth start and end and can easily construct the start and end date in Y-m-d format. For the start date d is always 01 and for the end date its the last day of the month.
Check how the query behaves using DATE() and without date
I have a table users where regdate is not indexed
explain select count(*) from users where year(regdate) = year(now()) and month(regdate) = 01 ;
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 79309 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
As u can see num of rows it may scan is the upper limit of the table.
Now lets add an index
mysql> alter table users add index `u_regdate_idx`(`regdate`) ;
Query OK, 79309 rows affected (1.44 sec)
Records: 79309 Duplicates: 0 Warnings: 0
Now run the same again
mysql> explain select count(*) from users where year(regdate) = year(now()) and month(regdate) = 01 ;
+----+-------------+-------+-------+---------------+---------------+---------+------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------------+---------+------+-------+--------------------------+
| 1 | SIMPLE | users | index | NULL | u_regdate_idx | 4 | NULL | 79309 | Using where; Using index |
+----+-------------+-------+-------+---------------+---------------+---------+------+-------+--------------------------+
As u can see there is no change in the performance.
Now lets change the query and see
mysql> explain select count(*) from users where regdate between '2014-01-01' AND '2014-01-31' ;
+----+-------------+-------+-------+---------------+---------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------------+---------+------+------+--------------------------+
| 1 | SIMPLE | users | range | u_regdate_idx | u_regdate_idx | 4 | NULL | 1 | Using where; Using index |
+----+-------------+-------+-------+---------------+---------------+---------+------+------+--------------------------+
Now u can see its far better.
MYSQL Table trial_list structure as follows...
id | product_id | expiry_date(date) | by_user | curr_datentime(timestamp)
we are able to extend any trial, and if we do that it simply another row with new expiry_date.
Now we would like to get rows got expired yesterday, we are currently using following sql query.....
Sample MYSQL DATASET
+----+-------------+-------------+-------------+----------+---------------------+
| id | product_id | comment | expiry_date | by_user | dnt |
+----+-------------+-------------+-------------+----------+---------------------+
| 2 | 50 | testing | 2011-02-18 | tester | 2011-02-17 23:36:12 |
+----+-------------+-------------+-------------+----------+---------------------+
| 3 | 50 | again | 2011-02-20 | tester | 2011-02-19 20:36:12 |
+----+-------------+-------------+-------------+----------+---------------------+
| 4 | 50 | extend | 2011-02-23 | tester | 2011-02-21 22:36:12 |
+----+-------------+-------------+-------------+----------+---------------------+
$sql = 'SELECT id, product_id, expiry_date, by_user, curr_datentime FROM trial_list WHERE expiry_date < CURDATE() ORDER BY expiry_date DESC';
We believe this is not correct as its getting all rows which date is older than yesterday not updated expiry_date, suppose we have given some user expiry date 1st feb 2011 and then we change again with 12th feb 2011, so it selects 1st feb 2011 entry. I think it makes sense.
What you have to do first is get the latest item per product_id. After that you can further filter it down to those which are expired. Something like:
SELECT a.* FROM
trial_list AS a
LEFT JOIN trial_list AS b ON a.product_id = b.product_id AND a.id < b.id
WHERE b.product_id IS NULL
AND a.expiry_date < curdate()
See http://dev.mysql.com/doc/refman/5.5/en/example-maximum-row.html
Try using NOW() instead of CURDATE(), you are comparing a Date to a Timestamp, NOW() will compare timestamps.