Count the number of users between certain dates - mysql

I have a table of users with when they signed_up and when they were last_seen:
+--------+-----------+-----------+
| userid | signed_up | last_seen |
+--------+-----------+-----------+
| 1 | 1/1/14 | 1/3/14 |
| 2 | 1/1/14 | 1/5/14 |
| 3 | 1/3/14 | 1/5/14 |
| 4 | 1/6/14 | 1/7/14 |
+--------+-----------+-----------+
Lets assume each user comes to the site on everyday between signed_up and last_seen. I'd like to count how many total users there are on each day. The result I'm looking for is below:
+--------+----------------+
| date | count_of_users |
+--------+----------------+
| 1/1/14 | 2 |
| 1/2/14 | 2 |
| 1/3/14 | 3 |
| 1/4/14 | 2 |
| 1/5/14 | 2 |
| 1/6/14 | 1 |
| 1/7/14 | 1 |
+--------+----------------+
And just for clarification, here's how the numbers were calculated (I don't need this table, this is just an illustration)
+--------+----------------+-------+-------+-------+-------+
| date | count_of_users | user1 | user2 | user3 | user4 |
+--------+----------------+-------+-------+-------+-------+
| 1/1/14 | 2 | 1 | 1 | | |
| 1/2/14 | 2 | 1 | 1 | | |
| 1/3/14 | 3 | 1 | 1 | 1 | |
| 1/4/14 | 2 | | 1 | 1 | |
| 1/5/14 | 2 | | 1 | 1 | |
| 1/6/14 | 1 | | | | 1 |
| 1/7/14 | 1 | | | | 1 |
+--------+----------------+-------+-------+-------+-------+
Not sure if this is getting beyond what should be done in MySql... Thanks for the help!

Create a date range table:
mysql> CREATE TABLE IF NOT EXISTS calendar (date DATE NOT NULL PRIMARY KEY);
mysql> INSERT INTO calendar (SELECT calendar.date
FROM (
SELECT (SELECT MIN(signed_up) FROM users)
+ INTERVAL (a.val + (10 * b.val)) DAY
as date
FROM (select 0 as val 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 union all select 8 union all select 9)
as a
CROSS JOIN (select 0 as val 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 union all select 8 union all select 9)
as b
) calendar
WHERE calendar.date BETWEEN
(SELECT MIN(signed_up) FROM users) AND
(SELECT MAX(last_seen) FROM users));
mysql> select * from calendar;
+------------+
| date |
+------------+
| 2014-01-01 |
| 2014-01-02 |
| 2014-01-03 |
| 2014-01-04 |
| 2014-01-05 |
| 2014-01-06 |
| 2014-01-07 |
+------------+
7 rows in set (0.00 sec)
mysql> select * from users;
+--------+------------+------------+
| userid | signed_up | last_seen |
+--------+------------+------------+
| 1 | 2014-01-01 | 2014-01-03 |
| 2 | 2014-01-01 | 2014-01-05 |
| 3 | 2014-01-03 | 2014-01-05 |
| 4 | 2014-01-06 | 2014-01-07 |
+--------+------------+------------+
4 rows in set (0.00 sec)
Once you have the date range table, the query you are looking for can be expressed as a JOIN / GROUP BY operation:
mysql> SELECT c.date, count(c.date)
FROM calendar c JOIN users u WHERE c.date BETWEEN u.signed_up AND u.last_seen
GROUP BY c.date;
+------------+---------------+
| date | count(c.date) |
+------------+---------------+
| 2014-01-01 | 2 |
| 2014-01-02 | 2 |
| 2014-01-03 | 3 |
| 2014-01-04 | 2 |
| 2014-01-05 | 2 |
| 2014-01-06 | 1 |
| 2014-01-07 | 1 |
+------------+---------------+
7 rows in set (0.00 sec)
If you are doing this kind of query often, it would pay to generate (and keep) the calendar table. However, if you don't want to create the calendar table, the result can be generated on the fly using a single query:
mysql> SELECT c.date, COUNT(c.date)
FROM (SELECT calendar.date
FROM (
SELECT (SELECT MIN(signed_up) FROM users)
+ INTERVAL (a.val + (10 * b.val)) DAY
as date
FROM (select 0 as val 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 union all select 8 union all select 9)
as a
CROSS JOIN (select 0 as val 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 union all select 8 union all select 9)
as b
) calendar
WHERE calendar.date BETWEEN
(SELECT MIN(signed_up) FROM users) AND
(SELECT MAX(last_seen) FROM users)) c
JOIN users u WHERE c.date BETWEEN u.signed_up AND u.last_seen
GROUP BY c.date
which yields
+------------+---------------+
| date | COUNT(c.date) |
+------------+---------------+
| 2014-01-01 | 2 |
| 2014-01-02 | 2 |
| 2014-01-03 | 3 |
| 2014-01-04 | 2 |
| 2014-01-05 | 2 |
| 2014-01-06 | 1 |
| 2014-01-07 | 1 |
+------------+---------------+
7 rows in set (0.00 sec)
Note that, depending on how large a calendar you need, you may need to add more CROSS JOINS in the INSERT INTO calendar statement. See the link for an example.

Related

SQL - Select records that their columns do not follow the same order

Given we have following table where the series number and the the date should increment
+----+--------+------------+
| id | series | date |
+----+--------+------------+
| 1 | 10 | 2020-08-13 |
| 2 | 9 | 2020-08-02 |
| 3 | 8 | 2020-06-23 |
| 4 | 7 | 2020-06-08 |
| 5 | 6 | 2020-05-20 |
| 6 | 5 | 2020-05-05 |
| 7 | 4 | 2020-05-01 |
+----+--------+------------+
Is there a way to check if there are records that do not follow this pattern ?
For example row 2 has bigger series number but it's date is before row 3
+----+--------+------------+
| id | series | date |
+----+--------+------------+
| 1 | 10 | 2020-08-13 |
| 2 | 9 | 2020-06-02 |
| 3 | 8 | 2020-07-23 |
| 4 | 7 | 2020-06-08 |
| 5 | 6 | 2020-05-20 |
| 6 | 5 | 2020-05-05 |
| 7 | 4 | 2020-05-01 |
+----+--------+------------+
You can use window functions:
select *
from (
select t.*, lead(date) over(order by series) lead_date
from mytable t
) t
where date > lead_date
Alternatively:
select *
from (
select t.*, lead(series) over(order by date) lead_series
from mytable t
) t
where series > lead_series
You can use lag():
select t.*
from (select t.*,
lag(id) over (order by series) as prev_id_series,
lag(id) over (order by date) as prev_id_date
from t
) t
where prev_id_series <> prev_id_date;
You can fetch problematic rows and their corresponding conflicting rows using SELF JOIN like this (assuming your table is called "series"):
SELECT s1.id AS row_id, s1.series AS row_series, s1.date AS row_date,
s2.id AS conflict_id, s2.series AS conflict_series, s2.date AS conflict_date
FROM series AS s1
JOIN series AS s2
ON s1.series > s2.series AND s1.date < s2.date;

Row counter per Column

Say I have a table like so
| id | user_id | event_id | created_at |
|----|---------|----------|------------|
| 1 | 5 | 10 | 2015-01-01 |
| 2 | 6 | 7 | 2015-01-02 |
| 3 | 3 | 8 | 2015-01-01 |
| 4 | 5 | 9 | 2015-01-04 |
| 5 | 5 | 10 | 2015-01-02 |
| 6 | 6 | 1 | 2015-01-01 |
I want to be able to generate a counter of events per user. So my result would be:
| counter | user_id | event_id | created_at |
|---------|---------|----------|------------|
| 1 | 5 | 10 | 2015-01-01 |
| 1 | 6 | 7 | 2015-01-02 |
| 1 | 3 | 8 | 2015-01-01 |
| 2 | 5 | 9 | 2015-01-04 |
| 3 | 5 | 10 | 2015-01-02 |
| 2 | 6 | 1 | 2015-01-01 |
One idea is to self join the table and group by to replicate row_number() over.. function available in other RDBMS.
Check this Rextester Demo and see second query, to understand how inner join works in this case.
select t1.user_id,
t1.event_id,
t1.created_at,
count(*) as counter
from your_table t1
inner join your_table t2
on t1.user_id=t2.user_id
and t1.id>=t2.id
group by t1.user_id,
t1.event_id,
t1.created_at
order by t1.user_id,t1.event_id;
Output:
+---------+----------+------------+---------+
| user_id | event_id | created_at | counter |
+---------+----------+------------+---------+
| 3 | 8 | 01-01-2015 | 1 |
| 5 | 10 | 01-01-2015 | 1 |
| 5 | 10 | 02-01-2015 | 3 |
| 5 | 9 | 04-01-2015 | 2 |
| 6 | 1 | 01-01-2015 | 2 |
| 6 | 7 | 02-01-2015 | 1 |
+---------+----------+------------+---------+
Try the following:
select counter,
xx.user_id,
xx.event_id,
xx.created_at
from xx
join (select a.id,
a.user_id,
count(*) as counter
from xx as a
join xx as b
on a.user_id=b.user_id
and b.id<=a.id
group by 1,2) as counts
on xx.id=counts.id
Use a join to generate rows for each id with all the other lower ids for that user below it and count them.
Try This one:
Sub query will help to get this rsult.
select (select count(*) from user_event iue where iue.user_id == oue.user_id) as counter,
oue.user_id,
oue.event_id,
oue.created_at
from user_event oue
You could try to use a variable as a table, cross join it with the source table and reset whenever user id changes.
SELECT #counter := CASE
WHEN #user = user_id THEN #counter + 1
ELSE 1
END AS counter,
#user := user_id AS user_id,
event_id,
created_at
FROM your_table m,
(SELECT #counter := 0,
#user := '') AS t
ORDER BY user_id;
I've created a demo here

How to fetch only continuous records from mysql?

I have a database table like below
___________
id | speed
-----------
1 | 3
2 | 2
3 | 0
4 | 0
5 | 0
6 | 2
7 | 0
8 | 0
9 | 2
10 | 0
Now I want to get the records where speed is 0 but only from 3 to 5 which are continuous and greater than any other continuous records. I don't want 7,8 records or the 10th record. How can I achieve this?
Probably the fastest method is to use MySQL session variables to increment the "group" each time the speed changes, as you scan through the rows.
select n.*, #groupid:=IF(#prev_speed=speed,#groupid,#groupid+1) as groupid, #prev_speed:=speed
from (select #groupid:=0, #prev_speed=-1) _init
cross join n
order by id;
+----+-------+---------+--------------------+
| id | speed | groupid | #prev_speed:=speed |
+----+-------+---------+--------------------+
| 1 | 3 | 1 | 3 |
| 2 | 2 | 2 | 2 |
| 3 | 0 | 3 | 0 |
| 4 | 0 | 3 | 0 |
| 5 | 0 | 3 | 0 |
| 6 | 2 | 4 | 2 |
| 7 | 0 | 5 | 0 |
| 8 | 0 | 5 | 0 |
| 9 | 2 | 6 | 2 |
| 10 | 0 | 7 | 0 |
+----+-------+---------+--------------------+
Then using the above query as a derived table, calculate the lowest and highest id per group, and the count of rows. Sort the groups by the count of rows.
select min(id) as minid, max(id) as maxid, count(*) as count
from (
select n.*, #groupid:=IF(#prev_speed=speed,#groupid,#groupid+1) as groupid, #prev_speed:=speed
from (select #groupid:=0, #prev_speed=-1) _init
cross join n
order by id
) as t1
group by t1.groupid
order by count desc;
+-------+-------+-------+
| minid | maxid | count |
+-------+-------+-------+
| 3 | 5 | 3 |
| 7 | 8 | 2 |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 6 | 6 | 1 |
| 9 | 9 | 1 |
| 10 | 10 | 1 |
+-------+-------+-------+
Then using the first row from the above as another derived table, join to the original table for the rows in the range from the min to max id.
select n.*
from (
select min(id) as minid, max(id) as maxid, count(*) as count
from (
select n.*, #groupid:=IF(#prev_speed=speed,#groupid,#groupid+1) as groupid, #prev_speed:=speed
from (select #groupid:=0, #prev_speed=-1) _init
cross join n
order by id
) as t1
group by t1.groupid
order by count desc limit 1
) as t2
inner join n on n.id between t2.minid and t2.maxid
+----+-------+
| id | speed |
+----+-------+
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
+----+-------+

select a column of data then a count of that column's value when a certain condition is true

Let's say I have a table like this:
project_id | created_by | created
1 | 3 | 2015-04-01
2 | 3 | 2015-04-07
3 | 4 | 2015-05-01
4 | 4 | 2015-05-02
and I want to select these columns, then a count of how many projects were created by the created_by before each project, to look like this:
project_id | created_by | created | previous by created_by user
1 | 3 | 2015-04-01 | 0
2 | 3 | 2015-04-07 | 1
3 | 4 | 2015-05-01 | 0
4 | 4 | 2015-05-02 | 1
How do I select the count for that last column? I've tried count(case where [condition] then 1 else null end) but I keep only getting one row of results when I use that.
You can use a subquery which i already mentioned in the comments.
For Example the query could look like this:
SELECT t1.*,
(SELECT count(*)
FROM Table t2
WHERE UNIX_TIMESTAMP(t2.date) < UNIX_TIMESTAMP( t1.date)
AND t2.created_by = t1.created_by) before
FROM Table t1
It will return the columns of the the Table 'Table' and the result of the subquery as column 'before' which contains the count of before created rows.
Is this what you are after ?
select
project_id,
created_by,
created,
rn as `previous by created_by user`
from(
select
project_id,
created_by,
created,
#rn:=if(#prev_created_by = created_by,#rn+1,0) as rn,
#prev_created_by := created_by
from project,(select #rn:=0,#prev_created_by:=null)x
order by created_by,created
)x;
Here is a test case
mysql> select * from project ;
+------------+------------+------------+
| project_id | created_by | created |
+------------+------------+------------+
| 1 | 3 | 2015-04-01 |
| 2 | 3 | 2015-04-07 |
| 3 | 4 | 2015-05-01 |
| 4 | 4 | 2015-05-02 |
+------------+------------+------------+
4 rows in set (0.00 sec)
The above query will have
+------------+------------+------------+-----------------------------+
| project_id | created_by | created | previous by created_by user |
+------------+------------+------------+-----------------------------+
| 1 | 3 | 2015-04-01 | 0 |
| 2 | 3 | 2015-04-07 | 1 |
| 3 | 4 | 2015-05-01 | 0 |
| 4 | 4 | 2015-05-02 | 1 |
+------------+------------+------------+-----------------------------+
Select t1.project_id , t1.created_by, t1.created,count(t2.created)
from t1 , (select created_by,created from t1) as t2
Where t1.created_by=t2.created_by and t1.created>t2.created
group by t1.project_id ,t1.created_by, t1.created

get certain number of rows using all id´s IN sentence

I have the next query:
SELECT a.id, a.brand_id
FROM articles a
WHERE a.deleted=0 AND a.brand_id IN (5,6)
LIMIT 4
How can I get 4 articles from all the brand_id's named at the IN sentence? For example, I would like to get 2 articles from brand_id=5 and 2 articles from brand_id=6
You can use union all
(
SELECT a.id, a.brand_id
FROM articles a
WHERE a.deleted=0 AND a.brand_id = 5 limit 2
)
union all
(
SELECT a.id, a.brand_id
FROM articles a
WHERE a.deleted=0 AND a.brand_id = 6 limit 2
)
UPDATE , this could be achieved using m-per-group logic and one way would be as -
Consider the table
mysql> select * from articles ;
+------+----------+---------+
| id | brand_id | deleted |
+------+----------+---------+
| 1 | 5 | 0 |
| 2 | 6 | 0 |
| 3 | 2 | 0 |
| 4 | 4 | 1 |
| 5 | 5 | 0 |
| 6 | 5 | 1 |
| 7 | 5 | 0 |
| 8 | 6 | 0 |
| 9 | 4 | 0 |
| 10 | 4 | 0 |
| 11 | 4 | 1 |
| 12 | 6 | 0 |
| 13 | 5 | 1 |
| 14 | 5 | 0 |
+------+----------+---------+
So with the query below will return n-per-group as
select
id,
brand_id
from (
select
id,
brand_id,
#r := if(#brand = brand_id,#r+1,1) as row_num,
#brand:= brand_id
from articles,(select #r:=0,#brand:='')rr
where
brand_id in (4,5,6)
and deleted = 0
order by brand_id
)x
where x.row_num <=2 limit 6;
+------+----------+
| id | brand_id |
+------+----------+
| 9 | 4 |
| 10 | 4 |
| 1 | 5 |
| 5 | 5 |
| 2 | 6 |
| 8 | 6 |
+------+----------+
6 rows in set (0.00 sec)
So here the limit will be always number of items inside IN clause * 2
SELECT a.id, a.brand_id
FROM articles a
WHERE a.deleted=0 AND a.brand_id=5
LIMIT 2
UNION ALL
SELECT a.id, a.brand_id
FROM articles a
WHERE a.deleted=0 AND a.brand_id=6
LIMIT 2