Find out if a particular user logs in every day - mysql

How do I find out if a particular user log ins every day using SQL in MySQL?
I have a table like this.
+----------+---------------------+---------+
| login_id | login_date | user_id |
+----------+---------------------+---------+
| 8494 | 2016-03-01 00:00:00 | 20703 |
| 22403 | 2016-03-01 00:00:00 | 53473 |
| 23965 | 2016-03-01 00:00:00 | 79722 |
| 30173 | 2016-03-01 00:00:00 | 36396 |
| 34928 | 2016-03-02 00:00:00 | 20703 |
| 38740 | 2016-03-02 00:00:00 | 15758 |
| 42769 | 2016-03-02 00:00:00 | 79722 |
| 44364 | 2016-03-02 00:00:00 | 79722 |
| 45440 | 2016-03-03 00:00:00 | 20703 |
| 49050 | 2016-03-03 00:00:00 | 36396 |
| 50273 | 2016-03-03 00:00:00 | 79722 |
| 50344 | 2016-03-04 00:00:00 | 20703 |
| 51360 | 2016-03-04 00:00:00 | 20703 |
| 54404 | 2016-03-04 00:00:00 | 53473 |
| 61533 | 2016-03-04 00:00:00 | 79722 |
| 72852 | 2016-03-05 00:00:00 | 20703 |
| 74546 | 2016-03-05 00:00:00 | 38289 |
| 76487 | 2016-03-05 00:00:00 | 62529 |
| 82439 | 2016-03-05 00:00:00 | 36396 |
| 90006 | 2016-03-05 00:00:00 | 36396 |
| 90404 | 2016-03-06 00:00:00 | 20703 |
+----------+---------------------+---------+
And the answer would be something like
user_id, consecutive
20703, 'YES'
53473, 'NO'
20703 logins everyday from 2016-03-01 - 2016-03-06 but 53473 only logged in twice. the range is going to be from the start to finish of the entire table.

Well, you can do:
select user_id,
( count(distinct login_date) = cnt_ld ) as everyday_flag
from t cross join
(select count(distinct login_date) as cnt_ld
from t
) tt
group by user_id, tt.cnt_ld;
Note that this compares a user to all days in the table. If one day has no logins and you still want to include that day, then use the maximum minus the minimum for the comparison.
This also returns a "boolean" flag (really 0 or 1). You can use case to convert this to 'YES'/'NO', but I find the number easier to work with.

I get the max and min date in tbl then if the count per user id is equal to the datediff between max and min date (+1 day) then it is consecutive login else no.
select t1.user_id,
case when count(*)=datediff(t2.maxdt, t2.mindt)+1
then 'YES' else 'NO' end as consecutive
from tbl t1
join (select max(login_date) as maxdt,
min(login_date) as mindt
from tbl) t2
group by t1.user_id
order by t1.user_id;

I would just simply use group by with case expressions to find consecutive logins
select user_id,
case when count(distinct login_date) <> datediff(max(login_date), min(login_date))+1
then 'No' else 'Yes' end consecutive
from table t
group by user_id
If, you want to find users which has consecutive logins with start date to end date, then you could also do that via cross joins.
select user_id, case when counts <> total_counts then 'No' else 'Yes' end consecutive
from (select t.user_id, count(distinct login_date) counts
from table t
group by t.user_id )t cross join (
select count(distinct login_date) total_counts from table) tt

Related

Sum and subtract from another relation table in one query

I have two tables, one is called Attendance and the other is called Timeslices, I am trying to get the total seconds of Attendances subtracted from Timeslices for the current week and also with Doctrine.
I've got to get the rows but I have to sum and subtract each of them out of the query, but I need to learn to do it in one query.
This is the structure of the Attendance Table:
SELECT * FROM attendance;
+----+---------+---------------------+--------+---------------------+---------------------+
| id | user_id | day | status | check_in | check_out |
+----+---------+---------------------+--------+---------------------+---------------------+
| 1 | 1 | 2019-12-18 00:00:00 | end | 2019-12-18 09:52:00 | 2019-12-18 23:37:02 |
| 2 | 1 | 2019-12-19 00:00:00 | end | 2019-12-19 12:12:00 | 2019-12-19 21:05:00 |
+----+---------+---------------------+--------+---------------------+---------------------+
Timeslice table:
SELECT * FROM timeslice;
+----+---------------+-------------------------------------+---------------------+---------------------+---------------------+
| id | attendance_id | title | day | start_at | stopped_at |
+----+---------------+-------------------------------------+---------------------+---------------------+---------------------+
| 20 | 1 | Sacar al perro, ducharme y vestirme | 2019-12-18 00:00:00 | 2019-12-18 15:57:50 | 2019-12-18 12:15:36 |
| 21 | 1 | Dormir | 2019-12-18 00:00:00 | 2019-12-18 18:44:30 | 2019-12-18 16:16:44 |
| 22 | 1 | Descansar | 2019-12-18 00:00:00 | 2019-12-18 23:04:53 | 2019-12-18 20:56:29 |
| 23 | 2 | Comer | 2019-12-19 00:00:00 | 2019-12-19 16:03:00 | 2019-12-19 15:37:00 |
| 24 | 2 | Comer | 2019-12-19 00:00:00 | 2019-12-19 16:55:00 | 2019-12-19 16:17:00 |
| 25 | 2 | ducharme | 2019-12-19 00:00:00 | 2019-12-19 19:58:00 | 2019-12-19 17:20:00 |
+----+---------------+-------------------------------------+---------------------+---------------------+---------------------+
This is my current query in which I get the results, but then I have to calculate out of the query to get the desired result and SQLFiddle:
http://sqlfiddle.com/#!9/646be/3
SELECT SUM(TIME_TO_SEC(TIMEDIFF(a.check_out, a.check_in))) AS secondsAttendance
, ( SELECT SUM(TIME_TO_SEC(TIMEDIFF(t.start_at, t.stopped_at)))
FROM timeslice t
WHERE t.attendance_id = a.id
) secondsPauses
FROM attendance a
GROUP
BY a.id
What I need as I said before is to be able to do it in the same query without having to use PHP and with Doctrine
I've changed my answer after your comments. If you only need the value it seams that all you need to do is to use you initial query (with a few twicks) as a
subquery in the FROM clause (Derived Table) and then, do your calculations over it. In this case simply need to SUM the result of subtracting the secondsPauses to the secondsAttendance, like this:
-- make the calculation you need over the results
SELECT SUM(Results.secondsAttendance - Results.secondsPauses) as ActualValue
FROM (
-- use you initial results as a subquery and name it as Results
SELECT
SUM(TIME_TO_SEC(TIMEDIFF(a.check_out, a.check_in))) AS secondsAttendance,
(SELECT SUM(TIME_TO_SEC(TIMEDIFF(t.start_at, t.stopped_at)))
FROM timeslice t WHERE t.attendance_id = a.id) AS secondsPauses
FROM attendance a
-- filter date for the current week
where yearweek(DATE(a.check_in), 1) = yearweek(curdate(), 1)
GROUP BY a.id
) Results;
The result is:
+-------------+
| ActualValue |
+-------------+
| 38258 |
+-------------+
SqlFiddle in here

Count from multiple tables with group

I have two tables. The first tables registers admissions.
The second one has exits, as shown below:
Table 1: Admissions
+----------+---------------------+---------+
| entry_id | join_date | name |
+----------+---------------------+---------+
| 26 | 2017-01-01 00:00:00 | James |
| 29 | 2017-01-01 00:00:00 | Jan |
| 27 | 2017-01-01 00:00:00 | Chris |
| 28 | 2017-01-01 00:00:00 | Mary |
| 22 | 2017-01-02 00:00:00 | Anna |
| 21 | 2017-01-02 00:00:00 | Andy |
| 24 | 2017-01-02 00:00:00 | Bob |
| 20 | 2017-01-04 00:00:00 | Alice |
| 23 | 2017-01-04 00:00:00 | Chris |
| 25 | 2017-01-04 00:00:00 | Happy |
+----------+---------------------+---------+
Table 2: Exits
+----------+---------------------+----------+
| entry_id | exit_date | name |
+----------+---------------------+----------+
| 322 | 2017-01-01 00:00:00 | Kay |
| 344 | 2017-01-01 00:00:00 | Agnes |
| 920 | 2017-01-02 00:00:00 | Andre |
| 728 | 2017-01-02 00:00:00 | Mark |
| 583 | 2017-01-03 00:00:00 | Alsta |
| 726 | 2017-01-03 00:00:00 | Bull |
| 816 | 2017-01-03 00:00:00 | Jane |
| 274 | 2017-01-04 00:00:00 | Jack |
| 723 | 2017-01-04 00:00:00 | Anna |
| 716 | 2017-01-04 00:00:00 | Bill |
+----------+---------------------+----------+
I am looking for a solution to know the number of admissions, the number of exits and the balance, grouped by date.
I am looking for this >
+---------------------+--------+--------+-----------+
| date | joins | exist | net |
+---------------------+--------+--------+-----------+
| 2017-01-01 00:00:00 | 4 | 2 | 2 |
| 2017-01-02 00:00:00 | 3 | 2 | 1 |
| 2017-01-03 00:00:00 | 0 | 3 | -3 |
| 2017-01-04 00:00:00 | 3 | 3 | 0 |
+---------------------+--------+--------+-----------+
Notes: There may be days when admissions occur, but no exits are registered and vice versa.
Here you go:
SELECT
d,
SUM(CASE WHEN t = 'j' THEN 1 ELSE 0 END) as joins,
SUM(CASE WHEN t = 'x' THEN 1 ELSE 0 END) as exits,
SUM(CASE WHEN t = 'j' THEN 1 ELSE 0 END) - SUM(CASE WHEN t = 'x' THEN 1 ELSE 0 END) as net
FROM
(SELECT join_date as d, 'j' as t FROM admissions) j
UNION ALL
(SELECT exit_date as d, 'x' as t FROM exits) x
GROUP BY d
We concatenate the data using a UNION ALL and make a note of its type- join or exit, with a simple char we can compare later
We group this up by d, giving one date per line, and we sum the result of conditionally looking at whether its a 'j'oin or an e'x'it. If the line is a j, then a 1 is added to the column tracking the joins total for that day, and so on
The only thing this doesn't give you, is days where there are no joins or exits.. (For example 2018-12-25, 0, 0, 0 because xmas day was closed and noone did anything on that day).. But you didn't say you wanted those.
If you do want lines with a date, and 0 exits, 0 joins, 0 net, then we have to we have to work some additional magic, and it's a bit more of a headache/makes it harder to understand (so i left it out)
Following will do:
select
CASE WHEN join_date is not null THEN join_date
WHEN exit_date is not null THEN exit_date END as date,
entry.cnt as joins,
exit.cnt as exits,
(extry.cnt - exit.cnt) as net
FROM
(select join_date, COALESCE(count(*), 0) as cnt from Admissions group by join_date) entry
FULL OUTER JOIN
(select exit_date, COALESCE(count(*), 0) as cnt from Exits group by exit_date) exit
ON
entry.join_date=exit.exit_date
;
I didn't find the answer. Here's an answer from one of my friends, below is a MySQL version:
select aa.date, IFNULL(aa.joins, 0) joins, IFNULL(bb.exits,0) exits, (IFNULL(aa.joins,0) - IFNULL(bb.exits,0)) net
from
(
select join_date date, count(name) joins
from Admissions
group by join_date
) aa
left join
(
select exit_date date, count(name) exits
from Exits
group by exit_date
) bb on aa.date = bb.date
UNION
select bb.date, IFNULL(aa.joins, 0) joins, IFNULL(bb.exits,0) exits, (IFNULL(aa.joins,0) - IFNULL(bb.exits,0)) net
from
(
select join_date date, count(name) joins
from Admissions
group by join_date
) aa
right join
(
select exit_date date, count(name) exits
from Exits
group by exit_date
) bb on aa.date = bb.date order by date;

MySql aggregate by different initial values on other column

I have a table structure as follows
| user_id | value | date |
|---------|-------|------------|
| 1 | 5 | 2017-09-01 |
| 2 | 6 | 2017-09-01 |
| 1 | 1 | 2017-09-02 |
| 1 | 2 | 2017-09-03 |
| 2 | 9 | 2017-09-02 |
| 1 | 3 | 2017-09-04 |
| 2 | 5 | 2017-09-04 |
| 2 | 5 | 2017-09-05 |
| 1 | 1 | 2017-09-05 |
| 1 | 5 | 2017-09-06 |
| 1 | 6 | 2017-09-07 |
| 1 | 3 | 2017-09-08 |
| 1 | 4 | 2017-09-09 |
| 2 | 6 | 2017-09-06 |
| 1 | 1 | 2017-09-10 |
I have another table where initial cutoff date for user_ids are given like
| user_id | date |
|---------|------------|
| 1 | 2017-09-04 |
| 2 | 2017-09-05 |
The final cutoff date for all the user is 2017-09-08
I want to get the sum of values aggregated by user_id
What I have tried is
SELECT user_id, SUM(value) as Total
FROM table
WHERE date >= $DATE and date <= '2017-09-08'
GROUP BY user_id
I am stuck how should I deal with $DATE as it is variable for each user
The solution in the case should be
| user_id | Total |
|---------|------------|
| 1 | 18 |
| 2 | 11 |
Say you have table names as users and cutoff. cutoff having cutoff date for each user. Try this and let me know if it works or not.
select u.user_id,sum(u.value) Total
from users u join cutoff c on u.user_id=c.user_id and (u.date>=c.date and u.date<='2017-09-08')
group by u.user_id
SELECT x.user_id, SUM(x.value) as Total
FROM table x
WHERE x.date >= (select date from table2 where user_id = x.user_id)
and x.date <= '2017-09-08'
GROUP BY x.user_id
Using where exists
SELECT a.user_id, SUM(a.value) as Total
FROM table1 a
WHERE EXISTS (
SELECT 1
FROM table2 b
WHERE a.user_id = b.user_id
AND a.date >= b.date and a.date <= '2017-09-08'
)
GROUP BY user_id
DEMO
OR using conditional aggregation
SELECT a.user_id,
SUM(CASE WHEN a.date >= b.date and a.date <= '2017-09-08'
THEN a.value
ELSE 0
END) as Total
FROM table1 a
JOIN table2 b ON a.user_id = b.user_id
GROUP BY user_id
DEMO

MYSQL: Left JOIN from two SELECT to "fill gaps" in dates

Let's say I have a table "calendar"
+------------+
| day_date |
+------------+
| 2015-01-01 |
| 2015-01-02 |
| 2015-01-03 |
| .......... |
| 2015-07-14 |
| 2015-07-15 |
+------------+
With this query I can select the WEEK (that I need)
SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS YEAR,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start
FROM calendar
GROUP BY NUM_WEEK
And this is the result:
+----------+------+------------+
| NUM_WEEK | YEAR | date_start |
+----------+------+------------+
| 29 | 2015 | 2015-07-20 |
| 30 | 2015 | 2015-07-27 |
| 31 | 2015 | 2015-08-03 |
| 32 | 2015 | 2015-08-10 |
| 33 | 2015 | 2015-08-17 |
| 34 | 2015 | 2015-08-24 |
| 35 | 2015 | 2015-08-31 |
| 36 | 2015 | 2015-09-07 |
| 37 | 2015 | 2015-09-14 |
| 38 | 2015 | 2015-09-21 |
| 39 | 2015 | 2015-09-28 |
| 40 | 2015 | 2015-10-05 |
| 41 | 2015 | 2015-10-12 |
| 42 | 2015 | 2015-10-19 |
| 43 | 2015 | 2015-10-26 |
+----------+------+------------+
Now I have another table:
+----+------------+--------+---------------------+
| id | id_account | amount | date_transaction |
+----+------------+--------+---------------------+
| 1 | 283 | 150 | 2015-06-21 15:50:47 |
| 2 | 283 | 47.74 | 2015-07-23 15:55:44 |
| 3 | 281 | 21.55 | 2015-08-24 12:27:11 |
| 4 | 283 | 11.22 | 2015-08-25 10:00:54 |
+----+------------+--------+---------------------+
They are gaps in date.
With a similar query:
SELECT WEEK(date_transaction,1) AS NUM_WEEK,
YEAR(date_transaction) AS YEAR,
STR_TO_DATE(CONCAT(YEAR(date_transaction),WEEK(date_transaction,1),' Monday'), '%X%V %W')
AS date_start,
transaction.id_account,
SUM(amount) as total FROM transaction
INNER JOIN account ON account.id_account = transaction.id_account
WHERE amount > 0 AND transaction.id_account
IN ( SELECT id_account FROM account WHERE id_customer = 12 )
GROUP BY id_account, WEEK(date_transaction,1)
I obtain this result (probably data are not accurate, referring to previous tables, just to explain).
+----------+------+------------+-----------+----------+
| NUM_WEEK | YEAR | date_start | idAccount | total |
+----------+------+------------+-----------+----------+
| 29 | 2015 | 2015-07-20 | 281 | 22377.00 |
| 30 | 2015 | 2015-07-27 | 281 | 11550.00 |
| 32 | 2015 | 2015-08-04 | 281 | 4500.00 |
| 30 | 2015 | 2015-07-27 | 283 | 1500 |
+----------+------+------------+-----------+----------+
What I would, RIGHT (or LEFT) JOINING the two tables?
The min (and max) WEEK, so I can... (see 2)
Fill the gaps with missing WEEKS with NULL VALUES.
E.g., in a more complicated resultset:
+----------+------+------------+-----------+----------+
| NUM_WEEK | YEAR | date_start | idAccount | total |
+----------+------+------------+-----------+----------+
| 29 | 2015 | 2015-07-20 | 281 | 22377.00 |
| 30 | 2015 | 2015-07-27 | 281 | 11550.00 |
| 31 | 2015 | 2015-07-02 | 281 | NULL |
| 32 | 2015 | 2015-08-09 | 281 | 4500.00 |
| 29 | 2015 | 2015-08-09 | 283 | NULL |
| 30 | 2015 | 2015-07-16 | 283 | 1500 |
| 31 | 2015 | 2015-07-16 | 283 | NULL |
| 32 | 2015 | 2015-07-16 | 283 | NULL |
+----------+------+------------+-----------+----------+
Note, for example, that id=283 now has NULL at WEEK 29, 31 and 32, for example, like id=281 has NULL in WEEK 31.
I prepared also SQLFiddle here: http://sqlfiddle.com/#!9/a8fdc/3
Thank you very much.
I take a look on your question and i came up with this solution. Here is how your query could look like:
SELECT t1.NUM_WEEK, t1.`YEAR`, t1.date_start, t1.id_account, t2.total
FROM (SELECT c.NUM_WEEK, c.`YEAR`, c.date_start, a.id_account
FROM (SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start,
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
FROM calendar
GROUP BY NUM_WEEK) c
INNER JOIN account a
ON FIND_IN_SET(a.id_account, c.accounts_id)
ORDER BY a.id_account, c.NUM_WEEK) t1
LEFT JOIN
(SELECT WEEK(t.date_transaction,1) AS NUM_WEEK,
YEAR(t.date_transaction) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(t.date_transaction),WEEK(t.date_transaction,1),' Monday'), '%X%V %W') AS date_start,
t.id_account, SUM(t.amount) AS total
FROM `transaction` t
INNER JOIN account a
ON a.id_account = t.id_account
WHERE t.amount > 0 AND
t.id_account IN (SELECT id_account FROM account WHERE id_customer = 12)
GROUP BY id_account, WEEK(date_transaction,1)) t2
ON t1.NUM_WEEK = t2.NUM_WEEK AND t1.YEAR = t2.YEAR AND t1.id_account = t2.id_account;
Here is SQL Fiddle for that so you can check up result. Hope that is what are you looking for.
Little explanation:
First think i done is that I little modified your first query where you extract data from table calendar and add there one new column called accounts_id. That query now look's like this:
SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start,
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
FROM calendar
GROUP BY NUM_WEEK
Please pay attention on this line in SELECT statement
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
Note that when you select for specific customer you need to change customer ID in this line too!!!
Here is Fiddle so you can check result that this query produce.
This is necessary because we need to connect each week with each account to get desired result.
Next step is to extend previous query so we could separate accounts_id column (look result of previous query) so we could get row for each value in that column. Extended query look like this:
SELECT c.NUM_WEEK, c.`YEAR`, c.date_start, a.id_account
FROM (SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start,
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
FROM calendar
GROUP BY NUM_WEEK) c
INNER JOIN account a
ON FIND_IN_SET(a.id_account, c.accounts_id)
ORDER BY a.id_account, c.NUM_WEEK
and output you can see in this Fiddle
After that all we need to do is to make left join between this query and query you already wrote in your question (last query).
There might be a better solution or even this one maybe can be improved a little, but I don't have much time now to deal with that and this is the first think that cross my mind...
GL!
P. S. pay attention when you use reserved word in MySQL like YEAR, TRANSACTION etc for column name (as column_name).. that can cause you a treble if have to use them in name of column or table use backquote () to mark them (asyear`)...

How do I select the population of a User table as an increasing value by DATE?

Consider this excerpt of our Users table:
+-------------+---------------------+---------------------+
| id | last_login | created |
+-------------+---------------------+---------------------+
| 14551578822 | 2014-02-22 17:38:39 | 2013-03-26 23:30:50 |
| 18442388426 | 0000-00-00 00:00:00 | 2013-11-07 15:51:11 |
| 49983341634 | 2014-03-06 22:28:47 | 2013-03-23 16:00:05 |
| 9527246957 | 2014-01-17 02:37:53 | 2013-05-14 02:14:49 |
| 58667409337 | 2014-03-08 06:54:01 | 2013-05-15 01:52:23 |
| 1907780002 | 2014-03-01 03:24:04 | 2013-05-01 07:57:56 |
| 65319490251 | 2014-03-19 05:49:41 | 2013-03-23 08:53:43 |
| 23896465717 | 0000-00-00 00:00:00 | 2012-10-21 10:52:23 |
| 19147401900 | 0000-00-00 00:00:00 | 2013-05-01 17:43:28 |
| 28598429318 | 0000-00-00 00:00:00 | 2014-03-14 14:44:15 |
+-------------+---------------------+---------------------+
We have many, many users - and we would like to generate a report that will display the total number of users we have as the date increases. We would like output similar to this:
+---------+---------------+
| DATE | User Count |
+---------+---------------+
| 2012-08 | 122 |
| 2012-09 | 1746 |
| 2012-10 | 3847 |
| 2012-11 | 5826 |
...
| 2014-03 | 472647 |
| 2014-04 | 497286 |
+---------+---------------+
There must be some way to do it without subselects and all kinds of messiness like that. I have a table already that displays the number of joins per period by the following query:
SELECT DATE(users.created) as JOIN_DATE , COUNT(users.id) AS JOIN_COUNT from users
WHERE users.created > '2012-07-01 00:00:00'
GROUP BY JOIN_DATE
ORDER BY JOIN_DATE ASC
Just wondered if there was a way to do it something like that.
Thanks!
You can use a variable to sum up the population foreach iteration
SELECT t.date ,
#population := #population+t.per_time population
FROM (
SELECT
DATE_FORMAT(`last_login` ,'%Y-%m') `date`,
COUNT(*) per_time
FROM Table1
WHERE created > '2012-07-01 00:00:00'
GROUP BY `date` ) t ,
(SELECT #population:=0) p
Fiddle Demo