SQL WeekOfYear() stops at end of the year. Alternatives? - php

I have a query that gets executed based on the date range the user chooses. For example: 12-12-2019 to 1-13-2020.
// Retrieve count of attendance, no shows and cancellations per user selected category and sort by week number
$q = "SELECT YEARWEEK(`start_time`, 0) AS weekno,
SUM(`is_no_show` = 0 AND `is_cancelled` = 0) as attended,
SUM(`is_no_show` = 1) AS no_shows,
SUM(`is_cancelled` = 1)AS cancelled
FROM `myTable`
WHERE (`start_time` > :start_date AND `start_time` < :end_date)
AND category LIKE :cohort_type
GROUP BY weekno";
My issue is that this query stops pulling in data after 12-23-2019. It seems to stop at the last week of the year and not go into 2020 as week 1. How do I account for this? Any suggestions or tips is greatly appreciated!
Thank you.
General DB Structure:
+------------+-----------+-----------+
| start_time | no_shows | cancelled |
+------------+-----------+-----------+
| 2019-12-20 | 1 | 0 |
| 2019-12-21 | 0 | 0 |
| 2019-12-22 | 0 | 1 |
GOAL: I want to SUM the data on a weekly basis
EDIT: YEARWEEK() skips the first week of 2020 and goes straight to week 2.

Your query is missing a GROUP BY criteria on the year; as of now, it will mix weeks belonging to different years, which I assume is not what you want. I would suggest using YEARWEEK(), which takes the year in account.
Also, your ilter on the date seems akward, as it is mixing parameters and string concatenation; you can use half-open intervals instead (and proper parameter bindings).
Consider:
SELECT
YEARWEEK(`start_time`) AS weekno,
SUM(`is_no_show` = 0 AND `is_cancelled` = 0) attended,
SUM(`is_no_show` = 1) no_shows,
SUM(`is_cancelled` = 1) cancelled
FROM `attendee_categories_appts_joined`
WHERE
`start_time` > :start_date
AND `start_time` < :end_date
AND category LIKE :cohort_type
GROUP BY weekno

Related

How to get Sale data of every month of a year

I'm trying to get Sale Data of every month of particular year, but I'm having a problem building a query for it.
Here is What I've tried
SELECT COUNT(`id`) AS `total_order`
FROM orders
WHERE date BETWEEN ? AND ?
GROUP BY `total_order`
HERE is How my table look like
----------------------------------------
| id | item_name | amount | time |
| 21 | item_1 | 10 | 1506675630 |
| 22 | item_2 | 30 | 1506675630 |
| 23 | item_3 | 70 | 1506675630 |
| 24 | item_4 | 100 | 1506675630 |
----------------------------------------
Now here is what i want from the query
1 - Total Sales amount made from the beginning of the year till today.
2 - Sales made Today
3 - Sales made in Last Month
4 - Sales Made in Last 3 month
5 - Sales Made in Last 6 Month
6 - Total Number of Sales made in every month of this particular year
for e.g -
January - 20
Feb -100
March - 200 & so on.
How can i achieve this complex query?
SELECT `id` AS `Order_Number`, item_name, SUM(Amount)
FROM orders
WHERE time >= '01/01/17'
GROUP BY date
That would give you your first result. Try the others and let me know what you get
This answers your first question
SELECT
DATE_FORMAT(FROM_UNIXTIME(time), '%Y-%m') as interval,
COUNT(*) AS sales_made,
SUM(amount) AS sales_amount
FROM orders
WHERE
time BETWEEN UNIX_TIMESTAMP('2017-01-01') AND UNIX_TIMESTAMP('2018-01-01')
GROUP BY 1
ORDER BY 1;
Here is what i think would work:
for the first 5 queries try something like this.
SELECT SUM(amount)
FROM orders
WHERE DATE_FORMAT(FROM_UNIXTIME(`orders.time`), '%Y-%m-%d') BETWEEN ? AND ?
And for the last one you'll need :
SELECT DATE_FORMAT(FROM_UNIXTIME(`orders.time`), '%Y-%m-%d') AS 'date', SUM(amount)
FROM orders
WHERE date between ? AND ?
GROUP BY DATE_FORMAT(date, '%Y%m')
You can try without the FROM_UNIXTIME if it doesnt work.

How to check given date period is between two dates in MySQL and PHP

I have a travel history of the employee. I want to check, for the particular month, whether he is in outstation (traveled outside) or in the office, to calculate number of hours in travel. Just we are maintaining the travel database, in that we entered employee name with client place traveled with travel date and returned date.
One employee have the following data:
Traveled date: '2015-08-29' (29th Aug 2015)
returned date: '2015-11-06' (6th Nov 2015)
So here, I want to check in the month of October, all employees that are out of the office. Obviously this guy should come in that category, but I could not get him.
I also tried directly in MySQL workbench, but I didn't get the result.
My original PHP code:
// $req['date_start'] = '2015-10-01'
// $req['date_end'] = '2015-10-31'
$employeeTravel = new EmployeeTravelRecord();
$TravelEntryList = $employeeTravel->Find("(travel_date between ? and ? or return_date between ? and ? )",array($req['date_start'], $req['date_end'],$req['date_start'], $req['date_end']));
$startdate = $req['date_start'];
$enddate = $req['date_end'];
foreach($TravelEntryList as $Travelentry){
$key = $Travelentry->employee;
if($startdate >= $Travelentry->travel_date)
{
$firstdate = $startdate;
}
else
$firstdate = $Travelentry->travel_date;
if($enddate <= $Travelentry->return_date )
{
$lastdate = $enddate;
}
else
$lastdate = $Travelentry->return_date;
$holidays = $this->getholidays($firstdate,$lastdate);
$totalhours = $this->getWorkingDays($firstdate,$lastdate,$holidays); //It returns in total time of outstation in hours excluding company holidays
$amount = $totalhours;
if(isset($TravelTimeArray[$key])){
$TravelTimeArray[$key] += $amount;
}else{
$TravelTimeArray[$key] = $amount;
}
}
But my input data doesn't retrieve that particular employee record, because both traveled date and returned date don't fall in my input dates.
MySQL Workbench:
SELECT * FROM employeetravelrecords where travel_date between '2015-10-01' and '2015-10-31' or return_date between '2015-10-01' and '2015-10-31';
I got only the following result:
+----+----------+---------------+----------------+----------+------------------+------------------+----------------+
| id | employee | type | project | place | travel date | return date | details |
+----+----------+---------------+----------------+----------+------------------+------------------+----------------+
| 13 | 38 | International | PVMTC Training | Hongkong | 11/10/2015 13:33 | 28/11/2015 13:33 | PVMTC Training |
| 14 | 48 | International | PVMT | VIETNAM | 10/10/2015 9:28 | 1/1/2016 9:28 | PETRO |
| 17 | 57 | International | PVMT | Saudi | 10/10/2015 11:39 | 2/1/2016 11:39 | |
+----+----------+---------------+----------------+----------+------------------+------------------+----------------+
The following record didn't get retrieved by this query:
+---+----+---------------+------+-----+-----------------+-----------------+-----------------+
| 7 | 22 | International | MOHO | XYZ | 29/8/2015 18:00 | 6/11/2015 18:00 | FOR DDS review |
+---+----+---------------+------+-----+-----------------+-----------------+-----------------+
SELECT * FROM employeetravelrecords
WHERE
return_date >= '2015-10-01' /* start parameter */
and travel_date <= '2015-10-31' /* end parameter */
The logic seems a little mind-bending at first and I've even reordered the comparisons a little from my original comment above. Think of it this way: you need to return after the start of the range and leave before the end of the range in order to have an overlap.
For a longer explanation and discussion you might find this page useful: TestIfDateRangesOverlap
SELECT '2015-08-29' < '2015-10-31' AND '2015-11-06' >= '2015-10-01' on_leave;
+----------+
| on_leave |
+----------+
| 1 |
+----------+
You can use between for get data between two dates. Here is the working example.
Here is my table structure :
Table User
user_id user_name created_date modified_date
1 lalji nakum 2016-01-28 17:07:06 2016-03-31 00:00:00
2 admin 2016-01-28 17:25:38 2016-02-29 00:00:00
And here is my mysql query :
Query
SELECT * FROM `user` WHERE created_date between '2015-12-01' and '2016-12-31' or modified_date between '2015-12-01' and '2016-12-31'
Result of above query :
Result
user_id user_name created_date modified_date
1 lalji nakum 2016-01-28 17:07:06 2016-03-31 00:00:00
2 admin 2016-01-28 17:25:38 2016-02-29 00:00:00
You want to find all those people who are not available for the complete duration say between T1 & T2 (and T2 > T1). The query you used will only give users whose start date and return date both lie between the given interval which is not the required output.
So to get the desired output you need to check for all employees who have started their journey on or before T1 and return date is on or after T2 (thus unavailable for complete interval [T1, T2]). So the query you can use is:
SELECT * FROM employeetravelrecords where travel_date <= '2015-10-01' and return_date >= '2015-10-31';
If you want employees who are even partially not available between the given duration then we need employees who started their travel before T1 and have any return date later than T1 (thus making them atleast unavailable for a part of the given interval):
SELECT * FROM employeetravelrecords where travel_date <= '2015-10-01' and return_date > '2015-10-01';

SQL count distinct visits of a customer with opening hours from 10am to 6am

I am writing a customer loyalty software for a club that opens from 10am to 6am everyday. The data is store in MYSQL and I'd like to count the customer's total visits for the month.
I am using count(distinct(date)) but if the player came at 5pm and stayed till 3am with 2 transactions at 10pm and 2am. It will be counted as 2 visits instead of 1.
I have a transaction table with the columns listed below:
ps: anything in the brackets () is not real data. I get about 2000 transactions a day. I am also able to change the table structure
Transaction_ID | Date(not Date/Time) | Customer_ID | Item | price | timestamp
1 | 11-06-2015 (6pm) | Jane | drink| 2.00 | 156165166
2 | 09-06-2015 (2pm) | Jane | drink| 2.00 | 1433858493
3 | 10-06-2015 (3am) | Jane | drink| 2.00 | 1433906073
4 | 06-06-2015 (6pm) | Jane | drink| 2.00 | 156165166
Current code returns {4, Jane}. The answer I'm looking for is {3,Jane}. Transaction {2,3} should be considered as one visit
SELECT count(distinct(Date)) as visit, Customer_ID
FROM transaction
GROUP BY Customer_ID
WHERE timestamp BETWEEN $timestamp1 AND $timestamp2
$timestamp1 = strtotime("first day of february +10am");
$timestamp2 = strtotime("first day of march +6am");
How do you suggest to accurately count the total visits below? I am able to change the table structure from Date to Date/time.
The easiest answer with least changes to my codes.
SELECT count(DISTINCT(DATE(DATE_SUB(from_unixtime(timestamp),INTERVAL 6 HOUR))) as visit, Customer_ID
FROM transaction
GROUP BY Customer_ID
WHERE timestamp BETWEEN $timestamp1 AND $timestamp2
The easiest way is to shift your datetime (date,timestamp?) field back for 6 hours in a SQL statement and then you will get an interval in one day from 4AM to 12PM:
DISTINCT(DATE(DATE_SUB(dt,INTERVAL 6 HOUR)))
SQLFiddle demo
Here is the code you need:
SELECT
Customer_ID 'Customer ID'
, COUNT(DISTINCT visit) as 'Visits per month'
, MONTH(visit) 'Month'
, YEAR(visit) 'Year'
FROM
(SELECT
*
, CASE
WHEN (t_timestamp > Date_StartDate AND t_timestamp < Date_EndDate)
THEN d_date
WHEN (t_timestamp < Date_StartDate)
THEN date_add(d_date, INTERVAL -1 DAY)
END 'visit'
FROM
(SELECT *
, DATE_ADD(CAST(d_date AS DATETIME), INTERVAL 10 HOUR) Date_StartDate
, DATE_ADD(DATE_ADD(cast(d_date AS DATETIME), INTERVAL 6 HOUR), INTERVAL 1 DAY) Date_EndDate
FROM transactions) Results
) Results
GROUP BY customer_id, month(visit), year(visit)
Also, here is a SQLFiddle with the results of the code.
I haven't used the exact format for your Customer_ID (I've used INTEGER instead of VARCHAR) and didn't use the exact dates you used in your example, but obviously it should work for anything.
Consider adjusting the name of the columns used in my query to the appropriate column names and you should be fine.

Return how many records created, closed, and open for each day in a time period

I have a table that stores creation and closure dates. What I'm trying to do is run a query that gives me in a given time period, for each day, how many records were created, how many were closed, and how many were open. Table structure:
+------------------------+
| Field | Structure |
+------------+-----------+
| id | int |
| start_date | datetime |
| close_date | datetime |
+------------+-----------+
The first two queries are not a problem - what I'm working with right now is this:
SELECT COUNT(*), start_date FROM dates
WHERE start_date BETWEEN 'start' AND 'end'
GROUP BY start_date
ORDER BY start_date ASC
SELECT COUNT(*), close_date FROM dates
WHERE close_date BETWEEN 'start' AND 'end'
GROUP BY close_date
ORDER BY close_date ASC
The third query is an issue - I can get it to give me how many were open on a day where that day exists in the table, but what I haven't figured out is how to make it write out a row for every day in the year and give me the count for that day.
That's the first problem.
The second is that I want to get it to give me a result like this, and I can't figure out how to do it - searching around here has given me a few false starts, but nothing solid.
+------------+--------+--------+--------+
| Date | Opened | Closed | Active |
+------------+--------+--------+--------+
| 2012-05-06 | 3 | 2 | 7 |
| 2012-05-07 | 2 | 0 | 9 |
| 2012-05-08 | 0 | 0 | 9 |
| 2012-05-09 | 0 | 3 | 6 |
+------------+--------+--------+--------+
I'm entirely willing to run separate queries if necessary and handle all of it in code instead of db if I have to - I'm fairly sure I can write all of the query data from the individual queries to a single array and just loop through that to build the report, but I'd really prefer not. Any suggestions?
This query is tricky because you want the status list for every date in range.
In this query the dates are generated in join query.
For that i used the same table assuming it has
count of records >= the number of dates between daterange
id-s must be consecutive up to number of dates between daterange
You can replace that with your own dates list generation subquery.
set #start :='2012-05-06';
set #end :='2012-05-09';
select cdate as Date
, sum(start_date = cdate) Opened
, sum(close_date = cdate or close_date is null) Closed
, sum(start_date <= cdate and (close_date>cdate or close_date is null)) Active
from dates
join (select date(#start + interval id-1 day) cdate from dates where id <= to_days(#end)-to_days(#start)+1) d
on start_date <= cdate and (close_date>=cdate or close_date is null)
where start_date <= #end
and (close_date >= #start or close_date is null)
group by cdate
order by cdate
;
Creating an aggregate table like this is really difficult with just SQL statements. You will need to use subqueries, UNION statements and/or possibly a stored procedure to get the logic spot on so that it displays the table as you present it.
The two queries that you have already are much faster than the approach I mentioned above. A PHP array will definitely help there to get what you need.
So:
Step 1
SELECT COUNT(*) AS active_before_start
WHERE start_date < 'start'
AND close_date IS NULL
Get that number and store it in $active_before_start.
Step 2
SELECT COUNT(*) AS total_opened, start_date FROM dates
WHERE start_date BETWEEN 'start' AND 'end'
GROUP BY start_date
ORDER BY start_date ASC
Put these results in an array
foreach ($results as $result)
{
$key = $result->start_date;
$final_table[$key]['date'] = $key;
$final_table[$key]['opened'] = $result->total_opened;
}
Step 3
SELECT COUNT(*) AS total_closed, close_date FROM dates
WHERE close_date BETWEEN 'start' AND 'end'
GROUP BY close_date
ORDER BY close_date ASC
Again store those in the array
foreach ($results as $result)
{
$key = $result->close_date;
$final_table[$key]['date'] = $key;
$final_table[$key]['closed'] = $result->total_closed;
}
Step 4
Now you can traverse the table to manipulate the data so that it produces the result you need as such:
$results_table = array();
$active = $active_before_start;
foreach ($final_table as $key => $item)
{
$opened = (isset($item['opened']) ? $item['opened'] : 0;
$closed = (isset($item['closed']) ? $item['closed'] : 0;
$active = $active + ($opened' - $closed);
$results_table[$key]['date'] = $key;
$results_table[$key]['opened'] = $opened;
$results_table[$key]['closed'] = $closed;
$results_table[$key]['active'] = $active;
}
From then on you can always do a ksort just in case to show the sorted array results.
HTH
I can get it to give me how many were open on a day where that day exists in the table, but what I haven't figured out is how to make it write out a row for every day in the year and give me the count for that day.
I understand it as : you want to count the issue for each close date?
SELECT COUNT(*) FROM dates WHERE your_condition GROUP BY DAY_OF_YEAR(close_date)

SELECT rows WHERE next n rows satisfy CONDITIONS

Let's say we have this resource availability table:
+-----------+-----------------------------------------------------------+
| date | obvious, the date |
| timeslot | we have 12 fixed 2-hour timeslots so this will be 1 to 12 |
| r1 | number of resource type 1 available during this timeslot |
| r2 | same, for resource type 2 |
| r3 | same, for resource type 3 |
+-----------+-----------------------------------------------------------+
Now, I want to see all the available timeslots I can use to do job #43. For this job I need 2 units of r1, one unit of r2, and three units of r3. Assuming the job will need one timeslot I can use this query:
SELECT `date`, `timeslot` FROM `resource_availability`
WHERE
`r1` > '1' AND
`r2` > '0' AND
`r3` > '2'
ORDER BY 'date`, `timeslot`;
However, if I have another job, job #86 which takes 3 timeslots to complete and could not be stopped-restarted, then is it possible to get safe start times with a query?
I am currently checking continuity in my while loop, but thought it might be possible to have the query do that.
If that is possible, I would like to know which is quicker and more efficient. For efficacy evaluation, it should be noted that this table, being a sort of bitmap gets updated quite frequently - i.e. with each job being scheduled the resource availability columns get updated.
Also, it is fairly obvious the purpose of this system is to allow examining what-ifs. If my approach is not optimal, what better alternatives there are?
Should the last question be one too many, please ignore it, or let me know in the comments and I'd delete it.
Whew... I put together an idea that may get you what you want. Forgive me if it takes a bit to understand, but I hope you see that it's actually a fairly straightforward solution to a moderately complex problem.
I would build the query (in PHP) to have n self-joins where n is the number of time slots needed for the job. The self-joins join the next consecutive timeslot, and the results are thinned based on resources being available in all the slots. Note that you could move the dynamically-created WHERE clauses into the JOIN conditions... I have seen versions of MySQL which will improve speed that way.
php code:
// $r1, $r3, and $r3 are the required resources for this job.
$join_format = 'JOIN timeslots AS %s ON %date = %s.date AND %s.timeslot+1 = %s.timeslot';
$where_format = '(%s.r1 >= '.$r1.' AND %s.r2 >= '.$r2.' AND %s.r3 >= '.$r3.')';
$joins = array();
$wheres = array("block1.date > CURDATE()",
sprintf($where_format, "block1", "block1", "block1")
);
$select_list = 'block1.date, block1.timeslot as starting_time, block' . $slots_needed . '.timeslot as ending_time';
for($block = 2; $block <= $slots_needed; $block++) {
$join_alias = "block" . $block;
$previous_alias = "block" . ($block-1);
$joins[] = sprintf($join_format, $join_alias, $previous_alias,$join_alias, $previous_alias, $join_alias);
$wheres[] = sprintf($where_format, $join_alias, $join_alias, $join_alias);
}
$query_format = 'SELECT %s FROM timeslots as block1 %s WHERE %s GROUP BY block1.date, block1.timeslot ORDER BY block1.date ASC, block1.timeslot ASC';
$joins_string = implode(' ', $joins);
$wheres_string = implode(' AND ', $wheres);
$query = sprintf($query_format, $select_list, $joins_string, $wheres_string);
To the best of my intention, that should yield a query like this (for 2 needed blocks with 1 each of the resources needed:
resultant SQL:
SELECT
block1.date,
block1.timeslot as starting_time,
block2.timeslot as ending_time
FROM
timeslots AS block1
JOIN timeslots AS block2
ON block1.date = block2.date AND block1.timeslot+1 = block2.timeslot
WHERE
block1.date > CURDATE()
AND (block1.r1 >= 1 AND block1.r2 >= 1 AND block1.r3 >= 1)
AND (block2.r1 >= 1 AND block2.r2 >= 1 AND block2.r3 >= 1)
GROUP BY
block1.date, block1.timeslot
ORDER BY
block1.date ASC, block1.timeslot ASC
and it should yield results such as:
expected result set:
+------------+---------------+-------------+
| date | starting_time | ending_time |
+------------+---------------+-------------+
| 2001-01-01 | 1 | 2 |
+------------+---------------+-------------+
| 2001-01-01 | 2 | 3 |
+------------+---------------+-------------+
| 2001-01-01 | 7 | 8 |
+------------+---------------+-------------+
| 2001-01-01 | 8 | 9 |
+------------+---------------+-------------+
| 2001-01-02 | 4 | 5 |
+------------+---------------+-------------+
Notice that if there are 2 blocks needed, but 3 available (consecutively), the query will return both options (the first and second OR the second and third available times).

Categories