Using Date_Sub to subtract year with Aliases column - php

I have two column brand and date_time and during select I assign them to aliases. The query below:
SELECT brand,
YEAR(date_time) AS 'year',
SUM( brand = 'KFC' ) AS kfc
SUM( brand = 'PZH' ) AS pzh,
SUM( brand = 'SUB' ) AS sub
FROM scan_report
GROUP BY YEAR(date_time)
Result to this:
Now here's the confuse part. How can I use Date_Sub function to display data only for the last 5 years. I mean user will enter 2014 and will only display/select record from the last 5 year that means from 2010 to 2014.
Using WHERE such as this gave me empty records:
SELECT
YEAR(date_time) AS 'year',
SUM( brand = 'KFC' ) AS kfc
SUM( brand = 'PZH' ) AS pzh,
SUM( brand = 'SUB' ) AS sub
FROM scan_report WHERE DATE_SUB(2014, INTERVAL 5 YEAR)
GROUP BY YEAR(date_time)
or this
FROM scan_report WHERE DATE_SUB(year = 2014, INTERVAL 5 YEAR)
Any idea how I can retrieve record from the last 5 years of user input? I prefer to solve this problem with mysql, if no solution is found than I'll go with php. Thanks in advance.

Do this simple math in PHP:
$year = 2014; # from $_POST or anywhere else
$from = ($year - 4) . '-01-01 00:00:00';
$to = $year . '-12-31 23:59:59';
And then use this 2 variables in query.
$sql = "
...
WHERE date_time BETWEEN '$from' AND '$to'
...
";
demo
UPDATE (calculation in SQL):
If you don't want to calculate in PHP, you can then calculate in MySQL:
$sql = "
...
WHERE
date_time BETWEEN CONCAT({$year}-4, '-01-01 00:00:00') AND CONCAT({$year}, '-12-31 23:59:59')
...
";
UPDATE (don't care about speed or indexes):
$sql = "
...
WHERE
YEAR(date_time) AND {$year}-4 AND {$year}
...
";

Your problem is that the predicate in the WHERE clause is returning NULL for every row, so no rows are being returned.
The DATE_SUB function takes a DATE expression as the first argument, not an integer. That is, you could specify '2014-01-01' as the first argument, rather than 2014. That would return a non-zero date, which would evaluate to TRUE (in a boolean context) so all rows would be returned.
To include rows that have particular values of date_time, you'd need to use a comparison in the predicate (WHERE clause). To return rows with date_time values of '2009-01-01' and later...
SELECT YEAR(t.date_time) AS year
, SUM( t.brand = 'KFC' ) AS kfc
, SUM( t.brand = 'PZH' ) AS pzh
, SUM( t.brand = 'SUB' ) AS sub
FROM scan_report t
WHERE t.date_time >= DATE_SUB('2014-01-01', INTERVAL 5 YEAR)
GROUP BY YEAR(date_time)
Some additional notes: if you need (or want) to escape an identifier (e.g. a column name, because it's a reserved word), enclose the identifier in backtick characters, not single quotes, e.g.
SELECT YEAR(`t`.`date_time`) AS `year`

Related

Why mysql query between date variables is not returning a range

Foreword: I tried to find an answer to this and while there are some similarities in some of the answered questions, there were no matches.
I'm trying to generate 3 month reports but only the current date is returning a value.
<?php
// Db connect
include_once ('db.php');
global $con2;
// 3 month reports
$date_from = date("d-m-Y", strtotime(" -3 months"));
$date_to = date('d-m-Y');
// Count
$get = "select * from table1 where date between '$date_from' and '$date_to'";
$get_connect = mysqli_query($con2, $get);
$get_rows = mysqli_num_rows($get_connect);
// Display data
echo $get_rows;
?>
Additional info.
The dates already stored in the date column of table1 are in exactly the same format as the variables eg: 10-10-2017
The entires which are from today are being displayed but nothing else is. Is this because PHP can only read up till the first - dash?
Using dd-mm-yyyy strings as dates simply will not work correctly with "between"
select
*
from (
select '10-10-2017' as 'date' union all
select '11-11-2017' as 'date' union all
select '10-10-1989' as 'date' union all
select '10-10-2020' as 'date' union all
select '10-10-3017' as 'date'
) table1
where `date` between '10-10-2017' and '11-11-2017'
order by `date`
date
1 10-10-2017
2 10-10-2020
3 10-10-3017
4 11-11-2017
but conversion to a real date does:
select
* , str_to_date(`date`,'%d-%m-%Y')
from (
select '10-10-2017' as 'date' union all
select '11-11-2017' as 'date' union all
select '10-10-1989' as 'date' union all
select '10-10-2020' as 'date' union all
select '10-10-3017' as 'date'
) table1
where str_to_date(`date`,'%d-%m-%Y') between str_to_date('10-10-2017','%d-%m-%Y') and str_to_date('11-11-2017','%d-%m-%Y')
order by str_to_date(`date`,'%d-%m-%Y')
date str_to_date(`date`,'%d-%m-%Y')
1 10-10-2017 10.10.2017 00:00:00
2 11-11-2017 11.11.2017 00:00:00
Demo
In addition: The way "between" is implemented in SQL is that the first value must be lower than the second value (i.e. the sequence of the value IS vital). That is because "between" is really just a syntax shortcut for somevalue >= compare_val_low and somevalue <= compare_val_high if you reverse the comparison values it will return NULL unless somevalue happens to equal one or both of the comparison values. Using strings it will be easy for what appears to be a lower date, to in fact be a "higher value" due to the nature of string ordering, e.g. this might "look" ok:
between '29-08-2017' and '11-11-2017'
but it is not OK at all because '29' is higher than '11' and between will return no rows.

Using count() to output total entries per month as well as the month

I have tried a bit of searching, and as a result found the count() function to get me closer to my goal, but have stopped a bit. Here's what my code looks like now:
$sql = "SELECT COUNT( * ) as Count
FROM AttendanceDB
WHERE YEAR( class_time ) = '2015'
AND studentid = '$studentid'
AND furikae = 0
GROUP BY MONTH( class_time )";
Then I use echo $row['Count']; to output the data.
This allows me to total the number of rows per month for the year of 2015 and then output the answer. Now what I want to do is output the month in each row along with the total. I could just preformat everything, but there are some studentids which have entries beginning partway through the year, which means that the first months don't output any results.
How can I SELECT both the month from the DB as well as use the count function at the same time? Thanks
Here is what I think will help you:
$sql = "SELECT MONTH( class_time ) AS month, COUNT( * ) as Count
FROM AttendanceDB
WHERE YEAR( class_time ) = '2015'
AND studentid = '$studentid'
AND furikae = 0
GROUP BY MONTH( class_time )";
You could use an agregator function
$sql = "SELECT COUNT( * ),max(YOUR MONTH COLUMN) as MONTH as Count
FROM AttendanceDB
WHERE YEAR( class_time ) = '2015'
AND studentid = '$studentid'
AND furikae = 0
GROUP BY MONTH( class_time )";
How about:
$sql = "SELECT MONTH(class_time) AS m, COUNT(*) AS c
FROM AttendanceDB WHERE YEAR(class_time) = '2015'
AND studentid = '$studentid'
AND furikae = 0
GROUP BY m";
This way you'll have the month as a separate column, which will also show in the result as $row['m'] and the count per month as $row['c']. (I avoid the same names as the MySQL function names, hence the m and c.)

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.

BETWEEN months query in MySQL

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.

MySQL queries and intervals

This is the toughest query I ever made:
http://robertr.pastebin.com/X4bG4pFp
"SELECT `user`.`id` , `user`.`fname` , `user`.`lname` ,
YEAR( `user`.`bday` ) AS `bday_year` , `user`.`class_id` ,
( SELECT `class`.`class_name`
FROM `wp_class_classes` `class`
WHERE `user`.`class_id` = `class`.`id`) AS `class_name`
FROM `wp_class_users` `user`
WHERE MONTH( `bday` ) = $month AND DAY( `bday` ) = $day
OR `user`.`fname` =
( SELECT `name`.`names`
FROM `wp_class_namedays` `name`
WHERE `name`.`day` = '$month.$day'
AND `user`.`fname` = `name`.`names` )
This query grabs data from three different database tables to check if there is someone in the database, who has a party today. And in Latvia we have Name Days too. Anyway, this query works well, and does its job well, but now I want to make it a bit cooler.
I want it to show, who will be having a party next week. You've probably noticed these emails that Facebook sends to you every weekend showing who has a birthday coming up.
But I just can't understand how to get at least that interval?
I remember that PHP has some good functions with which you can find on which day starts month and so on, but maybe here are some bright heart, and willing to help me kick me a bit faster forward.
SELECT
`user`.`id`,
`user`.`fname`,
`user`.`lname` ,
YEAR(`user`.`bday`) AS `bday_year`,
`user`.`class_id`,
(
SELECT
`class`.`class_name`
FROM `wp_class_classes` `class`
WHERE `user`.`class_id` = `class`.`id`
) AS `class_name`,
CASE
WHEN MONTH(`week`.`Date`) = MONTH(`user`.`bday`) AND
DAY(`week`.`Date`) = DAY(`user`.`bday`) THEN 1
ELSE 2
END AS `event_type`
FROM `wp_class_users` `user`
LEFT JOIN `wp_class_namedays` `name` ON `user`.`fname` = `name`.`names`
LEFT JOIN (
SELECT CURDATE() + INTERVAL (1 - DAYOFWEEK(CURDATE())) DAY AS `Date` UNION ALL
SELECT CURDATE() + INTERVAL (2 - DAYOFWEEK(CURDATE())) DAY UNION ALL
SELECT CURDATE() + INTERVAL (3 - DAYOFWEEK(CURDATE())) DAY UNION ALL
SELECT CURDATE() + INTERVAL (4 - DAYOFWEEK(CURDATE())) DAY UNION ALL
SELECT CURDATE() + INTERVAL (5 - DAYOFWEEK(CURDATE())) DAY UNION ALL
SELECT CURDATE() + INTERVAL (6 - DAYOFWEEK(CURDATE())) DAY UNION ALL
SELECT CURDATE() + INTERVAL (7 - DAYOFWEEK(CURDATE())) DAY
) `week`
ON CONCAT(MONTH(`week`.`Date`), '.', DAY(`week`.`Date`)) IN (
CONCAT(MONTH(`user`.`bday`), '.', DAY(`user`.`bday`)),
`name`.`day`
)
WHERE `week`.`Date` IS NOT NULL
The user table is joined with the name day table, and the result set is then compared against the dates of the current week. The final result set lists only those users whose birthdays or name days happen during the week.
If you want to know about the events of, for example, the next week, you can simply change the intervals in the week.Date definitions as 8 - DAYOFWEEK..., 9 - DAYOFWEEK... etc.
One last thing is, instead of the correlated subquery in the select list you could use INNER JOIN, like this:
SELECT
`user`.`id`,
`user`.`fname`,
`user`.`lname` ,
YEAR(`user`.`bday`) AS `bday_year`,
`user`.`class_id`,
`class`.`class_name`
FROM `wp_class_users` `user`
INNER JOIN `wp_class_classes` `class` ON `user`.`class_id` = `class`.`id`
LEFT JOIN `wp_class_namedays` `name` ON ... /* the rest of the above script */
The event_type column as defined above can tell you whether the event is a birthday or not, but it doesn't let you know whether it's both the Birthday and a Name Day for that particular person.
In case you would like to have that distinction, you could change the event_type definition like this:
CASE
WHEN MONTH(`week`.`Date`) = MONTH(`user`.`bday`) AND
DAY(`week`.`Date`) = DAY(`user`.`bday`) THEN 1
ELSE 0
END +
CASE CONCAT(MONTH(`week`.`Date`), '.', DAY(`week`.`Date`))
WHEN `name`.`day` THEN 2
ELSE 0
END AS `event_type`
Now the result of the column would be:
1 – a birthday
2 – a name day
3 – both
Additionally, you could have 'B' instead of 1 and 'N' instead of 2 (and '' instead of 0). The results would be then 'B', or 'N', or 'BN'. Not sure whether + can be used for concatenation, though. If not, put both CASEs into CONCAT().
I'm not sure if I get your query right, but the command SYSDATE() is mentioned in the MySQL docs. You might want to try something like:
... where date = SYSDATE() + 7
(check the syntax, I come from Oracle ;) )
This will get the parties for the next 7 days.

Categories