I need to select all dates (day & month) within the next 45 days (regardless of year). The below query has stopped working. Like literally started returning no rows when there is definitely data there.
Is there another way to do this query? Happy to check records within a php loop if needed.
Essentially I need to show all records within the next 45 days, even if the year has lapsed.
SELECT
p.*,
c.company
FROM
products p
LEFT JOIN
customers c
ON c.id = p.id
WHERE
DATE_FORMAT(p.service_date, '%m-%d') >= DATE_FORMAT(CURDATE(), '%m-%d')
AND DATE_FORMAT(p.service_date, '%m-%d') <= DATE_FORMAT((CURDATE() + INTERVAL 45 DAY), '%m-%d')
AND c.email_service = 1
ORDER BY
p.service_date ASC
Worked this out with php
// Get the dates m-d for the next 45 days
for ($i = 0; $i <= 45; $i++) {
$days = '+' . $i . ' days';
$date = date('m-d', strtotime($days));
if ($i != 45) {
$DateQuery .= "'$date',";
} else {
$DateQuery .= "'$date'";
}
}
and Inserted the dates I needed directly into the query
SELECT
p.*,
c.company
FROM
products p
LEFT JOIN
customers c
ON c.id = p.id
WHERE
DATE_FORMAT(p.service_date, '%m-%d') IN
(
$DateQuery
)
AND c.email_service = 1
ORDER BY
p.service_date ASC
I don't know why your original query was utilizing Date_Format() on a column (service_date), already stored in MySQL Date type, for range comparisons. It is not able to use any indexing (if defined) either.
You simply need to add/subtract Interval to the date:
SELECT
p.*,
c.company
FROM
products p
LEFT JOIN
customers c
ON c.id = p.id
WHERE
p.service_date >= CURDATE()
AND p.service_date <= (CURDATE() + INTERVAL 45 DAY)
AND c.email_service = 1
ORDER BY
p.service_date ASC
The best way to solve this problem is to use DAYOFYEAR. The difficult part is dealing with the case when you are within 45 days of the end of the year, in which case you have to split the check to see if the date is between now and the end of the year or the beginning of the year and now + 45 days; otherwise you just check if the day of the year is between now and now + 45 days. Try replacing your condition with this:
CASE WHEN DAYOFYEAR('2018-12-31')-DAYOFYEAR(NOW()) < 45 THEN
DAYOFYEAR(p.service_date) BETWEEN DAYOFYEAR(NOW()) AND DAYOFYEAR('2018-12-31') OR
DAYOFYEAR(p.service_date) BETWEEN 1 AND DAYOFYEAR(NOW() + INTERVAL 45 DAY)
ELSE
DAYOFYEAR(p.service_date) BETWEEN DAYOFYEAR(NOW()) AND DAYOFYEAR(NOW() + INTERVAL 45 DAY)
END
Your full query should then look like this:
SELECT
p.*,
c.company
FROM
products p
LEFT JOIN
customers c
ON c.id = p.id
WHERE
(CASE WHEN DAYOFYEAR('2018-12-31')-DAYOFYEAR(NOW()) < 45 THEN
DAYOFYEAR(p.service_date) BETWEEN DAYOFYEAR(NOW()) AND DAYOFYEAR('2018-12-31') OR
DAYOFYEAR(p.service_date) BETWEEN 1 AND DAYOFYEAR(NOW() + INTERVAL 45 DAY)
ELSE
DAYOFYEAR(p.service_date) BETWEEN DAYOFYEAR(NOW()) AND DAYOFYEAR(NOW() + INTERVAL 45 DAY)
END)
AND c.email_service = 1
ORDER BY
p.service_date ASC
If you are worried about leap years, you could change the DAYOFYEAR('2018-12-31') to DAYOFYEAR(CONCAT(YEAR(NOW), '-12-31'))
Related
I want to get the today count of users and yesterday's users count for that i want to write only one query how can i do that..?
these are my queries I want only one query:
SELECT COUNT(*) FROM visitors group by visited_date ORDER by visited_date DESC limit 1,1 as todayCount
SELECT COUNT(*) FROM visitors group by visited_date ORDER by visited_date DESC limit 1,0 as yesterdayCount
My expected results or only 2 columns
todayCount yesterdayCount
2 4
This should do the trick:
SELECT COUNT(CASE
WHEN visited_date = CURDATE() THEN 1
END) AS todayCount ,
COUNT(CASE
WHEN visited_date = CURDATE() - INTERVAL 1 DAY THEN 1
END) AS yesterdayCount
FROM visitors
WHERE visited_date IN (CURDATE(), CURDATE() - INTERVAL 1 DAY)
GROUP BY visited_date
ORDER by visited_date
If you know the current and previous date, then you can do:
SELECT SUM(visited_date = CURDATE()) as today,
SUM(visited_date = CURDATE() - interval 1 day) as yesterday
FROM visitors
WHERE visited_date >= CURDATE() - interval 1 day;
If you don't know the two days, then you can do something similar, getting the latest date in the data:
SELECT SUM(v.visited_date = m.max_vd) as today,
SUM(v.visited_date < m.max_vd) as yesterday
FROM visitors v CROSS JOIN
(SELECT MAX(v2.visited_date) as max_vd FROM visitors v2) as m
WHERE v.visited_date >= m.max_vd - interval 1 day
Just try this simple query
select visited_date as date, COUNT(*) as count from `visitors`
group by `visited_date` order by `visited_date` asc
It will produce output as
It will work for you.
Try this:
$sqlToday = "Select COUNT(*) FROM menjava WHERE DATE(date_submitted)=CURRENT_DATE()";
$sqlYesterday = "Select COUNT(*) FROM menjava WHERE DATE(dc_created) = CURDATE() - INTERVAL 1 DAY";
Basically I am attempting to make a chart with this data. I am able to put my query into a while loop in PHP to get each average, but I would prefer this was done with one query producing one result table.
<?php
date_default_timezone_set('America/Los_Angeles');
include('../connect.php');
$subcategory = 'T-Shirts';
$date = date('Y-m-d', strtotime('-29 days'));
$today = date("Y-m-d");
$subcategory = mysqli_real_escape_string($conp, $subcategory);
echo "<table border=\"1\">";
echo "<tr>";
echo "<th>date</th>";
echo "<th>average</th>";
echo "</tr>";
while (strtotime($date) <= strtotime($today)) {
$from_date = date ("Y-m-d", strtotime("-29 day", strtotime($date)));
$query = $conp->query("SELECT ROUND(SUM(OutCount)/30) AS 'average' FROM inventory
LEFT JOIN item
ON inventory.itemcode = item.itemcode
WHERE item.subcategory = '$subcategory'
AND TrDateTime BETWEEN '$from_date' AND '$date' AND transactiontype like 'OUT_%'");
if($query->num_rows){
while($row = mysqli_fetch_array($query, MYSQL_ASSOC)){
if(!empty($row['average'])){
$average = $row['average'];
}else{
$average = "N/A";
}
}
mysqli_free_result($query);
}else{
$average = "N/A";
}
$date = date ("Y-m-d", strtotime("+1 day", strtotime($date)));
echo "<tr>";
echo "<td>" . $date . "</td>";
echo "<td>" . $average . "</td>";
echo "</tr>";
}
echo "</table>";
?>
I get all the dates in the past 30 days (including today) and the average sales from a range of 29 days prior until that date.
+------------+----------+
| date | average |
+------------+----------+
| 2015-04-09 | 222 |
| 2015-04-10 | 225 |
| 2015-04-11 | 219 |
| ... | ... |
+------------+----------+
I am able to get everything I need this way, but it is running 29 queries in this situation and MySQL would be substantially quicker. I started to come up with a MySQL procedure, but I am not sure how well this will work when I try and call it with PHP.
DELIMITER //
CREATE PROCEDURE average_daily_sales()
BEGIN
SET #today = CURDATE();
SET #date_var = CURDATE() - INTERVAL 29 DAY;
SET #from_date = #date_var - INTERVAL 29 DAY;
SET #to_date = #from_date + INTERVAL 29 DAY;
label1: WHILE #date_var < #today DO
SELECT DATE_FORMAT(trdatetime, '%Y-%m-%d') as 'date', ROUND(SUM(OutCount)/30) AS 'average'
FROM inventory
LEFT JOIN item
ON inventory.itemcode = item.itemcode
WHERE item.subcategory = 'T-Shirts'
AND trdatetime BETWEEN #from_date - INTERVAL 29 DAY AND #to_date
AND transactiontype like 'OUT_%';
SET #date_var = #date_var + INTERVAL 1 DAY;
END WHILE label1;
END; //
DELIMITER ;
Ultimately, I would prefer a regular MySQL statement that I can use to produce the desired result table in one shot. Any help would be greatly appreciated.
Do you have data on each distinct day in the range? If so, this is a slightly complex join operation, but very doable.
You can get the date ranges you need as follows:
SELECT DISTINCT
DATE(trdatetime)- INTERVAL 30 DAY AS startdate,
DATE(trdatetime) AS enddateplus1
FROM inventory
WHERE trdatetime >= NOW() - INTERVAL 31 DAY
Debug this query. Take a look to make sure you get each date range you want.
Then you can join this to your business query like so
SELECT dates.startdate,
ROUND(SUM(OutCount)/30) AS 'average'
FROM (
SELECT DISTINCT
DATE(trdatetime)- INTERVAL 30 DAY AS startdate,
DATE(trdatetime) AS enddateplus1
FROM inventory
WHERE trdatetime >= NOW() - INTERVAL 31 DAY
) dates
LEFT JOIN inventory ON i.trdatetime >= dates.startdate
AND i.trdatetime < dates.enddateplus1
LEFT JOIN item ON i.itemcode = item.itemcode
WHERE item.subcategory = 'T-Shirts'
AND transactiontype like 'OUT_%'
GROUP BY dates.startdate
If your inventory data is sparse, that is, you don't have transactions on all days, then your dates query will be missing some rows.
There's a way to fill in those missing rows. But it's a pain in the s. Read this for more info. http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/
Notice that BETWEEN works very badly indeed for filtering DATETIME or TIMESTAMP values.
If you create a calender table and populate that with a range of date values, e.g.
CREATE TABLE cal (dt DATE NOT NULL PRIMARY KEY) ;
INSERT INTO cal VALUES ('2015-04-01'),('2015-04-02'),('2015-04-03'), ... ;
you could use that as a row source, in a query like this:
SELECT cal.dt
, ( -- correlated subquery references value returned from cal
SELECT ROUND(SUM(n.OutCount)/30)
FROM inventory n
JOIN item t
ON t.itemcode = n.itemcode
WHERE t.subcategory = 'foo'
AND n.TrDateTime >= cal.dt + INTERVAL -28 DAY
AND n.TrDateTime < cal.dt + INTERVAL 1 DAY
AND n.transactiontype LIKE 'OUT_%'
) AS `average`
FROM cal
WHERE cal.dt >= '2015-04-01'
AND cal.dt < '2015-05-01'
ORDER BY cal.dt
It's not mandatory to create a cal calendar table. We could use an inline view and give it an alias of cal. For example, in the query above, we could replace this line:
FROM cal
with this:
FROM ( SELECT DATE('2015-04-01') AS dt
UNION ALL SELECT DATE('2015-04-02')
UNION ALL SELECT DATE('2015-04-03')
UNION ALL SELECT DATE('2015-04-04')
UNION ALL SELECT DATE('2015-04-05')
) cal
Or, if you have a rowsource that can give you a contiguous series of integers, starting at zero up t you could manufacture your date values from a base date, for example
FROM ( SELECT '2014-04-01' + INTERVAL i.n DAY
FROM source_of_integers i
WHERE i.n >= 0
AND i.n < 31
ORDER BY i.n
) cal
Some notes:
The original query shows an outer (LEFT) join, but the equality predicate in the WHERE clause negates the "outerness" of the join, it's equivalent to an inner join.
Some of the column references in the query are not qualified. Best practice is to qualify all column references, then the reader can understand which columns are coming from which tables, without requiring the reader to be familiar with which columns are in which tables. This also protects the statement from breaking in the future (with an "ambiguous column" error) when a column that has the same name is added to another table referenced in the query.)
FOLLOWUP
Personally, for a limited number of date values, I'd go with the inline view that doesn't reference a table. I'd have the PHP code generate that query for me.
With a starting date, say it's '2015-04-10', I'd take that date value and format it into a query, equivalent doing this:
$cal = "SELECT DATE('2015-04-10') AS dt" ;
Then I'd spin through a loop, and increment that date value by 1 day. Each time through the loop, I'd appending to $cal a select of the next date, the net effect of running through the loop three times would be equivalent to doing this:
$cal .= " UNION ALL SELECT DATE('2015-04-11')";
$cal .= " UNION ALL SELECT DATE('2015-04-12')";
$cal .= " UNION ALL SELECT DATE('2015-04-13')";
As a less attractive alternative, we could keep repeating the same value of the start date, and just increment an integer value, and let MySQL do the date math for us.
$cal .= " UNION ALL SELECT '2015-04-10' + INTERVAL 1 DAY";
$cal .= " UNION ALL SELECT '2015-04-10' + INTERVAL 2 DAY";
$cal .= " UNION ALL SELECT '2015-04-10' + INTERVAL 3 DAY";
Then, I'd just slide the $cal query into the SQL text as an inline view query. Something like this:
$sql = "SELECT cal.dt
, ( SELECT IFNULL(ROUND(SUM
,0) AS average_
FROM ( " . $cal . " ) cal
LEFT
JOIN item ON ... ";
Anyway, that's the approach I'd take if this was for a limited number of date values (a couple dozen or so), and if I was only going to be running this query occasionally, not hammering the database server with this query repeatedly, for every request.) If I was going to pound the server, I'd create and maintain a real cal table, rather than incur the overhead of materializing a derived table on every query.
The suggestions from #OllieJones and #spencer7593 either required a 'transaction' to take place every day in order to utilize SELECT DISTINCT DATE(trdatetime), you needed to create another table, or you needed to generate a derived table.
SELECT DISTINCT DATE(trdatetime) wasn't an option for me because I did not have transactions for everyday.
The hybrid PHP and MySQL example that #spencer7593 suggested would generate a derived table very well. In the end it took the static version about 1.8 seconds to get a result. The issue being that you would need additional PHP to generate this... (see #spencer7593 answer)
SELECT cal.dt
, ( -- correlated subquery references value returned from cal
SELECT ROUND(SUM(n.OutCount)/30)
FROM inventory n
JOIN item t
ON t.itemcode = n.itemcode
WHERE t.subcategory = 'foo'
AND n.TrDateTime >= cal.dt + INTERVAL -28 DAY
AND n.TrDateTime < cal.dt + INTERVAL 1 DAY
AND n.transactiontype LIKE 'OUT_%'
) AS `average`
FROM ( SELECT DATE('2015-04-01') AS dt
UNION ALL SELECT DATE('2015-04-02')
UNION ALL SELECT DATE('2015-04-03')
UNION ALL SELECT DATE('2015-04-04')
UNION ALL SELECT DATE('2015-04-05')
UNION ALL SELECT DATE('2015-04-06')
etc...
) cal
WHERE cal.dt >= '2015-04-01'
AND cal.dt < '2015-05-01'
ORDER BY cal.dt
I am attempted to use another one of #spencer7593 answers. I created a "source of integers" table with the numbers 0-31 as he suggested. This method took a little over 1.8 seconds.
SELECT cal.sd, cal.ed
, ( -- correlated subquery references value returned from cal
SELECT ROUND(SUM(n.OutCount)/30)
FROM inventory n
JOIN item t
ON t.itemcode = n.itemcode
WHERE t.subcategory = 'foobar'
AND n.TrDateTime >= cal.ed + INTERVAL -30 DAY
AND n.TrDateTime < cal.ed + INTERVAL 1 DAY
AND n.transactiontype LIKE 'OUT_%'
) AS `average`
FROM ( SELECT (CURDATE() + INTERVAL -30 DAY) + INTERVAL i.n DAY as `ed`, (((CURDATE() + INTERVAL -30 DAY) + INTERVAL i.n DAY) + INTERVAL - 30 DAY) as `sd`
FROM source_of_integers i
WHERE i.n >= 0
AND i.n < 31
ORDER BY i.n
) cal
WHERE cal.ed >= CURDATE() + INTERVAL -29 DAY
AND cal.ed <= CURDATE()
ORDER BY cal.ed;
You need a rowsource for these dates, there isn't really a way around that. In the end I made a cal table..
CREATE TABLE cal (
dt DATE NOT NULL PRIMARY KEY
);
CREATE TABLE ints ( i tinyint );
INSERT INTO ints VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
INSERT INTO cal (dt)
SELECT DATE('2010-01-01') + INTERVAL a.i*10000 + b.i*1000 + c.i*100 + d.i*10 + e.i DAY
FROM ints a JOIN ints b JOIN ints c JOIN ints d JOIN ints e
WHERE (a.i*10000 + b.i*1000 + c.i*100 + d.i*10 + e.i) <= 3651
ORDER BY 1;
And then ran a slightly modified version of #spencer7593 answer on it..
SELECT cal.dt
, ( -- correlated subquery references value returned from cal
SELECT ROUND(SUM(n.OutCount)/30)
FROM inventory n
JOIN item t
ON t.itemcode = n.itemcode
WHERE t.subcategory = 'foo'
AND n.TrDateTime >= cal.dt + INTERVAL -28 DAY
AND n.TrDateTime < cal.dt + INTERVAL 1 DAY
AND n.transactiontype LIKE 'OUT_%'
) AS `average`
FROM cal
WHERE cal.dt >= CURDATE() + INTERVAL -30 DAY
AND cal.dt < CURDATE()
ORDER BY cal.dt;
In my opinion, I believe this is the cleanest (less PHP) and highest performing answer.
Here is how I indexed the inventory table to speed it up substantially:
ALTER TABLE inventory ADD KEY (ItemCode, TrDateTime, TransactionType);
Thank you #OllieJones and #spencer7593 for all of your help!
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/
SELECT
doctors. fullname,
dutyroster.date,
dutyroster.time
FROM
dutyroster
INNER JOIN doctors ON doctors.docid = dutyroster.docid
WHERE doctors.docid = $doc_id AND
dutyroster.date = DATE(NOW()) AND DATE(NOW())+ INTERVAL 1 DAY
ORDER BY dutyroster.`date` ASC";
this query is used to find specific doctors information from a table called dutyroster. i want to get the docs shedule information for current day and tommrow only.. but this doesnt work.
and i made a second one which is also not working since it returns current one and all the next dates also
SELECT
doctors. fullname,
dutyroster.date,
dutyroster.time
FROM
dutyroster
INNER JOIN doctors ON doctors.docid = dutyroster.docid
WHERE doctors.docid = $doc_id AND
DATE_SUB(CURDATE(),INTERVAL 2 DAY) <= dutyroster.date
ORDER BY dutyroster.`date` ASC"
Instead of
... AND dutyroster.date = DATE(NOW()) AND DATE(NOW())+ INTERVAL 1 DAY
try
... AND (dutyroster.date = CURDATE() OR
dutyroster.date = CURDATE() + INTERVAL 1 DAY))
or in more concise way, as #MarcM suggested
... AND dutyroster.date IN (CURDATE(), CURDATE() + INTERVAL 1 day)
From your first attempt it almost looks like you are trying to program COBOL!
Also, for future reference "this doesn't work" is not a helpful comment. You should say what actually happens.
Anyway, try changing your where clause to either:
WHERE doctors.docid = $doc_id AND
(dutyroster.date = CURRENT_DATE OR dutyroster.date = CURRENT_DATE + INTERVAL 1 DAY)
or:
WHERE doctors.docid = $doc_id AND
dutyroster.date IN (CURRENT_DATE, CURRENT_DATE + INTERVAL 1 DAY))
I need a little help with row count. i manage to add today and total members count (rows). i want to count this week and this month. can anyone point me out how to do it? thanks.
$result = mysql_query("SELECT * FROM members");
$num_rows = mysql_num_rows($result);
echo "$num_rows Members\n";
$utoday = date("j. n. Y");
$today = mysql_query("SELECT * FROM mambers WHERE date='$utoday' ");
$num_today = mysql_num_rows($today);
echo "$num_today Members\n";
If you stored the date as a type date, you can use the mysql built-in time functions.
For example, you can group by MONTH(date).
If you want to count for this week starting from the most recent Monday:
SELECT COUNT(1) WeekCount
FROM members A,
(
SELECT
(MondayDate + INTERVAL 0 SECOND) PastMonday,
((MondayDate + INTERVAL 7 DAY) + INTERVAL 0 SECOND) NextMonday
FROM
(SELECT DATE(NOW() - INTERVAL WEEKDAY(NOW()) DAY) MondayDate) AA
) B
WHERE date >= PastMonday AND date < NextMonday
;
If you want to count for this month starting from the 1st query this:
SELECT COUNT(1) MonthCount
FROM members A,
(
SELECT FirstOfThisMonth,
((FirstOfThisMonth + INTERVAL 32 DAY) - INTERVAL (DAY(FirstOfThisMonth + INTERVAL 32 DAY)-1) DAY) FirstOfNextMonth
FROM
(
SELECT (DATE(NOW() - INTERVAL (DAY(NOW())-1) DAY) + INTERVAL 0 SECOND) FirstOfThisMonth
) AA
) B
WHERE date >= FirstOfThisMonth AND date < FirstOfNextMonth
;
Give it a Try !!!