Group Query Results by Start of Week - php

I have a few different tables that I'm trying to join aggregate results out of and group them based on different time intervals.
calendar table
+------------+
| adate |
+------------+
| 2015-09-24 |
| 2015-09-25 |
| 2015-09-26 |
| 2015-09-27 |
| 2015-09-28 |
....etc.......
plays table
+----+------------+----------+---------------------+---------------------+
| id | session_id | video_id | created_at | updated_at |
+----+------------+----------+---------------------+---------------------+
| 1 | 1 | 1 | 2015-09-25 15:49:50 | 2015-09-25 15:49:50 |
+----+------------+----------+---------------------+---------------------+
And the PHP/SQL:
switch ( TRUE ) {
case ($interval->days <= 7):
default:
$group_by = 'DATE(dates.adate)';
break;
case in_array( $interval->days, range(8, 31) ):
$group_by = 'CONCAT(YEAR(dates.adate), "/", WEEK(dates.adate))';
break;
case ($interval->days > 31):
$group_by = 'YEAR(dates.adate), MONTH(dates.adate)';
break;
}
$sql = '
SELECT calendar.adate,
IFNULL(g.event_count, 0) event_count
FROM calendar
LEFT JOIN (
SELECT DATE(events.created_at) created_at,
COUNT(*) event_count
FROM plays events
WHERE events.video_id = %d
AND events.created_at >= %s
AND events.created_at < date_add(%s, INTERVAL 1 DAY)
GROUP BY DATE(events.created_at)
) g
ON g.created_at = calendar.adate
WHERE calendar.adate BETWEEN %s AND %s
GROUP BY ' . $group_by . '
ORDER BY adate;
';
I'd like to change the grouping based on the interval length, ie. if it's a week's worth of results then group by each day, a months worth of results should group by "week of 9/28/15", a years worth of results should group by mm/yyyy
Can you show how to group the result set accordingly? I feel like I am close but missing something here.

Related

SQL - Get multiple values when limit 1

If I have a table like this:
ID | ident | product
1 | cucu1 | 99867 |
2 | kkju7 | 88987 |
3 | sjdu4 | 66754 |
4 | kjhu6 | 76654 |
5 | cucu1 | 98876 |
And use this query: SELECT ident,COUNT(*) FROM sales WHERE status=? AND team=? AND DATE(date) = DATE(NOW() - INTERVAL 1 DAY) GROUP BY ident order by COUNT(*) DESC LIMIT 1
I get the value: cucu1, since that has the most rows.
But if my table is like this:
ID | ident | product
1 | cucu1 | 99867 |
2 | kkju7 | 88987 |
3 | sjdu4 | 66754 |
4 | kkju7 | 76654 |
5 | cucu1 | 98876 |
It should return both cucu1 and kkju7, since they are the highest with same count, but still it gives me only cucu1. What am I doing wrong?
You can use rank():
SELECT ident, cnt
FROM (SELECT ident, COUNT(*) as cnt,
RANK() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM sales
WHERE status = ? AND team = ? AND
DATE(date) = DATE(NOW() - INTERVAL 1 DAY)
GROUP BY ident
) i
WHERE seqnum = 1;
LIMIT keyword simply limits the results to 1 row, no matter if there are equal values before or after the returned row.
A better solution would be to select the rows which have a count that's equal to max count in the query, which can be achieved by the following query:
SELECT ident,COUNT(*)
FROM sales
WHERE status=?
AND team=?
AND DATE(date) = DATE(NOW() - INTERVAL 1 DAY)
GROUP BY ident
HAVING COUNT(*) = MAX(COUNT(*))

Split date range and get combined results in mysql

I have two tables
orders_status_history
id | order_id | status | date
1 | 1201 | 2 | 2015-01-20
2 | 1124 | 4 | 2015-02-01
3 | 1245 | 1 | 2015-02-14
4 | 1365 | 2 | 2015-03-10
saved_shipping_invoices
id | order_id | product_id | date
1 | 1348 | 12541 | 2015-12-18
2 | 1298 | 11485 | 2016-01-02
3 | 1319 | 14521 | 2016-05-14
4 | 1441 | 10124 | 2016-05-14
and one year date range i.e.
$start_date = '2015-09-30';
$end_date = '2016-09-30';
Data in table "shipping_invoice_history" started from 2015-12-18. Now I want to break date interval on 2015-12-18. There will be two intervals now
$interval1_start = '2015-09-30';
$interval_end = '2015-12-17';
$interval2_start = '2015-12-18';
$interval2_end = '2016-09-30';
When it breaks the date interval, I want to get data from table "orders_status_history" on the first interval but on the second interval it gets data from "shipping_invoice_history" .Also I need to show the data in single table.
$week = '2015-09-30 ## 2016-09-30';
$week_ranges = explode('##', $week);
if ($week_ranges[0] > '2015-12-18' )
{
$invoices_shipped_qty = tep_db_query("SELECT * FROM `saved_shipping_invoices` WHERE `date_created` BETWEEN '".$week_ranges[0]."' AND '".$week_ranges[1]."'");
$invoices_shipped_arr = tep_db_fetch_array($invoices_shipped_qty);
$total_sent = $invoices_shipped_arr['shiped_qty'];
}
else {
$total_order_status_complete = tep_db_query("SELECT * FROM `orders_status_history` WHERE `orders_status_id` = '17' AND `date_added` BETWEEN '".$week_ranges[0]." 00:00:00' AND '".$week_ranges[1]." 23;59:59'");
$total_order_status_complete_arr = tep_db_fetch_array($total_order_status_complete);
Please suggest me how can i break date interval into two, get result from two tables and display in single run.
Probably this is what you want
select
'order_status_history' as table,
id,
order_id,
status,
null as product_id,
date from order_status_history
where
date between '2015-09-30' and '2015-12-17'
union all
select
'saved_shipping_invoices' as table,
id,
order_id,
null as status,
product_id,
date
from saved_shipping_invoices
where
date between '2015-12-18' and '2016-09-30'
Depending on how dynamic is your query, I like to do something like this instead of union.
I prefer this option:
select
if(date <='2015-12-17', 'Phase 1',
if(date >='2015-12-18', 'Phase 2',
'None')),
id,
order_id,
status,
null as product_id,
date from order_status_history
where
date between '2015-09-30' and '2016-09-30'
or
select
if(date between '2015-09-30' and '2015-12-17', 'Phase 1',
if(date between '2015-12-18' and '2016-09-30', 'Phase 2',
'None')),
id,
order_id,
status,
null as product_id,
date from order_status_history
where
date between '2015-09-30' and '2016-09-30'

Displaying daily totals between now and x days ago using MYSQL/PHP

I have a table called 'orders' and it contains; id, order_total and time fields. 'time' is an integer and stores a unix timestamp...
orders
| id | order_total | time |
-------------------------------------
| 1 | 42.00 | 1443355834 |
| 2 | 13.00 | 1443460326 |
| 3 | 51.00 | 1443468094 |
| 4 | 16.00 | 1443477442 |
| 5 | 10.00 | 1443606966 |
| 6 | 53.00 | 1443608256 |
I want to able to display in a table using php the sum, of 'order_total' for each day for the previous 'x' amount of days (or weeks or months) so it will look something like this:
| Date | Order Total |
---------------------------
| 27/09/15 | 42.00 |
| 28/09/15 | 80.00 |
| 30/09/15 | 63.00 |
I have made a MYSQL query and a php loop that kind of works but being new to MYSQL I am probably over-complicating things and there must be an easier way to do this ? When I say kind of works, it will correctly sum and show the order_totals up until the current day but for some reason will combine the current day with the previous day.
Here is what I currently have:
$x = $interval;
$y = $x - 1;
while ($x > 0) {
$sql10 = "
SELECT id,
time,
SUM(order_total) as sum,
date_format(DATE_SUB(FROM_UNIXTIME($now_time), INTERVAL $x DAY), '%Y-%m-%d') as thedate
FROM $ordersTABLE
WHERE FROM_UNIXTIME(time) BETWEEN date_format(DATE_SUB(FROM_UNIXTIME($now_time), INTERVAL $x DAY),'%Y-%m-%d')
AND date_format(DATE_SUB(FROM_UNIXTIME($now_time), INTERVAL $y DAY),'%Y-%m-%d')";
$result10 = mysql_query ( $sql10, $cid ) or die ( "Couldn't execute query." );
while ( $row = mysql_fetch_array ( $result10) ) {
$order_total = $row ["order_total"];
$thedate = $row ["thedate"];
$sum = $row ["sum"];
$sum = number_format($sum,2);
$thedate = strtotime($thedate);
$thedate = date("d/m/y",$thedate);
print "<tr><td width=\"120\">$thedate</td><td>\$$sum</td></tr>";
}
$x--;
$y--;
}
(The string $now_time contains the current time as a Unix Timestamp hence the converting as the system time can not be changed and this contains the correct local time for the user)
Is there better way to do this ?
You can convert the timestamps into YYYY MM DD using FROM_UNIXTIME function and then select only the ones which are older enough thanks to the DATEDIFF function. Today's date is provided by CURDATE function.
First of all, the query which retrieves the totals for the orders older then the interval and reformats the date fields:
$q1 = "SELECT " . $ordersTABLE . ".order_total AS total, FROM_UNIXTIME(" . $ordersTABLE . ".time, '%Y-%m-%d') AS short_date FROM " . $ordersTABLE . " WHERE DATEDIFF(CURDATE(), short_date) > " . $intervalInDAYS;
Then, the one that sums up the totals of the day:
$q2 = "SELECT short_date AS day, SUM(total) AS total FROM (" . $q1 . ") GROUP BY short_date";
And then you perform your query stored in $q2 and all other operations you need to display the result.
Result from the query should be in form:
| day | total |
===========================
| 25/09/15 | 34.00 |
| 16/09/15 | 100.00 |
| 31/07/14 | 3.20 |
| ... | ... |

MySQL get the sum of all rows without retrieving all of them

This may be a little confusing but please bear with me. Here's the thing:
I have a database that contains ~1000 records, as the following table illustrates:
+------+----------+----------+
| id | date | amount |
+------+----------+----------+
| 0001 | 14/01/15 | 100 |
+------+----------+----------+
| 0002 | 14/02/04 | 358 |
+------+----------+----------+
| 0003 | 14/05/08 | 1125 |
+------+----------+----------+
What I want to do is this:
Retrieve all the records beginning at 2014 and until yesterday:
WHERE `date` > '14-01-01' AND `date` < CURDATE()
But also get the sum of amount up to the current date, this is:
WHERE `date` < CURDATE()
I've already got this working by just selecting all the records based on the second condition, getting the sum, and then excluding those which don't match the first condition. Something like this:
SELECT `id`, `date`, `amount` FROM `table`
WHERE `date` < CURDATE()
And then:
$rows = fetchAll($PDOStatement);
foreach($rows as $row) {
$sum += $row->amount;
if (
strtotime($row->date) > strtotime('14-01-01') &&
strtotime($row->date) < strtotime(date('Y-m-d'))
) {
$valid_rows[] = $row;
}
}
unset $rows;
Is there a way to achieve this in a single query, efficiently? Would a transaction be more efficient than sorting out the records in PHP? This has to be SQL-standard compliant (I'll be doing this on MySQL and SQLite).
Update:
It doesn't matter if the result ends up being something like this:
+------+----------+----------+-----+
| id | date | amount | sum |
+------+----------+----------+-----+
| 0001 | 14/01/15 | 100 | 458 |
+------+----------+----------+-----+
| 0002 | 14/02/04 | 358 | 458 |
+------+----------+----------+-----+
| 0003 | 14/05/08 | 1125 | 458 |
+------+----------+----------+-----+
The worst case would be when the resulting set ends up being the same as the set that gives the sum (in this case appending the sum would be irrelevant and would cause an overhead), but for any other regular cases the bandwith save would be huge.
You can create a special record with your sum and add it at the end of your first query
SELECT * FROM `table` WHERE `date` > '14-01-01' AND `date` < CURDATE()
UNION
SELECT 9999, CURDATE(), SUM(`amount`) FROM `table` WHERE `date` < CURDATE()
Then you will have all your desired record and the record with id 9999 or whatever is your sum
This could be achieved by correlated subquery, something like below:
SELECT *, (SELECT SUM(amount) FROM t WHERE t.date < t1.date) AS PrevAmount
FROM t AS t1
WHERE `date` > '14-01-01' AND `date` < CURDATE()
However it is very unefficient if the number of records is large.
It's hackish, but:
> select * from foo;
+------+------+
| id | val |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
+------+------+
5 rows in set (0.02 sec)
> select * from foo
left join (
select sum(val)
from foo
where id < 3
) AS bar ON 1=1
where id < 4;
+------+------+----------+
| id | val | sum(val) |
+------+------+----------+
| 1 | 1 | 3 |
| 2 | 2 | 3 |
| 3 | 3 | 3 |
+------+------+----------+
Basically, do your summing in a joined subquery. That'll attach the sum result to every row in the outer table's results. You'll waste a bit of bandwidth sending that duplicated value out with every row, but it does get you the results in a "single" query.
EDIT:
You can get the SUM using a LEFT OUTER JOIN.
SELECT t1.`id`, t1.`date`, t2.sum_amount
FROM
`table` t1
LEFT OUTER JOIN
(
SELECT SUM(`amount`) sum_amount
FROM `table`
WHERE `date` < CURDATE()
) t2
ON 1 = 1
WHERE t1.`date` > STR_TO_DATE('01,1,2014','%d,%m,%Y') AND t1.`date` < CURDATE();
This will do what you want it to do...optimizing the subquery is the real challenge:
SELECT id,date,amount,(SELECT SUM(amount) FROM table) AS total_amount
FROM table
WHERE date BETWEEN '14-01-01' AND DATE_ADD(CURDATE(), INTERVAL -1 DAY)

MYSQL 'SELECT' and COUNT "active" user for day in user event table

+----+--------------+------------+--------+
| id | user | date(data) | type |
+----+--------------+------------+--------+
| 3 | 5458848 | 2013-12-19 | SUBSCRIBE |
| 4 | 5458848 | 2013-12-19 | UNSUBSCRIBE |
| 5 | 5458848 | 2013-12-20 | SUBSCRIBE |
| 7 | 5458848 | 2013-12-20 | UNSUBSCRIBE |
| 8 | 7883870 | 2013-12-20 | SUBSCRIBE |
| 9 | 7883870 | 2013-12-23 | UNSUBSCRIBE |
| 10 | 7883870 | 2013-12-24 | SUBSCRIBE |
| 11 | 7883870 | 2013-12-24 | UNSUBSCRIBE |
| 12 | 7883870 | 2013-12-24 | SUBSCRIBE |
+----+---------+------------+-------------+
Hello, I need to know how make query in mysql for watch how many users have active and not-active daily,
example: if one user is SUBSCRIBE and UNSUBSCRIBE in the same day, it mean that for that day, I have a user not-active
+------------+-------------+-------------+
| date(data) | active | inactive |
+------------+-------------+-------------+
| 2013-12-19 | 0 | 1 |
| 2013-12-20 | 1 | 1 |
| 2013-12-23 | 0 | 2 |
| 2013-12-24 | 1 | 1 |
+------------+-------------+-------------+
set #ac:=null;
DROP TABLE IF EXISTS ___active_nums;
CREATE TABLE ___active_nums(
event_id integer,
event_user_id integer,
event_date Date,
number_of_active_users integer,
INDEX USING BTREE (event_date)
)
ENGINE=MEMORY
;
INSERT INTO ___active_nums
SELECT
events.id,
events.user,
events.date,
ifnull( #ac:= ( #ac + if( type='SUBSCRIBE', 1, -1) ), #ac:=if( type='SUBSCRIBE', 1, -1) ) AS active_num
FROM
(
SELECT * FROM events ORDER BY id
)
AS events
;
SELECT
event_date as date,
number_of_active_users,
( SELECT count(DISTINCT event_user_id) FROM ___active_nums WHERE ___active_nums.event_date <= outer_active_nums.event_date )
- number_of_active_users
as number_of_inactive_users
FROM ___active_nums AS outer_active_nums
WHERE event_id IN ( SELECT max(event_id) FROM ___active_nums GROUP BY event_date );
Try this...
SELECT SUM(CASE WHEN type='SUBSCRIBE' THEN 1 ELSE 0 END) as active,
SUM(CASE WHEN type='UNSUBSCRIBE' THEN 1 ELSE 0 END) as inactive
WHERE (date>='2013-12-19' and date<='2013-12-24');
You need the difference between the cumulative number of subscribes and unsubscribes on each date in order to calculate whether someone has subscribed or not. For example, the last user 7883870 has a state of "subscribe" after three events on the same day.
One way to do this is with correlated subqueries:
select t.user, t.date,
(select sum(type = 'SUBSCRIBE') - sum(type = 'UNSUBSCRIBE')
from table t2
where t2.user = t.user and
t2.date <= t.date
) netsubs
from table t
group by t.user, t.date;
WIth this information, we can then determine the state someone is in on a given date.
select date,
sum(case when netsubs > 0 then 1 else 0 end) as active,
sum(case when netsubs = 0 then 1 else 0 end) as inactive
from (select t.user, t.date,
(select sum(type = 'SUBSCRIBE') - sum(type = 'UNSUBSCRIBE')
from table t2
where t2.user = t.user and
t2.date <= t.date
) netsubs
from table t
group by t.user, t.date
) t
group by t.date;
EDIT:
I misunderstood the question. On any given day, you want the number of users that are active or have unsubscribed up to that point.
select date, numacive, numusers - numactive as numinactive
from (select date,
(select count(distinct user)
from table t2
where t2.date <= t.date
) as numusers,
(select sum(type = 'SUBSCRIBE') - sum(type = 'UNSUBSCRIBE')
from table t2
where t2.date <= t.date
) as numactive
from (select distinct date
from table t
) t
) t;

Categories