SQL convert string 3M to 3 month, 1Y to 1 year - php

I probably made a poor decision years ago where I have been storing intervals in a char(3) column with values such as "1M" or "3M" or "1Y". This helps me store info about recurring invoices. "1M" means the invoice renews every 1 month.
Here is a sample for the database : https://i.imgur.com/D8oKaV3.png
The reason of this poor design is because I calculate the next invoice date through a php function :
function increment_date($date, $increment)
{
$new_date = new DateTime($date);
$new_date->add(new DateInterval('P' . $increment));
return $new_date->format('Y-m-d');
}
so that I can pass it arguments such as "P1M" which was actually very convenient for DateInterval
I now wish I stored them such as "1 month" instead of "1M", because I am stuck when try to run the following dynamic SQL request :
SELECT SUM(invoice_total) prevision_for_current_month
FROM lf_invoice_amounts a
JOIN lf_invoices_recurring r
ON r.invoice_id a.invoice_id
WHERE (month(recur_next_date) = 5 and year(recur_next_date)= 2020)
OR (month(recur_next_date - INTERVAL recur_frequency) = 5 and year(recur_next_date - INTERVAL recur_frequency) = 2020)
The part with month(recur_next_date - INTERVAL recur_frequency) fails and throws an error because it runs such as month(recur_next_date - INTERVAL 1M) which mySQL does not understand, while the following would have been correct : month(recur_next_date - INTERVAL 1 month)
The purpose of this sql request is to estimate all the money that should come in during current month, from invoices that are recurring every month/3 months/year/2 weeks/etc
I cannot refactor the whole code base at this moment. What would be a possible workaround?
TL;DR : How do I transform a column that contains value "1M" into "1 month" so that I can run SQL requests with intervals. (same for "3M" or "1Y" or "1M" or "2W" or "30D" or "31D", etc).
Ugly solutions also welcome. I'm currently think about a big nest of replace() maybe like month(recur_next_date - INTERVAL replace(recur_frequency, 'M', ' month')) ?

Unfortunately converting your recur_frequency into a string like 1 month isn't going to help, as the syntax for intervals requires that unit be one of YEAR, MONTH, DAY etc. i.e. one of a fixed set of specifiers, not a string, so trying to use something like INTERVAL recur_frequency or even INTERVAL recur_frequency recur_period won't work. Without using dynamic SQL to insert the value of recur_frequency into the query I think your best option is probably to store the recurrence frequency into one of 4 columns (e.g. recur_year, recur_month, recur_week, recur_day), then you can use a query such as
curdate() - interval recur_year year
- interval recur_month month
- interval recur_week week
- interval recur_day day
Demo on dbfiddle

Related

Request to find out the date in 2 days

How to write a sql query to find out that there are 2 days left before the current date.
In php, this can be done via:
$res['end_date'] - time () < 86400 * 3;
How can I do the same after 1 sql query, well or better, only 2 days, if less so that it does not work out, well, if it works out, it's okay.
UPD:
It is necessary to compose a sql query that will select only those records that have 2 days left before the end_date expires
The type is int for the field end_date and is stored via the time () function in php.
Can't compose a WHERE clause.
You can use the FROM_UNIXTIME function to convert it to a DateTime you can then use the NOW() plus 2 days to check if the date is under 2 days. You then have to check that the date is before the current time otherwise you'll get dates that have already gone.
SELECT
end_date
FROM
table
WHERE
FROM_UNIXTIME(end_date) <= NOW() + INTERVAL 2 DAY
AND
FROM_UNIXTIME(end_date) > NOW()
Assuming that you are storing an epoch timestamp (the number of seconds since January 1st, 1970), I would recommend:
select *
from mytable
where end_date >= unix_timestamp() and end_date < unix_timestamp() + 2 * 24 * 60 * 60
unix_timestamp() gives you the current epoch. You can use simple math to add two days to that.
The upside of this approach is that this does direct filtering against the store value, so this can take advantagae of an index on end_date - as opposed to converting the timestamp to a date, which requires converting the whole column before the filtering can happen. So this is much more efficient.
You can ajust the inequalities as you prefer. I used a half-open interval (inclusive on the lower bound and exclusive on the upper bound), which is a widely used approach.
I ended up doing this:
$time = time();
$params = $db->query("SELECT * FROM `params` WHERE (`end_date` - {$time}) < 86400 * 3");
And it worked.
I always do
select *
from mytable
where FROM_UNIXTIME(end_date) < NOW() + INTERVAL 2 DAY
This will get results where two days in the future is ahead of the end date ie, anything that will end within 2 days (or has already ended as I didn't add a check for that)
Edit: I see you can't use where
If you cannot use where clause
select FROM_UNIXTIME(end_date) - INTERVAL 2 DAY as end_date
from mytable
And then check in php if the result is before or after. This will show all results however

Generate items based on previous values that are periodic in MySQL

I have a table of reports with their dates, which are periodic, meaning that there should be a report every 6 months. I have a calendar in my application that I use to display dates for reports that have not yet been done, as a means of planning into the future. Currently, I have managed to display reports that have not yet been done for a six month interval. For example, if I had a report on 01.01.2017, the calendar will correctly show that a new report is needed on 01.07.2017 when passed a 30 day interval that contains this date. This is the query I use for this:
SELECT a.komitent_id,
DATE_FORMAT(MAX(str_to_date(a.report_date, '%d.%m.%Y')), "%d.%m.%Y"),
DATE_FORMAT(MAX(DATE_ADD(str_to_date(a.report_date, '%d.%m.%Y'), INTERVAL 6 MONTH)), "%d.%m.%Y") 'datum_isteka'
FROM izvjestaji_aparata a
WHERE str_to_date(a.report_date, '%d.%m.%Y') < str_to_date(:start, '%d.%m.%Y')
AND DATE_ADD(str_to_date(a.report_date, '%d.%m.%Y'), INTERVAL 6 MONTH)
BETWEEN str_to_date(:start, '%d.%m.%Y') AND str_to_date(:end, '%d.%m.%Y')
GROUP BY a.komitent_id;
Start and end params are passed from PHP and reprsent a monthly interval(from FullCalendar)
But now I want to show reports for a range of 01.01.2018 to 31.01.2018. For example, if I had a report with date between that 1, 1.5, 2, 2.5, etc. years ago or(6, 12, etc.) months ago I should show it in this calendar.
I have been thinking of trying to do something with TIMESTAMPDIFF and MOD, 6 = 0, but I cannot use it for dynamic parameters(range).
Is there any way I can achieve this with a query, or I have to employ PHP logic to do this?
EDIT:
To rephrase, the problem is comparing dates to all dates in passed range :start to :end. I need every date from this range whose difference with a.report_date in months is divisible by 6.

Conditional Select with mysql and PHP for a calendar app

Im currently programming a calendar with php and mysql and im stuck with some functionality while selecting all events i want to display. I implemented the possibilty to repeat an event. Therefore i set a timestamp from which the event starts and a timestamp to determine when the event ends. Furthermore i got some integer values which represent the rythm in which the event is repeated.
Then i fetch the events based on a timestamp which is send with a request.
I now want to enable the user to shift events from the weekend to the Friday before the weekend or the monday after the weekend. For example:
From: 1450306800 (today)
until: 0 (infinite)
rythm : 1 (-> every month)
jump:2 (-> on every 2nd day / month)
weekends : 3 (-> shift to next monday)
-> January 2nd 2016 is a saturday and i want to display that event on the next monday.
currently my select looks something like this: (:day -> timestamp from request, :d -> day of month from :day, :weekday -> day of the week from :day)
SELECT * FROM events
WHERE repeat_from <= :day
AND ((repeat_until >= :day) OR (repeat_until = 0))
AND CASE weekends
WHEN 0 THEN (:weekday BETWEEN 1 AND 7)
WHEN 1 THEN (:weekday < 6)
WHEN 2 THEN ??
WHEN 3 THEN ??
AND CASE rythm
WHEN 1 THEN (:d - DAY(FROM_UNIXTIME(repeat_from))) / (jump + 1) = CEIL ((:d - DAY(FROM_UNIXTIME(repeat_from))) / (jump + 1)) ... [all the other cases]
How do i check if the event would have been displayed on saturday or sunday before/after within the select? The only way i can think of is to more or less repeat the whole select from the "...AND CASE rythm..." part which is quite alot.
Or would the best way be, to fetch the event on every monday/friday anyway if it shifts and then check with a php function if the event would have been displayed on saturday or sunday ?
Why don't you do all the calculations and matching on the PHP back-end? With that much cases and clauses in your MySQL query you'll probably get yourself a really slow execution.
Select all Events by your major criteria. In your case that would probably be:
SELECT * FROM
events
WHERE
repeat_from <= :day AND
(
(repeat_until >= :day) OR
(repeat_until = 0)
)
Then just loop through all fetched rows and apply the built-in date() method - much faster and flexible:
# you can match the week number of different dates
$currentWeekNumber = date('W');
$specificDayWeekNumber = date('W', strtotime($specificDate));
# or match the day number
$currentDayNumber = date('N');
$specificDayNumber = date('N', strtotime($specificDate));
The date() method is super flexible and will allow you to check if certain Event date is within the weekend or not, so you can manipulate it further.
If you provide some further explanations and/or examples, I'll try to be more specific with the solution. Cheers.

Query to return results WHERE the datetime is under X months

I am working on a JSON API, and one of the input parameters is to specify an integer input defined like this:
1 - would return results within one month from today
2 - would return results within three months from today
3 - would return results within six months from today
4 - would return results within one year from today
So in essence the API would be used like this:
http://url.com/index.php?route=api/order/all&search_time=1, would return all results from table X within one month from today.
My table has the datetime field, so the date issue is not the problem. I know where is a MySQL function that you can retrieve the month like MONTH(datetime_item), so you can use it like WHERE MONTH(datetime_item) = 8, but this does not seem relevant to my usage.
So in MySQL pseudo-code, the query would look like this:
WHERE datetime is within three months ago from today
Try this,
SELECT * from table_name where datetime_item >= now()-interval 'x' month
Date arithmetic also can be performed using INTERVAL together with the + or - operator:
date + INTERVAL expr unit
date - INTERVAL expr unit
Reference - documentation for date/time functionsDocumentation for date/time functions

Best method for making a MySQL query dynamic (e.g., update query parameters to show Previous Week)

I have a query which calculates the total hours worked for the current week by task:
SELECT name AS 'Task Name'
, sum(hours) AS `Total Hours`
FROM time_records
WHERE yearweek(record_date, 3) = yearweek(now(), 3)
GROUP BY
weekday(record_date), name
The WHERE clause specifies the current week:
WHERE yearweek(record_date, 3) = yearweek(now(), 3)
This data is displayed as a table using the MySQL query and a PDO statement.
I would now like to add an option for the viewer to see the previous week of data by clicking a link under the table (e.g., "Previous Week"). In order to show that data, I would need to update the WHERE clause to query for the previous week:
WHERE yearweek(record_date, 3) = yearweek(now(), 3) - 1
I'm wondering what the best way to do this is? I would prefer to keep just one version of the overall query. One thought is to use PHP to add the "- 1" to the query, such as:
WHERE yearweek(record_date, 3) = yearweek(now(), 3) <?php if (prev) {echo "- 1" ;} ?>
I think this should work, but how can I determine the prev condition? Should I make the entire query be inside a function so that for the default it can be "Time_hours(0)" and for the previous it can be "Time_hours(1)" - or is there some better way?
I'm concerned about keeping the code manageable and following best practices. Any tips would be appreciated. Thanks!
I suggest you try a query that has a parameter for the number of weeks ago you want to process. YEARWEEK is a bad choice for weekly processing because it handles end-of-year rollovers poorly.
Try this, if your weeks begin on Sunday. This will give the current week.
select r.record_id, r.record_date, w.week weekstart
from record r
join (
select
(FROM_DAYS(TO_DAYS(CURDATE()) -MOD(TO_DAYS(CURDATE()) -1, 7))
- interval 0 week) week
)w
where record_date >= w.week
and record_date < w.week + interval 1 week
Fiddle: http://sqlfiddle.com/#!2/4c487/1/0
If you want, let's say, one week ago, change - interval 0 week to - interval 1 week in your query. See this fiddle. http://sqlfiddle.com/#!2/4c487/2/0
You can put an an arbitrary number of weeks ago, even weeks that span year ends. See here for interval -42 week for example. http://sqlfiddle.com/#!2/4c487/3/0
The expression FROM_DAYS(TO_DAYS(CURDATE()) -MOD(TO_DAYS(CURDATE()) -1, 7)) rounds the current date down to the previous Sunday. If you want to round it to the previous Monday, use FROM_DAYS(TO_DAYS(CURDATE()) -MOD(TO_DAYS(CURDATE()) -2, 7)). For example: http://sqlfiddle.com/#!2/4c487/8/0

Categories