How to make complete date from month and year in MySQL? [duplicate] - php

I am currently developing an application that displays documents and allows the members to search for these documents by a number of different parameters, one of them being date range.
The problem I am having is that the database schema was not developed by myself and the creator of the database has created a 'date' table with fields for 'day','month','year'.
I would like to know how I can select a specific day, month, year from the table and create a date object in SQL so that I can compare dates input by the user using BETWEEN.
Below is the structure of the date table:
CREATE TABLE IF NOT EXISTS `date` (
`deposition_id` varchar(11) NOT NULL default '',
`day` int(2) default NULL,
`month` int(2) default NULL,
`year` int(4) default NULL,
PRIMARY KEY (`deposition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

When you have integer values for year, month and day you can make a DATETIME by combining MAKEDATE() and DATE_ADD(). MAKEDATE() with a constant day of 1 will give you a DATETIME for the first day of the given year, and then you can add to it the month and day with DATE_ADD():
mysql> SELECT MAKEDATE(2013, 1);
+-------------------+
| MAKEDATE(2013, 1) |
+-------------------+
| 2013-01-01 |
+-------------------+
mysql> SELECT DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH);
+---------------------------------------------------+
| DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH) |
+---------------------------------------------------+
| 2013-03-01 |
+---------------------------------------------------+
mysql> SELECT DATE_ADD(DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH), INTERVAL (11)-1 DAY);
| DATE_ADD(DATE_ADD(MAKEDATE(2013, 1), INTERVAL (3)-1 MONTH), INTERVAL (11)-1 DAY) |
+----------------------------------------------------------------------------------+
| 2013-03-11 |
+----------------------------------------------------------------------------------+
So to answer the OP's question:
SELECT * FROM `date`
WHERE DATE_ADD(DATE_ADD(MAKEDATE(year, 1), INTERVAL (month)-1 MONTH), INTERVAL (day)-1 DAY)
BETWEEN '2013-01-01' AND '2014-01-01';

You can use STR_TO_DATE() function.

The simplest way to do this is:
DATE(CONCAT_WS('-', year, month, day))
LPAD is not necessary as #pbarney pointed out earlier. If you are comparing with another date object, it's not strictly necessary to wrap it with DATE as MySQL will cast it automatically:
some_date_field > CONCAT_WS('-', year, month, day)

To build a sortable date string from that, you'll need CONCAT to join the bits together and LPAD to make sure the month and day fields are two digits long. Something like this:
CONCAT(`year`,'-',LPAD(`month`,2,'00'),'-',LPAD(`day`,2,'00'))
Once you have that, you should be able to use BETWEEN, as they'll be in a sortable format. However if you still need to convert them to actual datetime fields, you can wrap the whole thing in UNIX_TIMESTAMP() to get a timestamp value.
So you'd end up with something like this:
SELECT UNIX_TIMESTAMP(CONCAT(`year`,'-',LPAD(`month`,2,'00'),'-',LPAD(`day`,2,'00'))) as u_date
WHERE u_date BETWEEN timestamp_1 and timestamp_2
However, be aware that this will be massively slower than if the field was just a simple timestamp in the first place. And you should definitely make sure you have an index on the year, month and day fields.

Expanding this answer, here's my take on it:
DELIMITER $$
CREATE FUNCTION fn_year_month_to_date(var_year INTEGER,
var_month enum('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12')
)
RETURNS DATE
BEGIN
RETURN (MAKEDATE(var_year, 1) + INTERVAL (var_month - 1) MONTH);
END $$
DELIMITER ;
SELECT fn_year_month_to_date(2020, 12)
;

Try to use CONCAT() and make it one field and compare .
Am not sure you can compare it as date after concatenation.
You can compare as integer.
concatinate year month day and make an integer like this 20101017 and compare.
Hopefully :)

Related

Find Records form date field and time field in one day before in mysql

I have the following structure as a table, I want to select the record from one day before, I have tried the following query for selecting the record.
SELECT slot_date, slot_time FROM slot_booking WHERE slot_date =
DATE_SUB(CURDATE(), INTERVAL 24 HOUR)
assume if today is (2018-04-03) time is 09 (24hr Format) I want to select a record from DB 2018-04-04 TIME between 09:00:00 to 9.30:00
i am struggling with compare time field, if you have any suggestion or solution post you answer
Concat date and time fields, use str_to_date to convert to datetime then use date_add or date_sub as required
SET #DT = '2018-04-14';
SET #T = '09:00:00';
set #now = '2018-04-13 09:00:00';
SELECT STR_TO_DATE(CONCAT(#DT,#T),'%Y-%m-%d%H:%i:%s') dt,
case when STR_TO_DATE(CONCAT(#DT,#T),'%Y-%m-%d%H:%i:%s')
between date_add(#now, interval 24 hour) and date_add(date_add(#now, interval 24 hour),interval 30 minute) then 'true'
else 'false'
end as inrange;
+---------------------+---------+
| dt | inrange |
+---------------------+---------+
| 2018-04-14 09:00:00 | true |
+---------------------+---------+
1 row in set (0.00 sec)

check dates on database

On my database table I have 2 columns, start_date and end_date.
Sample data would be:
-------------------------------
start_date | end_date
-------------------------------
2017-11-01 2017-11-02
2017-11-03 2017-11-07
2017-11-20 2017-11-28
2017-11-13 2017-12-02
-------------------------------
I need to find if there are 5 consecutive days that are not yet used, which in this case, there is:
(2017-11-08 to 2017-11-13)
I'm using PHP and MySQL.
Thanks in advance!
You'd need to check for edge cases depending on your actual data and if there were no overlap dates, but this is a good start for the provided data.
Assuming table and data as defined as below:
CREATE TABLE
`appointments`
(
`appointment_id` INT PRIMARY KEY AUTO_INCREMENT,
`start_date` DATE,
`end_date` DATE
);
INSERT INTO
`appointments`
(`start_date`, `end_date`)
VALUES
('2017-11-01', '2017-11-02'),
('2017-11-03', '2017-11-07'),
('2017-11-20', '2017-11-28'),
('2017-11-13', '2017-12-02');
If you order the rows, and take the lag from the end date before it, and take any gaps of 5 or more. In SQL Server there are LAG functions, but here's a way of doing the same. Then once you have a table of all rows and their corresponding gaps, you take the start date of that period, and create the gap period from the number of days between. Since TIMESTAMPDIFF is inclusive, you need to subtract a day.
SET #end_date = NULL;
SELECT
DATE_ADD(`start_date`, INTERVAL -(`gap_from_last`-1) DAY) AS `start_date`,
`start_date` AS `end_date`
FROM
(
SELECT
`appointment_id`,
CASE
WHEN #end_date IS NULL THEN NULL
ELSE TIMESTAMPDIFF(DAY, #end_date, `start_date`)
END AS `gap_from_last`,
`start_date`,
#end_date := `end_date` AS `end_date` -- Save the lag date from the row before
FROM
`appointments`
ORDER BY
`start_date`,
`end_date`
) AS `date_gap` -- Build table that has the dates and the number of days between
WHERE
`gap_from_last` > 5;
Provides:
start_date | end_date
------------------------
2017-11-08 | 2017-11-13
Edit: Oops! Forgot the SQLFiddle (http://sqlfiddle.com/#!9/09cfce/16)
SELECT x.end_date + INTERVAL 1 DAY unused_start
, MIN(y.start_date) unused_end
FROM appointments x
JOIN appointments y
ON y.start_date >= x.end_date
GROUP
BY x.start_date
HAVING DATEDIFF(MIN(y.start_date),unused_start) >= 5;

MySQL BETWEEN DATE RANGE

I have a scenario where I need to pull up delivery dates based on a table below (Example)
job_id | delivery_date
1 | 2013-01-12
2 | 2013-01-25
3 | 2013-02-15
What I'm trying to do is show the user all the delivery dates that start with the earliest (in this case it would be 2013-01-12) and add an another 21 days to that. Basically, the output I would expect it to show of course, the earliest date being the starting date 2013-01-12 and 2013-01-25. The dates past the February date are of no importance since they're not in my 21 date range. If it were a 5 day range, for example, then of course 2013-01-25 would not be included and only the earliest date would appear.
Here is main SQL clause I have which only shows jobs starting this year forward:
SELECT date, delivery_date
FROM `job_sheet`
WHERE print_status IS NULL
AND job_sheet.date>'2013-01-01'
Is it possible to accomplish this with 1 SQL query, or must I go with a mix of PHP as well?
You can use the following:
select *
from job_sheet
where print_status IS NULL
and delivery_date >= (select min(delivery_date)
from job_sheet)
and delivery_date <= (select date_add(min(delivery_date), interval 21 day)
from job_sheet)
See SQL Fiddle with Demo
If you are worried about the dates not being correct, if you use a query then it might be best to pass in the start date to your query, then add 21 days to get the end date. Similar to this:
set #a='2013-01-01';
select *
from job_sheet
where delivery_date >= #a
and delivery_date <= date_add(#a, interval 21 day)
See SQL Fiddle with Demo
SELECT date,
delivery_date
FROM job_sheet
WHERE print_status IS NULL
AND job_sheet.date BETWEEN (SELECT MIN(date) FROM job_sheet) AND
(SELECT MIN(date) FROM job_sheet) + INTERVAL 21 DAY
SELECT j.job_id
, j.delivery_date
FROM `job_sheet` j
JOIN ( SELECT MIN(d.delivery_date) AS earliest_date
FROM `job_sheet` d
WHERE d.delivery_date >= '2013-01-01'
) e
ON j.delivery_date >= e.earliest_date
AND j.delivery_date < DATE_ADD(e.earliest_date, INTERVAL 22 DAY)
AND j.print_status IS NULL
ORDER BY j.delivery_date
(The original query has a predicate on job_sheet.date; the query above references the d.delivery_date... change that if it is supposed to be referencing the date column instaed.)
If the intent is to only show delivery_date values from today forward, then change the literal '2013-01-01' to an expression that returns the current date, e.g. DATE(NOW())

MySQL date finder from today

I have a table of users which has a date field for their birthday:
buddy_auto_id int(11) PK
user_id varchar(128)
buddy_user_id varchar(128)
buddy_name varchar(128)
buddy_bday date
buddyuser_id varchar(20)
active enum('Yes','No')
requestsentby int(11)
whenrequested timestamp
I'm trying to find the 3 users whose birthdays fall soonest compared to todays date and then display the number of days until their birthday ordered by soonest first.
Is this possible within a SQL query or do I have to pull it out and let PHP do the equation?
Many thanks
We first need to calculate the next birthday, then order by that value:
select *,
buddy_bday + interval
if(
month(buddy_bday) < month(now()) or
(month(buddy_bday) = month(now()) and day(buddy_bday) < day(now())),
year(now())+1,
year(now())
) - year(buddy_bday) year as next_bday
from buddies order by next_bday - date(now());
The long if statement figures out whether the buddy already had his/her birthday this year.
This should be possible with SQL. You need to compare the current date with a birthday expressed as from this year or next year, depending on whether we've past it in the current year. Once you have this "next birthday" date, use DATEDIFF function to determine number of days distant from current date.
SELECT *,
DATEDIFF(
# determine date of next birthday by adding age in years to birthday year
DATE_ADD(buddy_bday, INTERVAL
YEAR(CURDATE())-YEAR(buddy_bday)
# add a year if we celebrated birthday already this year
+(MONTH(buddy_bday)<MONTH(CURDATE()) OR (MONTH(buddy_bday)=MONTH(CURDATE()) AND DAY(buddy_bday) < DAY(CURDATE())))
YEAR),
CURDATE())
AS days_to_next_bday
FROM user_table
ORDER BY days_to_next_bday
LIMIT 3;
You should use a DAYOFYEAR function. Try this query -
SELECT buddy_auto_id , buddy_bday FROM table_name
WHERE DAYOFYEAR(buddy_bday) - DAYOFYEAR(NOW()) > 0
ORDER BY DAYOFYEAR(buddy_bday) - DAYOFYEAR(NOW())
LIMIT 3;
So, this query works only for current year.
EDITED query 2:
This one works for all dates.
CREATE TABLE birtdays(
buddy_auto_id INT(11) NOT NULL AUTO_INCREMENT,
buddy_bday DATE DEFAULT NULL,
PRIMARY KEY (buddy_auto_id)
);
INSERT INTO birtdays VALUES
(1, '2011-10-04'),
(2, '2011-03-01'),
(3, '2011-11-29'),
(4, '2011-11-10'),
(5, '2011-12-29'),
(6, '2011-11-30'),
(7, '2011-12-08'),
(8, '2011-09-17'),
(9, '2011-12-01'),
(10, '2011-12-11');
SELECT buddy_auto_id, buddy_bday
FROM
birtdays, (SELECT #day_of_year:=DAYOFYEAR(NOW())) t
ORDER BY
DAYOFYEAR(buddy_bday + INTERVAL YEAR(NOW()) - YEAR(buddy_bday) YEAR) - #day_of_year
+ IF (DAYOFYEAR(buddy_bday + INTERVAL YEAR(NOW()) - YEAR(buddy_bday) YEAR) - #day_of_year > 0, 0, DAYOFYEAR(STR_TO_DATE(CONCAT(YEAR(NOW()), '-12-31'), '%Y-%m-%d')))
LIMIT 3;
+---------------+------------+
| buddy_auto_id | buddy_bday |
+---------------+------------+
| 6 | 2011-11-30 |
| 7 | 2011-12-08 |
| 10 | 2012-02-11 |
+---------------+------------+

How to minimize the load in queries that need grouping with different invervals?

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

Categories