This question already has answers here:
generate_series() equivalent in MySQL
(4 answers)
Closed 5 years ago.
Can anyone fluent in both MySQL and PostgreSQL translate this to MySQL?
SELECT *
FROM generate_series(DATE_TRUNC('day', NOW() - interval '30 day'),
DATE_TRUNC('day', NOW()),
interval '1 day'
)
I know that generate_series() does not exist in MySQL. Is there a similar function?
There is no equivalent. MySQL doesn't have CTEs, generate_series(), or even window functions.
If you have a table that is big enough (in this case 31 rows), you can do:
select (curdate() - interval (#rn := #rn + 1) day) as dte
from t cross join
(select #rn := -1)
limit 31;
Related
I want to add month in transaction date using mysql interval function by join plan table and transaction table,however this method not working but If I add months in static way to transaction date it is working.
plan table:
plan_id plan
1 6 month
2 12 month
3 3 month
transaction table:
id user_id subscribed_on plan_id
1 2 2020-04-04 1
2 4 2019-02-22 2
Mysql query (not working):
SELECT t.* FROM transaction t inner join plan p on p.plan_id=t.plan_id
where t.user_id=2 and DATE_ADD(date(t.subscribed_on), INTERVAL p.plan) >= CURDATE()
order by t.id desc
If I add month in static way than it is working fine:
SELECT t.* FROM transaction t inner join plan p on p.plan_id=t.plan_id
where t.user_id=2 and DATE_ADD(date(t.subscribed_on),
INTERVAL 6 month) >= CURDATE()
order by t.id desc
MySQL does not support using interval that way. Unlike in other databaes (such as Postgres for example), the unit argument is a keyword, not a literal string.
I would suspect that your table may store other intervals than just months (say, years, days, and so on). If so, you can use string functions and a case expression to accommodate the different possible values, like:
select t.*
from transaction t
inner join plan p on p.plan_id = t.plan_id
where
t.user_id = 2
and date(t.subscribed_on) + case substring_index(p.plan, ' ', -1)
when 'year' then interval substring_index(p.plan, ' ', 1) year
when 'month' then interval substring_index(p.plan, ' ', 1) month
when 'day' then interval substring_index(p.plan, ' ', 1) day
end
>= current_date
order by t.id desc
The logic here is to split the stored interval string into two parts: the number, and the unit; the case expression processes the unit and generate the proper literal interval accordingly.
Unfortunately a string in the data is not equivalent to an interval. One method is:
date(t.subscribed_on) + interval substring_index(plan, ' ') + 0 month
Note here that month is a keyword, not a string.
Try to force the plan column in the plan table to be an integer. Does not seem to be possible to cast a string to an interval.
I tried like so:
WITH
plan( plan_id,plan) AS (
SELECT 1,'6 month'
UNION ALL SELECT 2,'12 month'
UNION ALL SELECT 3,'3 month'
)
,
transaction(id,user_id,subscribed_on,plan_id) AS (
SELECT 1,2,DATE '2020-09-04',1
UNION ALL SELECT 2,4,DATE '2019-02-22',2
)
SELECT t.*
FROM transaction t
INNER JOIN plan p ON p.plan_id = t.plan_id
WHERE t.user_id = 2
AND DATE_ADD(
DATE(t.subscribed_on)
, INTERVAL CAST(REPLACE(plan,' month','') AS SIGNED) MONTH
) >= CURDATE()
ORDER BY t.id DESC
(returns no results, as you don't have any dates high enough in your example data...)
I want to show date range of week by passing a variable which contains year and month i.e 2016-07 like this in mysql how could i achieve this:
for eg: if i pass 2016-07 then my output should look like this:
2016-07-03 to 2016-07-10 || 2016-07-11 to 2016-07-17
and so on
There are several parts to solving this problem, when you go to solve it in MySQL.
One is to decide whether your weeks start on Sundays or Mondays. That's a locale-specific business rule. In locales formerly part of the British Empire (USA, India) it's usually Sunday. In other places, it's Monday.
So we'll need a function like this: firstDayOfWeek(date). More about that in a moment.
Once we have that, we'll need a way to get the last day of the month in question. That's easy; it's a built-in MySQL function. LAST_DAY(date)
You have said you'll specify the month in question with a string like 1941-12. We'll need to turn that into a DATE object. That can be done like this:
STR_TO_DATE(CONCAT(`1941-12`, `-01'), '%Y-%m-%d')
We need a virtual table with the integers from 0 to 4. Let's call that table seq_0_to_4. We pick that range of integers because no months have more than five Sundays (or Mondays). More on that later.
OK, these are the conceptual building blocks. Let's use them.
The first day of the week derived from the last day of the month is
SET #month := '1941-12';
SET #first_day_of_month := STR_TO_DATE(CONCAT(`1941-12`, `-01'), '%Y-%m-%d')
SET #seed : =FirstDayOfWeek(LAST_DAY(first_day_of_month));
Then you need four five consecutive week-starting days the last of which is #seed.
SELECT #seed - INTERVAL (7*seq.seq) first_day_of_week
FROM seq_0_to_4
Next you need to limit that to days in your month.
SELECT first_day_of_week,
first_day_of_week + INTERVAL 6 DAY last_day_of_week
FROM (
SELECT #seed - INTERVAL (7*seq.seq) first_day_of_week
FROM seq_0_to_4
) w
WHERE first_day_of_week >= #first_day_of_month
ORDER BY first_day_of_week
That gives you a table of rows, one for each week beginning in the month. If you want to exclude weeks in which the last weekday is in the next month, change your WHERE to
WHERE first_day_of_week >= #first_day_of_month
AND first_day_of_week + INTERVAL 6 DAY <= #seed
Finally, to get the exact string format you specified in your question, wrap that query in this:
SELECT GROUP_CONCAT (
CONCAT(first_day_of_week, ' to ', last_day_of_week)
SEPARATOR ' || '
ORDER BY first_day_of_week)
FROM (
SELECT first_day_of_week,
first_day_of_week + INTERVAL 6 DAY last_day_of_week
FROM (
SELECT #seed - INTERVAL (7*seq.seq) first_day_of_week
FROM seq_0_to_4
) w
WHERE first_day_of_week >= #first_day_of_month
) x
That's it.
I promised to describe FirstDayOfWeek(dt). Here it is.
FROM_DAYS(TO_DAYS(dt) -MOD(TO_DAYS(dt) -1, 7))
It's a bit of a magic spell, but it works. If your weeks start Mondays, it is this.
FROM_DAYS(TO_DAYS(dt) -MOD(TO_DAYS(dt) -2, 7))
I promised to describe seq_0_to_4. If you're using the MariaDB fork of MySQL, it's built in. If you're using the Sun / Oracle fork, you define it like this.
(SELECT 0 AS seq UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) seq_0_to_4
Putting it all together:
SET #month := '1941-12';
SET #first_day_of_month := STR_TO_DATE(CONCAT(`1941-12`, `-01'), '%Y-%m-%d');
SET #seed := FROM_DAYS(TO_DAYS(LAST_DAY(first_day_of_month))
-MOD(TO_DAYS(LAST_DAY(first_day_of_month)) -1, 7));
SELECT GROUP_CONCAT (
CONCAT(first_day_of_week, ' to ', last_day_of_week)
SEPARATOR ' || '
ORDER BY first_day_of_week)
FROM (
SELECT first_day_of_week,
first_day_of_week + INTERVAL 6 DAY last_day_of_week
FROM (
SELECT #seed - INTERVAL (7*seq.seq) first_day_of_week
FROM (SELECT 0 AS seq UNION SELECT 1 UNION SELECT 2
UNION SELECT 3 UNION SELECT 4
) seq
) w
WHERE first_day_of_week >= #first_day_of_month
) x
It's unreasonably complex (the technical term is a freakin' hairball) to solve your problem in pure MySQL-dialect SQL, but it's possible.
Probably is too late but i figured out this using Common Table expressions in MYSQL 8.0.2
WITH RECURSIVE
Years(y) AS
(
SELECT 2020
UNION ALL
SELECT y + 1 FROM Years WHERE y < 2021
),
Days (d) AS
(
SELECT 1
UNION ALL
SELECT d + 1 FROM Days WHERE d < 366
)
SELECT
y AS Year,
MONTH(MakeDate(y,d)) AS Month,
WEEK(MakeDate(y,d))+1 -WEEK(TIMESTAMPADD(MONTH,MONTH(MakeDate(y,d))-1,MakeDate(y,1))) AS Week,
Min(MakeDate(y,d)) AS StartDate,
timestampadd(second,-1,timestampadd(day,1,MAx(MakeDate(y,d)))) AS EndDate
FROM Years,Days
WHERE Year(MakeDate(y,d)) <= y
GROUP BY y, MONTH(MakeDate(y,d)),WEEK(MakeDate(y,d))+1 -WEEK(TIMESTAMPADD(MONTH,MONTH(MakeDate(y,d))-1,MakeDate(y,1)))
ORDER BY 1,2,3
In mysql, I am calculating averages of the same metric over different intervals (3 Day, 7 Day, 30 Day, 60 Day, etc...), and I need the results to be in a single line per id.
Currently, I am using a Join per each interval. Given that I have to compute this for many different stores, and over several different intervals, is there a cleaner and/or more efficient way of accomplishing this?
Below is the code I am currently using.
Thanks in advance for the help
SELECT T1.id, T1.DailySales_3DayAvg, T2.DailySales_7DayAvg
FROM(
SELECT id, avg(DailySales) as 'DailySales_3DayAvg'
FROM `SalesTable`
WHERE `Store`=2
AND `Date` >= DATE_SUB('2012-07-28', INTERVAL 3 DAY)
AND `Date` < '2012-07-28'
) AS T1
JOIN(
SELECT id, avg(DailySales) as 'DailySales_7DayAvg'
FROM `SalesTable`
WHERE `Store`=2
AND `Date` >= DATE_SUB('2012-07-28', INTERVAL 7 DAY)
AND `Date` < '2012-07-28'
) AS T2
ON T1.ArtistId = T2.ArtistId
Where the results are:
id DailySales_3DayAvg DailySales_7DayAvg
3752 1234.56 1114.78
...
You can use a query like this -
SELECT
id,
SUM(IF(date >= '2012-07-28' - INTERVAL 3 DAY, DailySales, 0)) /
COUNT(IF(date >= '2012-07-28' - INTERVAL 3 DAY, 1, NULL)) 'DailySales_3DayAvg',
SUM(IF(date >= '2012-07-28' - INTERVAL 7 DAY, DailySales, 0)) /
COUNT(IF(date >= '2012-07-28' - INTERVAL 7 DAY, 1, NULL)) 'DailySales_7DayAvg'
FROM
SalesTable
WHERE
Store = 2 AND Date < '2012-07-28'
GROUP BY
id
I don't think you can do this in any other way if you want to pull real-time data. However, if you can afford displaying slightly outdated data, you could pre-calculate these average (like once or twice a day) for each item.
You may want to look into the Event Scheduler, which allows you to keep everything inside MySQL.
This question already has answers here:
Calculate age in MySQL (InnoDB)
(13 answers)
Closed 8 years ago.
I am trying to determine the age of the people that are listed in my database. This is what I came up with, but I can't seem to limit the query to show people who are only 18 and under.
SELECT
*
FROM
patientdetails
WHERE
ageInYears IN (
SELECT
DATEDIFF(CURRENT_DATE, STR_TO_DATE(p.DOB, '%d-%m-%Y'))/365 AS ageInYears
FROM
patientdetails p
) < 19
I believe something like this should work:
SELECT *
FROM patientdetails
WHERE DATEDIFF(CURRENT_DATE, STR_TO_DATE(p.DOB, '%d-%m-%Y'))/365 < 19
You don't need the subquery:
SELECT *
FROM PatientDetails pd
WHERE DATEDIFF(CURRENT_DATE, STR_TO_DATE(p.DOB, '%d-%m-%Y'))/365 <19
This sort of works:
SELECT *
FROM PatientDetails pd
WHERE DATEDIFF(CURRENT_DATE, STR_TO_DATE(pd.DOB, '%d-%m-%Y')) / 365 < 19
Dividing the days by 365 will fail to accommodate for leap years, meaning you will be off by 4 days guaranteed. You could divide by 365.25 and then subtract 0.75. I think that accounts for everything, but I'm shooting from the hip a bit. I'm not sure offhand if there would be integer errors with this calculation or not. I'm not sure how MySQL handles implicit typecasting.
I would be more inclined to do it this way. Logically, it's "all patients whose 19th birthday is in the future":
SELECT *
FROM PatientDetails pd
WHERE DATE_ADD( STR_TO_DATE(pd.DOB, '%d-%m-%Y'), INTERVAL 19 YEAR ) > CURDATE()
However, this exact question is addressed and explained in the MySQL documentation for date calculations, but it seems kind of ass-backwards to me:
SELECT name, birth, CURDATE(),
(YEAR(CURDATE())-YEAR(birth))
- (RIGHT(CURDATE(),5)<RIGHT(birth,5))
AS age
FROM pet
I have a table with a year and month in different columns, and I need to find all rows between 3 months ago and now.
SELECT * FROM `table`
WHERE DATE(CONCAT(`year`, '-', `month`, '-01')) >=
DATE_FORMAT(NOW() - INTERVAL 3 MONTH, '%Y-%m-01')
It just seems rather verbose, and possibly inefficient as this is a very large table. Is there a better way to do this?
There isn't much optimization to be had when the date is stored as separate fields, but I would re-write your query as:
SELECT *
FROM `table`
WHERE STR_TO_DATE(`year`+ '-'+ `month` + '-01', '%Y-%m-%d') >= DATE_SUB(NOW(), INTERVAL 3 MONTH)
The concatenation renders indexing on the year and month columns useless.
For more info about MySQL date functions, see: http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html
Well, I had the same issue and I have figured what I think is the solution for this case.
SELECT *
FROM table
WHERE (month >= (month(curdate()) - 3 AND year >= year(curdate()))
AND (month >= month(curdate()) AND year >= year(curdate())))
Supposing you have columns named year and month.