BETWEEN months query in MySQL - php

I am trying to get rows witch has a season start date and season end date in current date.
But I have problem with periods between months in winter. For example winter starts 01.11 and end at 28.02 (I don't care about 27 or 28)
When I try to get products in winter like below query
SELECT *
FROM products P
LEFT JOIN seasons S ON P.s_id = S.id
WHERE MONTH(CURDATE()) BETWEENS MONTH(S.startdate) and MONTH(S.enddate)
I get nothing
The table seasons has one row with below value
id = 1
Description = Winter
startdate = 2013-11-01
enddate = 2014-02-28
!IMPORTANT
I don;t care about year
Can anyone help? thanks

You can achieve this with a CASE:
SELECT *
FROM products P
LEFT JOIN seasons S ON P.s_id = S.id
WHERE CASE
WHEN MONTH(S.startdate) > MONTH(S.enddate)
AND (MONTH(CURDATE()) > MONTH(S.startdate)
OR MONTH(CURDATE()) < MONTH(S.enddate))
THEN 1
WHEN MONTH(S.startdate) <= MONTH(S.enddate)
AND MONTH(CURDATE()) BETWEEN MONTH(S.startdate) AND MONTH(S.enddate)
THEN 1
ELSE 0
END
This assumes that whenever MONTH(startdate) > MONTH(enddate) the year has changed.
Will return true in that case whenever MONTH(curdate()) is bigger than MONTH(startdate) OR is smaller than MONTH(enddate).
In the case when MONTH(startdate) <= MONTH(enddate) it just validates if it is between them.
sqlfiddle demo

Why would you get anything? You're doing the equivalent of
WHERE 5 BETWEEN 'yyyy-mm-dd' AND 'yyyy-mm-dd'
You're doing a literal apples/oranges comparison. Perhaps you want
WHERE MONTH(CURDATE()) BETWEEN MONTH(S.startdate) AND MONTH(S.enddate)
instead, so you're doing apples to apples.

I don't like the strange converting to strings and parsing solutions. You can do this with a little math...
SELECT *
FROM products P
LEFT JOIN seasons S ON P.s_id = S.id
WHERE ((DAYOFYEAR(CurDate()) - DAYOFYEAR(S.startdate)) + 365) % 365 BETWEEN 0 AND
(((DAYOFYEAR(S.enddate) + 365) - DAYOFYEAR(S.startdate))) % 365

Between requires the values be in order - your start date is AFTER your end date so you'll always get nothing.
Additionally, you are comparing months to dates; since the date has the year in it you can't ignore the year that way.
You have to compare the day in year to ignore the year. If DAYOFYEAR(curdate()) is between DAYOFYEAR(end date) and DAYOFYEAR(start date) that will work if both end data and start date are in the same year. You'll need to get a bit fancier if they are in different years, but that should be obvious.
Also, you probably have to think through what "not caring about year" really means. If you're looking for dates between March and September, that will be the reverse of looking for dates between September and March. What you want will alter how you program it.

I found solution.
SELECT *
FROM products P
LEFT JOIN seasons S ON P.s_id = S.id
WHERE
IF(
( CURDATE() BETWEEN STR_TO_DATE( CONCAT( YEAR(CURDATE()),'-',MONTH(S.startdate),'-',DAY(S.startdate)) , '%Y-%m-%d') AND STR_TO_DATE( CONCAT( IF( MONTH(S.`enddate`) < MONTH(S.`startdate`) ,YEAR(CURDATE())+1,YEAR(CURDATE()) ),'-',MONTH(s.`enddate`),'-',DAY(S.`enddate`)) , '%Y-%m-%d') ),
"IN Season",
IF( P.`s_id` IS NOT NULL AND P.`s_id` != "","Out OF Season","" )
) = "IN Season"
I have 2 rows
1. Startdate: 2013-11-01 EndDate: 2014-02-28 (Winter)
2. StartDate: 2013-06-01 ENDDate: 2013-08-31 (Summer)
It works perfect.

Related

Get data from 2 differents days and compare

I have a question. So I have this data in array :
id idm amount date
1 5 10 2017-08-23 12:12:12
2 5 20 2017-08-23 12:14:16
3 6 13 2017-08-23 18:00:00
4 5 25 2017-08-24 19:00:00
5 5 160 2017-08-24 19:30:00
So the idea is to get the sum of amount from date 2017-08-23 and compare with date 2017-08-24. If difference between those 2 values for a user is bigger than 20 for example in this case I found a user.
My propose was to make 2 sql's :
select sum(amount) as previous_amount, idm
FROM table
WHERE date >= '2017-08-23 00:00:00' AND date <= '2017-08-23 23:59:59'
GROUP By idm
select sum(amount) as actual_amount, idm
FROM table
WHERE date >= '2017-08-24 00:00:00' AND date <= '2017-08-24 23:59:59'
GROUP By idm
And make the treatment in php, but maybe exist a methode to do that in sql. Can you help me please ? Thx in advance and sorry for my english.
If I correctly understand, this is what you want:
select
idm,
sum(case when date >= '2017-08-23 00:00:00' AND date <= '2017-08-23 23:59:59' then amount end) -
sum(case when date >= '2017-08-24 00:00:00' AND date <= '2017-08-24 23:59:59' then amount end) as diff
from your_table
group by idm
having diff not between -20 and 20
Not tested but this might work..
select (sum(b.amount) - sum(a.amount)) as result, idm
from table a join table b on a.idm = b.idm
where a.date = date('2017-08-23') and b.date = date('2017-08-24')
group by idm
if you can provide a fiddle that might help us help you.
My approach would be to calculate the differences per idm and day (which I 'll show you how) and do the rest in the code. So looking at this first part, I am using the datetime manipulation functions to remove the time element out of the datetime values which keeps date only as string. This is now something we can group on:
select sum(amount) as previous_amount, idm, DATE_FORMAT(`date`, '%Y-%m-%d') as day
FROM test
GROUP By idm, day
order by idm, day
This will give you something like:
previous_amount idm day
30 5 2017-08-23
185 5 2017-08-24
13 6 2017-08-23
At this point I would continue in code. However, if you want to do this in the database you need to join the above result table on itself. This simulates a LAG/LEAD behavior (two Oracle instructions that are not available in MySQL). So the query is:
select
diff1.idm,
previous_amount,
actual_amount,
diff2.actual_amount-diff1.previous_amount as difference,
diff1.day as from_day,
diff2.day as to_day
from (
select sum(amount) as previous_amount, idm, DATE_FORMAT(`date`, '%Y-%m-%d') as day
FROM test
GROUP By idm, day
order by idm, day
) as diff1
left join (
select sum(amount) as actual_amount, idm, DATE_FORMAT(`date`, '%Y-%m-%d') as day
FROM test
GROUP By idm, day
order by idm, day
) as diff2 on diff1.idm=diff2.idm
where DATE(diff2.day) > DATE(diff1.day)
and diff2.actual_amount-diff1.previous_amount > 20;
The result of the above looks like:
idm previous_amount actual_amount difference from_day to_day
5 30 185 155 2017-08-23 2017-08-24
Note that the above query guarantees that day2 > day1 but does not enforce one day only difference. However, I think it could get extended to do so fairly easily.
sqlfiddle here
UPDATE #1
If you want to guarantee single day difference then replace the where condition with TIMESTAMPDIFF(DAY, diff1.day, diff2.day) = 1 fiddle
Try that, should be faster than #Shuddh solution because it does not rely on a join
SELECT
idm,
sum(IF(date(`date`) = '2017-08-23', amount, 0)) as amountDay1,
sum(IF(date(`date`) = '2017-08-24', amount, 0)) as amountDay1,
sum(IF(date(`date`) = '2017-08-23', amount, 0))
- sum(IF(date(`date`) = '2017-08-24', amount, 0)) as diffDay1Day2
FROM
table
GROUP BY
idm

MySQL Query results based on month

I really need some help. Not MySQL friendly, muddled through this last few days but now stuck...
Need to take the below query and modify it to pull out only records closed in month of "January" for instance. Is this possible from the below? Cant figure it...
<?php
$recentlyClosedDays = 7;
?>
$query1 = "
SELECT HD_TICKET.ID as ID,
HD_TICKET.TITLE as Title,
HD_STATUS.NAME AS Status,
HD_PRIORITY.NAME AS Priority,
HD_TICKET.CREATED as Created,
HD_TICKET.MODIFIED as Modified,
S.FULL_NAME as Submitter,
O.FULL_NAME as Owner,
HD_TICKET.RESOLUTION as Resolution,
(SELECT COMMENT FROM HD_TICKET_CHANGE WHERE HD_TICKET_ID=HD_TICKET.ID ORDER BY TIMESTAMP DESC LIMIT 1) as Comment,
HD_TICKET.CUSTOM_FIELD_VALUE0 as Type
FROM HD_TICKET
JOIN HD_STATUS ON (HD_STATUS.ID = HD_TICKET.HD_STATUS_ID)
JOIN HD_PRIORITY ON (HD_PRIORITY.ID = HD_TICKET.HD_PRIORITY_ID)
LEFT JOIN USER S ON (S.ID = HD_TICKET.SUBMITTER_ID)
LEFT JOIN USER O ON (O.ID = HD_TICKET.OWNER_ID)
WHERE (HD_TICKET.HD_QUEUE_ID = $mainQueueID)
AND (HD_STATUS.STATE like '%Closed%')
AND (HD_TICKET.TIME_CLOSED >= DATE_SUB(NOW(), INTERVAL $recentlyClosedDays DAY))
ORDER BY HD_TICKET.TIME_CLOSED DESC
";
Any help would be greatly apprecaited and beer will be owed :)
To select DATE, DATETIME, or TIMESTAMP values in the current month, you do this.
WHERE timestampval >= DATE(DATE_FORMAT(NOW(), '%Y-%m-01'))
AND timestampval < DATE(DATE_FORMAT(NOW(), '%Y-%m-01')) + INTERVAL 1 MONTH
For the previous month you can do this:
WHERE timestampval >= DATE(DATE_FORMAT(NOW(), '%Y-%m-01')) - INTERVAL 1 MONTH
AND timestampval < DATE(DATE_FORMAT(NOW(), '%Y-%m-01'))
For the previous year you could do this:
WHERE timestampval >= DATE(DATE_FORMAT(NOW(), '%Y-01-01')) - INTERVAL 1 YEAR
AND timestampval < DATE(DATE_FORMAT(NOW(), '%Y-01-01'))
You can summarize (aggregate) tables by month like this:
SELECT DATE(DATE_FORMAT(timestampval , '%Y-%m-01')) AS month_starting,
SUM(whatever) AS total,
COUNT(whatever) AS transactions
FROM table
GROUP BY DATE(DATE_FORMAT(timestampval , '%Y-%m-01'))
This all works because this expression:
DATE(DATE_FORMAT(sometime, '%Y-%m-01'))
takes an arbitrary sometime value and returns the first day of the month in which the timestamp occurs. Similarly,
DATE(DATE_FORMAT(sometime, '%Y-01-01'))
returns the first day of the year. You can then use date arithmetic like + INTERVAL 1 MONTH to manipulate those first days of months or years.
Here's a more complete writeup on this topic. http://www.plumislandmedia.net/mysql/sql-reporting-time-intervals/

How to skip other OR condition if first is matched in SELECT Query?

I am having a trouble with OR condition inside the SELECT.
I want a simple result if one condition is matched and rest OR condition should not be use.
What i want is:
I have some users shared records and i would like to email them the newest items shared on my website.
For me: Newest Items will be least two days older
Like Today is 9th so i would like to pull all records of 7th. but if i
didn't get any record of 7th then i would like to pull all record of
6th (3 days older from today). if i didn't get any records on 6th then
i would like to pull 1 day older from today.
for all this i have used OR in my SELECT query like this:
SELECT `tg`.* FROM `tblgallery` AS `tg` WHERE (
(tg.added_date BETWEEN '2014-07-07 00:00:00' AND '2014-07-08 00:00:00') OR
(tg.added_date BETWEEN '2014-07-06 00:00:00' AND '2014-07-07 00:00:00') OR
(tg.added_date BETWEEN '2014-07-08 00:00:00' AND '2014-07-09 00:00:00') )
And i have records in my database for dates:
2014-07-06
2014-07-07
and when i run this query it gives me all record of both dates.
But I need to pull only record of 2014-07-07 not of both.(I have mentioned above.)
I know i can do this by using multiple Select and i think that will not be a good idea to request to database again and again.
My Question is : How to pull data from database if the first match is true? and skip all data of rest dates?
OR
Is there any other way to do this?
Please Help
Usually one would just work with LIMIT, which is not applicable here, since there might be many rows per day. What I do is quite similar to LIMIT.
SELECT * FROM (
SELECT
tg.*,
#gn := IF(DATE(tg.added_date) != #prev_date, #gn + 1, #gn) AS my_group_number,
#prev_date := DATE(tg.added_date)
FROM tblgallery tg
, (SELECT #gn := 0, #prev_date := CURDATE()) var_init
ORDER BY FIELD(DATE(tg.added_date), CURDATE() - INTERVAL 1 DAY, CURDATE() - INTERVAL 3 DAY, CURDATE() - INTERVAL 2 DAY) DESC
) sq
WHERE my_group_number = 1;
Here's how it works.
With this line
, (SELECT #gn := 0, #prev_date := CURDATE()) var_init
the variables are initialized.
Then the ORDER BY is important! The FIELD() function sorts the rows from 2 days ago (gets value 3), to 3 days ago (gets value 2), to 1 day ago (gets value 1). Everything else gets value 0.
Then in the SELECT clause the order is also important.
With this line
#gn := IF(DATE(tg.added_date) != #prev_date, #gn + 1, #gn) AS my_group_number,
the variable #gn is incremented when the date of the current row is different from the date of the previous row.
With this line
#prev_date := DATE(tg.added_date)
the date of the current row is assigned to the variable #prev_date. In the line above it still has the value of the previous row.
Now those entries have a 1 in column my_group_number that have the most recent date in the order
2 days ago
3 days ago
yesterday
4 days ago
5 days ago
...
Try this Query:
SELECT GalleryID, PixName, A.added_date
FROM tblGallery A
INNER JOIN (
SELECT added_date FROM tblGallery
WHERE added_date <= DATE_SUB('2014-07-09 00:00:00', interval 2 day)
GROUP BY added_date
ORDER BY added_date DESC
LIMIT 1 ) B
ON A.added_date = B.added_date
See my SQL Fiddle Demo
And even if the date is more than 2 days older it will still work.
See here the Demo below wherein the latest is 4 days older from July 9, 2014
See the 2nd Demo
And if you want the current date instead of literal date like here then you could use CURDATE() function instead. Like one below:
SELECT GalleryID, PixName, A.added_date
FROM tblGallery A
INNER JOIN (
SELECT added_date FROM tblGallery
WHERE added_date <= DATE_SUB(CURDATE(), interval 2 day)
GROUP BY added_date
ORDER BY added_date DESC
LIMIT 1 ) B
ON A.added_date = B.added_date
See 3rd Demo
Well, I'm not being able to solve the multi OR issue but this is how could you get records being added last two days. Change the interval or the CURDATE() in order to fit your needs.
SELECT id, date_added
FROM gallery
WHERE date_added BETWEEN CURDATE() - INTERVAL 2 DAY AND CURDATE()
ORDER BY date_added
Check the SQL Fiddel
It is not about how OR works in MySQL.
I think you are misunderstanding where part by looking at your discussion with #B.T.
It will be executed for each record.
so if one of the record evaluates to false for the first condition then it will evaluate the second condition for that particular record and so on so if any condition evaluates to true by considering all the conditions then that will become part of your result set.
Try this query.
SELECT `tg`.* FROM `tblgallery` AS `tg` WHERE tg.added_date = (
select date (
select distinct(tg.added_date) date from tblgallery as tg
) as t1 order by case
when date between '2014-07-07 00:00:00' AND '2014-07-08 00:00:00'
then 1
when date between '2014-07-06 00:00:00' AND '2014-07-07 00:00:00'
then 2
when date between '2014-07-08 00:00:00' AND '2014-07-09 00:00:00'
then 3
else 4
end limit 1);
Here's what I am doing in this query.
I am getting all the distinct dates.
then I am ordering all the condition in order i.e if first condition is true then 1, if second is true then 2 and so on.
I am limiting the result to 1 so after the order whichever the result is the first row will be selected and which is a date and will be used in the condition.
Note: I have note tested it yes, so you may need to do some changes to the query.

Working out the amount of free dates in a given time period

I have a fun one for you. I have a database with the date columns free_from and free_until. What I need to find is the amount of days between now and 1 month today which are free. For example, if the current date was 2013/01/15 and the columns were as follows:
free_from | free_until
2013/01/12| 2013/01/17
2013/01/22| 2013/01/26
2013/01/29| 2013/02/04
2013/02/09| 2013/02/11
2013/02/14| 2013/02/17
2013/02/19| 2013/02/30
The answer would be 16
as 2 + 4 + 6 + 2 + 2 + 0 = 16
The first row only starts counting at the 15th rather than the 12th
since the 15th is the current date.
The last row is discounted because none of the dates are within a
month of the current date.
The dates must be counted as it the free_from date is inclusive and
the free_until date is exclusive.
I'm assuming DATEDIFF() will be used somewhere along the line, but I can't, for the life of me, work this one out.
Thanks for your time!
Edit: This is going into PHP mysql_query so that might restrict you a little concerning what you can do with MYSQL.
SET #today = "2013-01-15";
SET #nextm = DATE_ADD(#today, INTERVAL 1 month);
SET #lastd = DATE_ADD(#nextm, INTERVAL 1 day);
SELECT
DATEDIFF(
IF(#lastd> free_until, free_until, #lastd),
IF(#today > free_from, #today, free_from)
)
FROM `test`
WHERE free_until >= #today AND free_from < #nextm
That should work. At least for your test data. But what day is 2013/02/30? :-)
Dont forget to change #today = CURDATE();
The best I can think of is something like:
WHERE free_until > CURDATE()
AND free_from < CURDATE() + INTERVAL '1' MONTH
That will get rid of any unnecessary rows. Then on the first row do in PHP:
date_diff(date(), free_until)
On the last row, do:
date_diff(free_from, strtotime(date("Y-m-d", strtotime($todayDate)) . "+1 month"))
Then on intermediate dates do:
date_diff(free_from, free_until)
Something to that effect, but this seems extremely clunky and convoluted...
From the top of my mind... first do a:
SELECT a.free_from AS a_from, a.free_until AS a_until, b.free_from AS b_from
FROM availability a
INNER JOIN availability b ON b.free_from > a.free_until
ORDER BY a_from, b_from
This probably will return a set of rows where for each row interval you have next i.e. greater intervals. The results are ordered strategically. You can then wrap the results in a partial group by:
SELECT * FROM (
SELECT a.free_from AS a_from, a.free_until AS a_until, b.free_from AS b_from
FROM availability a
INNER JOIN availability b ON b.free_from > a.free_until
ORDER BY a_from, b_from
) AS NextInterval
GROUP BY a_from, b_until
In the above query, add a DATE_DIFF clause (wrap it in SUM() if necessary):
DATE_DIFF(b_until, a_from)

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())

Categories