In perl I have the following database query:
my $list = $db->SelectARef("SELECT p.*, u.usr_login, u.usr_money, u.usr_email, u.usr_pay_email, u.usr_pay_type
FROM Payments p, Users u
WHERE status='PENDING'
AND p.usr_id=u.usr_id
ORDER BY u.usr_pay_type");
Within the array result there is a field named "created".
What I want to do is add another element to the array for each row as "next payment".
Payments are processed after 30 full days from the datetime value but only on the 6th day of every month. Basically I want each result to have a "next payment" element stating which day and month they should get paid on.
e.g created = 2013-07-29 18:55:37
30 days from this is the 28th August 2013
Therefore the next payment date would be the 6th September
I have no idea where to start with this, any help anyone can provide would be greatly appreciated!
Thanks
The following shows the logic:
select (date(t) - interval (day(t) - 1) day) + interval 1 month + interval 5 day
from (select cast(now() as datetime) as t) t;
You can put this into your query as:
SELECT p.*, u.usr_login, u.usr_money, u.usr_email, u.usr_pay_email, u.usr_pay_typel,
created - interval (day(created) - 1) day) + interval 1 month + interval 5 day as nextpay
FROM Payments p join
Users u
on p.usr_id=u.usr_id
WHERE status='PENDING'
ORDER BY u.usr_pay_type;
The expression can be simplified to:
created - interval (day(created) + 4) day) + interval 1 month as nextpay
If I understand correctly, you want to compute a next_payment date which is the nearest 6th day of a month that is at least 30 days from the created date. Another way of saying that is that your due date is always the 6th of the month with at least a 30 day grace period from the close date.
Unfortunately, we can't do a simple rounding or stepping here, because the Gregorian calendar isn't quite a regular series. (It quite isn't a regular series?) So, we'll put conditional logic into a query. As this looks just awful, we'll hide it in a function:
DELIMITER //
CREATE FUNCTION next_payment(close_date DATE)
RETURNS DATE
DETERMINISTIC
READS SQL DATA
LANGUAGE SQL
BEGIN
DECLARE min_grace INT DEFAULT 30; -- minimum grace period of 30 days
DECLARE bill_dom INT DEFAULT 6; -- billing day of month is the 6th
DECLARE d DATE;
SET d = close_date + INTERVAL min_grace DAY;
IF DAY(d) > bill_dom THEN -- Did 30 days\' grace put us beyond the 6th?
SET d = d + INTERVAL 1 MONTH; -- If so, advance to next month.
END IF;
RETURN d + INTERVAL(bill_dom - DAY(d)) DAY; -- Now "round" to the 6th of the month
END
//
DELIMITER ;
How you use the logic is up to you. Adding SELECT next_payment(p.created) AS "next_payment", ... will give you the additional column you want. You could parameterize the above (e.g., CREATE FUNCTION next_payment(close_date DATE, min_grace INT, bill_dom INT)) for greater flexibility. You might rip the SET/IF logic out of the function and jam it into one unwieldy CASE/END statement in your SELECT. You could create a VIEW that automatically "appends" a next_payment column to the table, or ALTER TABLE and populate a new column, etc.
Related
I've managed to select the data from the current week but the week itself starts from Sunday which is not the right format for me, it should starts from Monday. I'm using MySQL to query the data.
SELECT IFNULL(SUM(rendeles_dbszam),0) as eladott_pizzak_szama
FROM rendeles
WHERE WEEK(rendeles_idopont) = WEEK(CURRENT_DATE())
'Week' in mysql has 2 inputs: date and week type. By default it's equal 0. That means week starts from sunday. Try this code:
SELECT IFNULL(SUM(rendeles_dbszam),0) as eladott_pizzak_szama FROM rendeles WHERE WEEK(rendeles_idopont) = WEEK(CURRENT_DATE(),1)
You can use this little formula to get the Monday starting the week of any given DATE, DATETIME, or TIMESTAMP object.
FROM_DAYS(TO_DAYS(datestamp) -MOD(TO_DAYS(datestamp) -2, 7))
I like to use it in a stored function named TRUNC_MONDAY(datestamp) defined like this.
DELIMITER $$
DROP FUNCTION IF EXISTS TRUNC_MONDAY$$
CREATE
FUNCTION TRUNC_MONDAY(datestamp DATETIME)
RETURNS DATE DETERMINISTIC NO SQL
COMMENT 'preceding Monday'
RETURN FROM_DAYS(TO_DAYS(datestamp) -MOD(TO_DAYS(datestamp) -2, 7))$$
DELIMITER ;
Then you can do stuff like this
SELECT IFNULL(SUM(rendeles_dbszam),0) as eladott_pizzak_szama
FROM rendeles
WHERE TRUNC_MONDAY(rendeles_idopont) = TRUNC_MONDAY(CURRENT_DATE())
or even this to get a report covering eight previous weeks and the current week.
SELECT SUM(rendeles_dbszam) as eladott_pizzak_szama,
TRUNC_MONDAY(rendeles_idopont) as week_beginning
FROM rendeles
WHERE rendeles_idopont >= TRUNC_MONDAY(CURDATE()) - INTERVAL 8 WEEK
AND rendeles_idopoint < TRUNC_MONDAY(CURDATE()) + INTERVAL 1 WEEK
GROUP BY TRUNC_MONDAY(rendeles_idopont)
I particularly like this TRUNC_MONDAY() approach because it works unambiguously even for calendar weeks that contain New Years' Days.
(If you want TRUNC_SUNDAY() change the -2 in the formula to -1.)
I currently have a query that is getting the records where their deadline is less than 3 months away. The deadline is stored as a Date, but the year is not important as this record should flag up every year.
My query:
SELECT client_name
FROM client
WHERE MOD(DAYOFYEAR(deadline) - DAYOFYEAR(CURDATE()), +365) <= 90
AND DAYOFYEAR(deadline) > DAYOFYEAR(CURDATE())
Apologies if there’s errors in the syntax as I’m writing this from memory – but it does work.
It works up until a deadline is in the first quarter and the current date is in the final quarter then it no longer returns the record. How do I get around this?
So the query needs to return the records that have a deadline within 3 months of the current date. The Year in the deadline date should be ignored as this could be years ago, but it is the day and month of the deadline that is important.
Or is the problem the date I am storing? Should I update this each year?
Thanks
One approach is to use a conditional test like this:
WHERE CONCAT(YEAR(NOW()),DATE_FORMAT(d.deadline,'-%m-%d'))
+ INTERVAL CONCAT(YEAR(NOW()),DATE_FORMAT(d.deadline,'-%m-%d'))<NOW() YEAR
< NOW() + INTERVAL 3 MONTH
We can unpack that a little bit. On that first line, we're creating a "next due" deadline date, by taking the current year, and appending the month and day value from the deadline.
But there's a problem. Some of those "next due" deadline dates are in the past. So, to handle that problem (when the 3 month period "wraps" into the next year), we need to add a year to any "next due" deadline date that's before the current date.
Now, we can compare that to a date 3 months from now, to determine if the "next due" deadline date is in the next 3 months.
That's a bit complicated.
Here's a SQL Fiddle as a demonstration: http://sqlfiddle.com/#!2/c90e9/3.
For testing, NOW() is inconvenient because it always returns today's date. So, for testing, we replace all occurrences of NOW() with a user-defined variable #now, and set that to various dates, so we can appropriately test.
Here's the SQL statement I used for testing. The first expression is the conditional test we're planning on using in the WHERE clause. For testing, we want to return all the rows, and just see which rows get due_in_3mo flagged as TRUE (1) and which get flagged as FALSE (0).
The second expression in the SELECT list just the "next due" deadline date, same as used in the first expression.
The rest of the expressions are pretty self-explanatory... we also want to display the date 3 months in the future we're comparing to, and the original "deadline" date value.
SELECT
CONCAT(YEAR(#now),DATE_FORMAT(d.deadline,'-%m-%d'))
+ INTERVAL CONCAT(YEAR(#now),DATE_FORMAT(d.deadline,'-%m-%d'))<#now YEAR
< #now + INTERVAL 3 MONTH
AS `due_in_3mo`
, CONCAT(YEAR(#now),DATE_FORMAT(d.deadline,'-%m-%d'))
+ INTERVAL CONCAT(YEAR(#now),DATE_FORMAT(d.deadline,'-%m-%d'))<#now YEAR
AS `next_due`
, d.id
, d.deadline + INTERVAL 0 DAY AS `deadline`
, #now + INTERVAL 3 MONTH AS `now+3mo`
, #now + INTERVAL 0 DAY AS `now`
FROM d d
CROSS
JOIN (SELECT #now := '2015-11-01') i
ORDER BY d.id
Change the value assigned to #now in the inline view (aliased as i) to test with other date values.
(You may want to use DATE(NOW()) in place of NOW() so that times don't get mixed in, and you may want to subtract another day from that, that really just depends how you want to handle the edge case of a deadline with month and day the same as the current date. (i.e. do you want to handle that as "in the past" or not.)
To summarize the approach: generate the "next due" deadline date as a DATE value in the future, and compare to the a date 3 months from now.
I'm trying to solve a task: I have a table containing information about ships' battles. Battle is made of name and date. The problem is to get the last friday of the month when the battle occurred.
WITH num(n) AS(
SELECT 0
UNION ALL
SELECT n+1 FROM num
WHERE n < 31),
dat AS (
SELECT DATEADD(dd, n, CAST(battles.date AS DATE)) AS day,
dateadd(dd, 0, cast(battles.date as date)) as fight,
name FROM num,battles)
SELECT name, fight, max(day) FROM dat WHERE DATENAME(dw, day) = 'friday'
I thought there must be a maximum of date or something, but my code is wrong.
The result should look like this:
Please, help!!
P.S. DATE_FORMAT is not available
Possible problem: as spencer7593 noticed - and as I should have done and didn't - your original query is not MySQL at all. If you're porting a query that's OK. Otherwise this answer will not be helpful, as it makes use of MySQL functions.
The day you want is number 4 (0 being Sunday in MySQL).
So you want the last day of the month if the last day of the month is a 4; if the day of the month is a 5 you want a date which is 1 day earlier; if the day of the month is a 3 you want a date which is 1 day later, but that's impossible (the month ends), so you really need a date six days earlier.
This means that if the daynumber difference is negative, you want it modulo seven.
You can then build this expression (#DATE is your date; I use a fake date for testing)
SET #DATE='2015-02-18';
DATE_SUB(LAST_DAY(#DATE), INTERVAL ((WEEKDAY(LAST_DAY(#DATE))+7-4))%7 DAY);
It takes the last day of the month (LASTDAY(#DATE)), then it computes its weekday, getting a number from 0 to 6. Adds seven to ensure positivity after subtracting; then subtract the desired daynumber, in this case 4 for Friday.
The result, modulo seven, is the difference (always positive) from the last day's daynumber to the wanted daynumber. Since DATE_SUB(date, 0) returns the argument date, we needn't use IF.
SET #DATE='1962-10-20';
SELECT DATE_SUB(LAST_DAY(#DATE), INTERVAL ((WEEKDAY(LAST_DAY(#DATE))+7-4))%7 DAY) AS friday;
+------------+
| friday |
+------------+
| 1962-10-26 |
+------------+
Your query then would become something like:
SELECT `name`, `date`,
DATE_SUB(LAST_DAY(`date`),
INTERVAL ((WEEKDAY(LAST_DAY(`date`))+7-4))%7 DAY) AS friday
FROM battles;
Pseudo code of what I'd like to do:
SELECT * FROM table WHERE NTH_DAY(DATE(table_date)) = NTH_DAY($input_date);
I want to determine what the nth weekday of the month is for a given date. For example, if given the input "2013-08-30" it should return 5, since it is the fifth occurrence of that weekday (Friday) in the month.
I've been reading through countless similar questions but the majority are looking for just the opposite, for example they want to find the date of the fifth Friday, whereas I want to determine what the nth number of a date is. Other questions appear to be what I'm after but they're in different programming languages I don't understand.
Can someone please explain if this is possible in a MySQL query and how to do it?
To find which "nth" a given day is is rather easy:
select (case when day(table_date) between 1 and 7 then 1
when day(table_date) between 8 and 14 then 2
when day(table_date) between 15 and 21 then 3
when day(table_date) between 22 and 28 then 4
else 5
end) as NthDay
You can also do this using "remainder" arithmetic:
select 1 + floor((day(table_date) - 1) / 7) ) as NthDay
I'm going to throw my two cents in here with a third option that gets it based on finding the 1st of each day, and then adding multiples of 7 to get to the final result.
Function
CREATE FUNCTION `ISNTHDAYINMONTH`(checkDate DATE, weekNumber INTEGER, dayOfWeek INTEGER, monthOfYear INTEGER) RETURNS INTEGER
NO SQL
DETERMINISTIC
BEGIN
DECLARE firstOfMonth DATE;
SET firstOfMonth = DATE_SUB(checkDate, INTERVAL DAYOFMONTH(checkDate) - 1 DAY); #Find the first day of the current month
IF DAYOFWEEK(checkDate) = dayOfWeek AND MONTH(checkDate) = monthOfYear #Make sure at least this matches
AND DAYOFMONTH(checkDate) = ((1 + (7 + dayOfWeek - DAYOFWEEK(firstOfMonth)) % 7) + 7 * (weekNumber - 1)) THEN #When the date matches the nth dayOfWeek day of the month
RETURN 1;
ELSE
RETURN 0; #Nope
END IF;
END
Use
SELECT ISNTHDAYINMONTH(datecol, weekNumber, dayOfWeek, monthOfYear);
Where weekNumber is 1 through 5, dayOfWeek is 1 through 7 (1 = Sun), and monthOfYear is 1 through 12.
Example
To check if datecol is the second Tuesday of April:
SELECT ISNTHDAYINMONTH(datecol, 2, 3, 4);
Calculations
Let's break it down. This line gets the first day of the month for the date that is passed in.
SET firstOfMonth = DATE_SUB(checkDate, INTERVAL DAYOFMONTH(checkDate) - 1 DAY); #Find the first day of the current month
This check ensures that the date has the correct day of the week, and correct month (just in case the user passed in a date that isn't even the right day of the week).
DAYOFWEEK(checkDate) = dayOfWeek AND MONTH(checkDate) = monthOfYear
Now for the big one:
DAYOFMONTH(checkDate) = ((1 + (7 + dayOfWeek - DAYOFWEEK(firstOfMonth)) % 7) + 7 * (weekNumber - 1))
To get the amount we need to add to 1 to get the date, we calculate dayOfWeek that we are looking at, minus the day of the week that the first day of the month falls on to get the offset mod 7 ((dayOfWeek - DayOfWeek(first)) % 7).
Note that because the first of the month could land on a day before or on the day we are looking at, we add an additional 7 and then mod by seven. This is needed because MySQL's Mod function does not properly compute mod. I.e. -1 % 7 should be 6, but MySQL returns -1. Adding 7 and taking the modulus ensures the result will always be correct.
To summarize:
NumberToAddToOneToGetDateOfFirstWeekDay = (7 + DayOfWeek - DayOfWeek(firstDayOfMonth)) %
Then we add one and how every many multiples of 7 are needed to get to the correct week.
select ceiling(DATEPART(day, GETDATE())/7.0)
This will always give you nth occurrence number of current day in a month.
For example, if the current day is Friday, and it occurred 3rd time this month. It will return 3.
If you replace GETDATE() with your Date variable or column name it will return the occurrence of the day on that date in the corresponding month.
This would give the number of the week (which is actually the same as if it is the 5th friday or 5th Monday)
SELECT WEEK(dateasd,5) -
WEEK(DATE_SUB(dateasd, INTERVAL DAYOFMONTH(dateasd)-1 DAY),5)
from test
Use Weekday() function to find which day it is
Fiddle at
http://www.sqlfiddle.com/#!2/7a8b6/2/0
I am trying to write a function in mySQL that takes two dates(startDate and endDate) as parameters. It then calculates the days in each month.
The database contains a targetRevenue table that has got the target revenue values for each month and year.
id month year targetRev
25 1 2012 1000.00
26 2 2012 5000.00
27 3 2012 8000.00
The function finds the revenue for a month based on the number of days in it and then returns the total.
Example : startDate : 2012-01-19 endDate : 2012-03-24
Function returns [ targetRev(19 days in Jan) + targetRev(29 days Feb) + targetRev(24days in March)]
I am new to writing functions in mysql , so a little bit of help to get me started would be very useful. Thanks in advance!
If instead of your month and year columns, you represented the month of each record in your targetRevenue table by a DATE column containing the first day of each month:
ALTER TABLE targetRevenue
ADD COLUMN first DATE;
UPDATE targetRevenue
SET first = STR_TO_DATE(CONCAT_WS('-', year, month, 1), '%Y-%c-%e');
ALTER TABLE targetRevenue
DROP COLUMN year,
DROP COLUMN month;
You could then obtain the total target revenue for your project (assuming it is inclusive of both start and end date) with:
-- calculate the summation of
SELECT SUM(CONVERT(
-- number of project days in month...
GREATEST(0,
-- ...is calculated as the difference between...
DATEDIFF(
-- ...the last day of the project in this month...
LEAST('2012-03-24', LAST_DAY(first)),
-- ...and the first day of the project in this month...
GREATEST('2012-01-19', first)
)
-- ...plus one because first and last project days were inclusive
+ 1
)
-- multiply by the target revenue for this month
* targetRev
-- divide by the number of days in the month
/ DAY(LAST_DAY(first)),
-- convert result to fixed-point format, to two d.p.
DECIMAL(11,2)
)) AS total
FROM targetRevenue
-- only perform for months in which the project was active
WHERE '2012-01-19' <= LAST_DAY(first) AND first <= '2012-03-24'
See it on sqlfiddle.
If you can't change the schema, you could replace references to first with the value to which that column was updated above.
For this you can use SUM() function like:
SELECT SUM(targetRev) from your_table
WHERE date_column BETWEEN your_startDate_column AND your_endDate_column;
you need not to calculate days of each month..
Use this query like this
SELECT SUM(targetRev), MONTH(date_column) as mo
from your_table
WHERE date_column BETWEEN your_startDate AND your_endDate
GROUP BY mo;
This will give the result for each month total revenue (use like this logic)
If it is two different years you can use like
concat(year(date_column),month(date_column)) as mo