SQL "for" in select: smarter way? - mysql

I need to know if there is an effective way in (My)SQL to do a select statement that resembles a for-loop in most programming languages. The simplest example would be to create a column with the values of 1 to 100. I have searched for solutions around (the one I found most interesting was one with cross joining each digit up to target length). In the end, I created a table named N (as in the maths symbol) in my db witha single column 'n' and values from 1 to 1 million.
Here's an example I've needed this:
select .....
cross join
(
select n,str_to_date(period_add(date_format(CURDATE(),'%Y%m'),n),'%Y%m') as 'date' from n where n<period_diff(201912,date_format(CURDATE(),'%Y%m'))
)
This returns a column with all the year/month combinations from curdate (happens to be march 2017) to Dec 2019. You can see the n used as a "variable" to add months and to calculate the "for loop's end condition". This works well. The question then is: is there a more elegant way to do this?

Your method is fine. I might be inclined to do:
select date_add(date_add(curdate(), interval 1 - day(curdate()) day), interval n.n month)
from n
where date_add(date_add(curdate(), interval 1 - day(curdate()) day), interval n.n month) <= '2019-12-01'
I prefer doing date arithmetic rather than going back and forth to strings.
The expression:
date_add(curdate(), interval 1 - day(curdate()) day)
is just a way of getting the first day of the month.

Related

Select records between a date in d-mY format and 1rst of next month, plus 1 day in Mysql

I need to select all data from a table 'followup' between followup_date and first of next month (including 1rst of next month). The format of my date in DB (obtained from an API) is d-m-Y.
The follow-up date for example is: 18-07-2020.
I have the following query:
SELECT * from followup
WHERE DATEDIFF(STR_TO_DATE(`followup_date`,'%d-%m-%Y'), DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH,'%Y-%m-01'))- 1 < 0 ;
I am getting -15 days as difference and getting records correctly, including 1rst. Is the query correct and efficient and will it work correctly for all months.
Requesting suggestions from experts for improvements, if any.
You should convert your followup_date column to a DATE type. You can then make your query sargable by removing the function calls on followup_date and simply comparing it with the target date:
SELECT * from followup
WHERE `followup_date` <= DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH,'%Y-%m-01')
I suspect adding one day to LAST_DAY(CURDATE()) might be more efficient:
SELECT * from followup
WHERE `followup_date` <= LAST_DAY(CURDATE()) + INTERVAL 1 DAY
Demo on SQLFiddle

DATE_FORMAT two times in Where Clause doesn't work

I want to check if a dataset is older than the current month -1 day (so if it's the first of November it should still be older than October). This is my SQL:
SELECT *
FROM XY
WHERE DATE_FORMAT(calendar_day, '%Y-%m') <> DATE_FORMAT((CURRENT_DATE()-1, '%Y-%m');
But it doesn't work because of the second DATE_FORMAT. If I remove it, it works, but then it also compares the days and not the months. How do I solve this?
I want to check if a dataset is older than the current month -1
Don't use DATE_FORMAT() on a column for this type of query. Keep all date functions on the "current date". Functions on columns impede optimization.
I think this does what you want:
SELECT *
FROM XY
WHERE calendar_day <= LAST_DAY(CURRENT_DATE() - interval 1 day - interval 1 month);
Try this using year and month function:
SELECT *
FROM XY
WHERE (year(calendar_day) <> year(CURRENT_DATE()-1))
and (month(calendar_day)<>month(CURRENT_DATE()-1))

Select by -3 days?

I'm trying to select data from a specific table in my database, but I want to be able to only view the last 3 days worth of data, I have the following code but for some reason I can't get it to work.
SELECT * FROM demands WHERE t.date >= DATE_ADD(CURDATE(), INTERVAL -3 DAY)
You may avoid usage of DATE_ADD() at all:
SELECT * FROM demands as t WHERE t.date >= (CURDATE() - INTERVAL 3 DAY)
As #OGHaza mentioned, you specified column with alias to nowhere: t.date should be just date (note that it is a reserved word, so you should use backticks around it in this case) or demands should be specified with an alias like demands as t.

Select query for two weeks ago

In my database table I have a field for date (varchar field to save date in yy-mm-dd format ), now I want to select records for two weeks ago.
How can i do it ?
Implicit date arithmetic is fairly flexible in MySQL. You can compare dates as strings without explicit use of CAST() or DATE(), but you're trusting MySQL to interpret your format correctly. Luckily for you, it will do just fine with yy-mm-dd.
I would recommend using BETWEEN and INTERVAL so that your query is easily readable; for example:
SELECT * FROM Holidays
WHERE Date BETWEEN (NOW() - INTERVAL 14 DAY) AND NOW();
The only trick with BETWEEN is that you have to put the lower bound first and the upper bound second; for example, if you write BETWEEN 5 AND 2, this always evaluates to FALSE because there is no value that can be greater than or equal to 5 while also being less than or equal to 2.
Here's a demo of the query in action at SQL Fiddle, and a list of the recognized INTERVAL expressions in MySQL.
Note that the parentheses around the expression NOW() - INTERVAL 14 DAY are not required but I would recommend using them here purely for the sake of clarity. It makes the predicate clause just a little bit easier to read in the absence of proper syntax highlighting, at the expense of two characters.
Ideally you should be using date types to store dates, but being that's not the case, you should look into casting to date then comparing.
select * from yourtable where cast (yourdate as Date) BETWEEN Date_Add(CURDATE(), INTERVAL -21 Day) and Date_Add(CURDATE(), INTERVAL -14 Day)
Note, this is untested and may need a little tweaking, but should give you a general idea of what you need to do.
Also, if it's possible, you should really look into converting the varchar field to a date field....they have date types to prevent this sort of thing from happening, although i know changing field types isn't always a possibility.
you can simply do with ADDDATE to get 14 days ago. compare string with date will work.
SELECT *
FROM your_table
WHERE your_date >= ADDDATE(NOW(), -14) AND your_date < NOW()
I use this for select data in past of past
SELECT * FROM Holidays
WHERE a.dDate >= DATE( NOW( ) ) - INTERVAL 14
DAY AND a.dDate <= DATE( NOW( ) ) - INTERVAL 8

How to get first day of every corresponding month in mysql?

I want to get first day of every corresponding month of current year. For example, if user selects '2010-06-15', query demands to run from '2010-06-01' instead of '2010-06-15'.
Please help me how to calculate first day from selected date. Currently, I am trying to get desirable using following mysql select query:
Select
DAYOFMONTH(hrm_attendanceregister.Date) >=
DAYOFMONTH(
DATE_SUB('2010-07-17', INTERVAL - DAYOFMONTH('2010-07-17') + 1 DAY
)
FROM
hrm_attendanceregister;
Thanks
Is this what you are looking for:
select CAST(DATE_FORMAT(NOW() ,'%Y-%m-01') as DATE);
You can use the LAST_DAY function provided by MySQL to retrieve the last day of any month, that's easy:
SELECT LAST_DAY('2010-06-15');
Will return:
2010-06-30
Unfortunately, MySQL does not provide any FIRST_DAY function to retrieve the first day of a month (not sure why). But given the last day, you can add a day and subtract a month to get the first day. Thus you can define a custom function:
DELIMITER ;;
CREATE FUNCTION FIRST_DAY(day DATE)
RETURNS DATE DETERMINISTIC
BEGIN
RETURN ADDDATE(LAST_DAY(SUBDATE(day, INTERVAL 1 MONTH)), 1);
END;;
DELIMITER ;
That way:
SELECT FIRST_DAY('2010-06-15');
Will return:
2010-06-01
There is actually a straightforward solution since the first day of the month is simply today - (day_of_month_in_today - 1):
select now() - interval (day(now())-1) day
Contrast that with the other methods which are extremely roundabout and indirect.
Also, since we are not interested in the time component, curdate() is a better (and faster) function than now(). We can also take advantage of subdate()'s 2-arity overload since that is more performant than using interval. So a better solution is:
select subdate(curdate(), (day(curdate())-1))
This is old but this might be helpful for new human web crawlers XD
For the first day of the current month you can use:
SELECT LAST_DAY(NOW() - INTERVAL 1 MONTH) + INTERVAL 1 DAY;
You can use EXTRACT to get the date parts you want:
EXTRACT( YEAR_MONTH FROM DATE('2011-09-28') )
-- 201109
This works well for grouping.
You can use DATE_FORMAT() function in order to get the first day of any date field.
SELECT DATE_FORMAT(CURDATE(),'%Y-%m-01') as FIRST_DAY_CURRENT_MONTH
FROM dual;
Change Curdate() with any other Date field like:
SELECT DATE_FORMAT(purchase_date,'%Y-%m-01') AS FIRST_DAY_SALES_MONTH
FROM Company.Sales;
Then, using your own question:
SELECT *
FROM
hrm_attendanceregister
WHERE
hrm_attendanceregister.Date) >=
DATE_FORMAT(CURDATE(),'%Y-%m-01')
You can change CURDATE() with any other given date.
There are many ways to calculate the first day of a month, and the following are the performance in my computer (you may try this on your own computer)
And the winner is LAST_DAY(#D - interval 1 month) + interval 1 day
set #D=curdate();
select BENCHMARK(100000000, subdate(#D, (day(#D)-1))); -- 33 seconds
SELECT BENCHMARK(100000000, #D - INTERVAL (day(#D) - 1) DAY); -- 33 seconds
SELECT BENCHMARK(100000000, cast(DATE_FORMAT(#D, '%Y-%m-01') as date)); -- 29 seconds
SELECT BENCHMARK(100000000, LAST_DAY(#D - interval 1 month) + interval 1 day); -- 26 seconds
I'm surprised no one has proposed something akin to this (I do not know how performant it is):
CONCAT_WS('-', YEAR(CURDATE()), MONTH(CURDATE()), '1')
Additional date operations could be performed to remove formatting, if necessary
use date_format method and check just month & year
select * from table_name where date_format(date_column, "%Y-%m")="2010-06"
SELECT LAST_DAY(date) as last_date, DATE_FORMAT(date,'%Y-%m-01') AS fisrt_date FROM table_name
date=your column name
The solutions that use last_day() and then add/subtract a month and a day are not interchangeable.
Example:
date_sub(date_add(last_day(curdate()), interval 1 day), interval 3 month)
always works for any supplied number of months you want to go back
date_add(date_sub(last_day(now()), interval 3 month), interval 1 day)
will fail in some cases, for instance if your current month has 30 days and the month you're subtracting back to (and then adding a day) has 31.
date_add(subdate(curdate(), interval day(?) day), interval 1 day)
change the ? for the corresponding date
This works fine for me.
date(SUBDATE("Added Time", INTERVAL (day("Added Time") -1) day))
** replace "Added Time" with column name
Use Cases:
If you want to reset all date fields except Month and Year.
If you want to retain the column format as "date". (not as "text" or "number")
Slow (17s):
SELECT BENCHMARK(100000000, current_date - INTERVAL (day(current_date) - 1) DAY);
SELECT BENCHMARK(100000000, cast(DATE_FORMAT(current_date, '%Y-%m-01') as date));
If you don't need a date type this is faster:
Fast (6s):
SELECT BENCHMARK(100000000, DATE_FORMAT(CURDATE(), '%Y-%m-01'));
SELECT BENCHMARK(100000000, DATE_FORMAT(current_date, '%Y-%m-01'));
select big.* from
(select #date := '2010-06-15')var
straight_join
(select * from your_table where date_column >= concat(year(#date),'-',month(#date),'-01'))big;
This will not create a full table scan.