Currently, I got this:
SELECT `deals`.*, deals_bookings.date AS 'b_date', deals_bookings.id AS 'book_id'
FROM `deals_bookings`
INNER JOIN deals ON (deals.ID = deals_bookings.deal_id)
INNER JOIN users ON (users.id = deals.partner_id)
INNER JOIN deals_bookings_times ON (deals_bookings_times.book_id = deals_bookings.id)
WHERE 1 AND deals_bookings.date = CURDATE() AND users.company_type = 'restaurant'
This grabs deals, that has a booked date for today (CURDATE()).
Now I have added a column: deals_bookings.everyWeekDay
The plan for that column is to store week number -> between 1-7 where 1 is monday (first day in week) and 7 is sunday.
Is it possible by pure mysql query to select all deals that has either deals_bookings.date = CURDATE() OR if deals_bookings.everyWeekDay = todays week number ?
YOu want to use
deals_bookings.everyWeekDay = WEEKDAY(NOW())
But WEEKDAY returns Monday - 0, Sunday - 6; so we have to add 1
Combining the condition
WHERE (`deals_bookings`.`date` = CURDATE() OR `deals_bookings`.`everyWeekDay` = WEEKDAY(NOW()) + 1 ) AND `users`.`company_type` = 'restaurant'
Related
This question already has answers here:
MySQL how to fill missing dates in range?
(6 answers)
MySQL: Select All Dates In a Range Even If No Records Present
(6 answers)
Closed 2 years ago.
I have the following query that returns the dates first_visit starting from today and 7 days back, as well as the visitors hash per day:
SET time_zone= '{$company_timezone}';
SELECT DATE( first_visit ) AS day , COUNT( DISTINCT hash ) AS total
FROM table
WHERE company = 1 and first_visit > SUBDATE( CURDATE( ) , 7 )
GROUP BY day
The flaw with this is that if company = 1 have visitors only today and three days ago, I will get this:
day --------- total
2020-03-08 ----- 30
2020-03-05 ----- 40
leaving out all other dates inbetween.
What I want is to get all the past 7 days, even there are no visitors at all. If there are no visitors, then it should just show 0.
How to edit my query in order to achieve this?
Thank you
Perform an outer join with a derived table that contains desired dates:
select b.date as day, count(distinct hash) as total
from table
right join (select #now := #now - interval 1 day as date from (select #now := curdate()) a, table limit 7) b
on b.date = date(first_visit) and company = 1
group by b.date
This assumes that table has at least 7 rows.
Note: there are two occurrences of table.
If you have data for each day -- but not for that company -- then conditional aggregation is a pretty simply approach:
SELECT DATE( first_visit ) AS day ,
COUNT( DISTINCT CASE WHEN company = 1 THEN hash END ) AS total
FROM table
WHERE first_visit > SUBDATE( CURDATE( ) , 7 )
GROUP BY day;
This only works if all days are represented in your table for some company.
Some solutions involve a table of numbers.
Here is one way with a recursive query, available in MySQL 8.0:
with d as (select 0 n union all select n + 1 where n < 6)
select
current_date - interval n day myday,
count(distinct t.hash) total
from d
left join mytable t
on t.company = 1
and t.first_visit >= current_date - inteval n day
and t.first_visit < current_date - interval (n - 1) day
group by d.n
In earlier version, you can enumerate the numbers as a derived table:
select
current_date - interval n day myday,
count(distinct t.hash) total
from (
select 0 n union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all select 7
) d
left join mytable t
on t.compatny = 1
and t.first_visit >= current_date - inteval n day
and t.first_visit < current_date - interval (n - 1) day
group by d.n
I want to show records for previous month only, excluding this month's dates.For example, today is February 5th and I want to show records for January 1st to 31st
i have a table- tbl_order_details where I need to fetch all order records by current month and previous month respectively. the column name for date type is orderDate this is what I an doing for fetching rows for current month till date:
SELECT COUNT(1)
FROM tbl_order_details
where merchantCode= '$user_code'
AND MONTH(orderDate) = MONTH(CURRENT_DATE())
AND YEAR(orderDate) = YEAR(CURRENT_DATE())
But I cant figure out how do I show records for january that does not include any records from February
SELECT * FROM tbl_order_details
WHERE YEAR(orderDate) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(orderDate) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)
For writing a PHP code, you can get the Month and Year from PHP itself by using the strtotime function depending upon the input your table takes and then formatting it in your sql query. For eg.:
<?php
$month = date("M", strtotime("previous month"));
$year = date("Y", strtotime("this year"));
$query_get = 'SELECT COUNT(1) FROM tbl_order_details where merchantCode= {$user_code} AND MONTH(orderDate) = {$month} AND YEAR(orderDate) = {$year}'
?>
And further pass $query_get to your DB query to fetch the required result. Or else, you can straight push the following query as #Rohit suggested above.
<?php
$query_get = 'SELECT * FROM tbl_order_details WHERE YEAR(orderDate) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH) AND MONTH(orderDate) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)'
?>
Avoid using DATE (), MONTH (), DAY (), YEAR (), SUBSTR (), LEFT (), RIGHT (), LIKE when mentioning columns in WHERE or JOIN'S because you no longer use the indexes that exist in the columns mentioned. Ex: WHERE YEAR(orderDate) = ... Avoid doing this for the reasons stated above.
I suggest use as follows ...
If your "orderDate" column is of type date, do as follows:
SELECT COUNT(1)
FROM tbl_order_details
where merchantCode= '$user_code'
AND orderDate BETWEEN DATE_ADD(LAST_DAY(NOW() - INTERVAL 2 MONTH), INTERVAL 1 DAY)
AND LAST_DAY(DATE_ADD(LAST_DAY(NOW() - INTERVAL 2 MONTH), INTERVAL 1 DAY));
Will return the first day of the previous month
DATE_ADD(LAST_DAY(NOW() - INTERVAL 2 MONTH), INTERVAL 1 DAY)
Returns the last day of the previous month
LAST_DAY(DATE_ADD(LAST_DAY(NOW() - INTERVAL 2 MONTH), 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/
I got 2 columns, 'create_time' is when the account has been registered and 'last_play' is when the account has logged in the last time. I want to select accounts which have been registered in a specific week / month and have been active within the last 2 days of this specific week / month.
Here is how I select ALL entries of last week without considering last_play (works):
SELECT COUNT(id) FROM account.account
WHERE WEEKOFYEAR(create_time) = WEEKOFYEAR(NOW()) - 1
AND YEAR(create_time) = YEAR(NOW());
That's my current query for last week which doesn't work:
SELECT COUNT(id) FROM account.account
WHERE WEEKOFYEAR(create_time) = WEEKOFYEAR(NOW()) - 1
AND YEAR(create_time) = YEAR(NOW())
AND DATE(last_play) BETWEEN
ADDDATE(DATE(DATE_SUB(NOW(), INTERVAL 1 WEEK)),
INTERVAL 1 - DAYOFWEEK(DATE(NOW())) DAY)
AND DATE(NOW());
Basing on your first working query, you can use the MySQL function WEEKDAY to identify saturdays and sundays:
SELECT COUNT(id) FROM account.account
WHERE WEEKOFYEAR(create_time) = WEEKOFYEAR(NOW()) - 1
AND YEAR(create_time) = YEAR(NOW())
AND WEEKOFYEAR(last_play) = WEEKOFYEAR(create_time) //last_play is in the same week as create_time
AND WEEKDAY(last_play) IN (5,6); //wekkday is saturday or sunday
This gives you entries which were active on the saturday or sunday in the same week they registered.
EDIT: For months, you basically do the same thing, but replace WEEKOFYEAR by MONTH and WEEKDAY by DAYOFMONTH. The last two days of a given month you can find manually by checing for all possible cases:
SELECT COUNT(id) FROM account.account
WHERE MONTH(create_time) = MONTH(NOW()) - 1
AND YEAR(create_time) = YEAR(NOW())
AND MONTH(last_play) = MONTH(create_time) //last_play is in the same MONTH as create_time
AND
(DAYOFMONTH(last_play) IN (30,31) AND MONTH(last_play) IN (1,3,5,7,8,10,12)
OR DAYOFMONTH(last_play) IN (29,30) AND MONTH(last_play) IN (4,6,9,11)
OR DAYOFMONTH(last_play) IN (27,28) AND MONTH(last_play) IN (2))
Never mind a leap year ;-). Or incorporate it again manually by yourself.