Include zeros in SQL count query? - mysql

I want to be able to return 0 when I am doing a count, I'd preferably not use joins as my query doesn't use them.
This is my query.
SELECT count( user_id ) as agencyLogins,
DATE_FORMAT(login_date, '%Y-%m-%d') as date
FROM logins, users
WHERE login_date >= '2015-02-10%' AND login_date < '2016-02-11%'
AND logins.user_id = users.id
GROUP BY DATE_FORMAT(login_date,'%Y-%m-%d')
What it does is counts the amount of times a user has logged into the website.
It doesn't count zeros though where as I want to know when there has been no log ins.

Please try using explicit join in the future, more readable and will make you avoid this errors. What you need is a left join:
SELECT t.id,count(s.user_id) as agencyLogins, DATE_FORMAT(s.login_date, '%Y-%m-%d') as date
FROM users t
LEFT OUTER JOIN login s
ON(t.id = s.user_id)
WHERE (s.login_date >= '2015-02-10%' AND s.login_date < '2016-02-11%') or (s.user_id is null)
GROUP BY t.id,DATE_FORMAT(s.login_date,'%Y-%m-%d')

This might be help you out
SELECT SUM(agencyLogins), date FROM (
SELECT count( user_id ) as agencyLogins,
DATE_FORMAT(login_date, '%Y-%m-%d') as date
FROM logins, users
WHERE login_date >= '2015-02-10%' AND login_date < '2016-02-11%'
AND logins.user_id = users.id
GROUP BY DATE_FORMAT(login_date,'%Y-%m-%d')
UNION ALL
SELECT 0,''
) AS A
GROUP BY DATE

I think below SQL useful to you. 2015-02-10% please remove % symbol in that string.
SELECT IF(COUNT(user_id) IS NULL,'0',COUNT(user_id)) as agencyLogins, DATE_FORMAT(login_date, '%Y-%m-%d') as date FROM users left join logins on logins.user_id = users.id
WHERE date(login_date) >= date('2015-02-10') AND date(login_date) <= date('2016-02-11')
GROUP BY DATE_FORMAT(login_date,'%Y-%m-%d')

Related

How to select a number of logins by a MySQL query?

I would like to select all login events and also the number of logins under one specific IP address by a MySQL query.
The Query that I have come up with is as follows. But I am afraid I have been missing something.
Hopefully one can see my mistake.
SELECT `pp_loginevent`.`loginevent_user`,
`pp_loginevent`.`loginevent_creationdate`,
`pp_loginevent`.`loginevent_ip`,
`pp_user`.`user_id`,
`pp_user`.`user_name`,
`pp_user`.`user_rights`,
`pp_user`.`user_active`,
`pp_license`.`license_name`
(SELECT COUNT(1)
FROM `pp_loginevent`
WHERE `loginevent_ip` = `pp_loginevent`.`loginevent_ip`
) AS `pp_loginevent`.`number_of_logins`
FROM `pp_loginevent`
LEFT JOIN `pp_user`
ON `pp_loginevent`.`loginevent_user` = `pp_user`.`user_id
LEFT JOIN `pp_license`
ON `pp_loginevent`.`loginevent_license` = `pp_license`.`license_id`
WHERE `loginevent_creationdate` LIKE '2019-%'
AND `user_rights` <= '6'
GROUP BY `loginevent_ip`
ORDER BY `loginevent_creationdate` DESC
What I am trying to do is to fetch the "column" number_of_logins and that should contain the number of logins made from a specific IP address.
You can't see anything wrong here:
`pp_license`.`license_name`
(SELECT COUNT(1) FROM `pp_loginevent` WHERE `loginevent_ip` = `pp_loginevent`.`loginevent_ip`) AS `pp_loginevent`.`number_of_logins`
?
While this won't resolve all the problems with this query, I think writing the query out this way makes one error very obvious:
SELECT e.loginevent_user
, e.loginevent_creationdate
, e.loginevent_ip
, u.user_id
, u.user_name
, u.user_rights
, u.user_active
, l.license_name
(SELECT COUNT(1)
FROM pp_loginevent
WHERE loginevent_ip = pp_loginevent.loginevent_ip) pp_loginevent.number_of_logins
FROM pp_loginevent e
JOIN pp_user u
ON u.user_id = e.loginevent_user
LEFT
JOIN pp_license l
ON l.license_id = e.loginevent_license
WHERE e.loginevent_creationdate >= '2019-01-01'
AND e.loginevent_creationdate < '2020-01-01'
AND user_rights <= 6
GROUP
BY loginevent_ip
ORDER
BY loginevent_creationdate DESC
In truth, as well as this syntax error, I suspect that there are other, logical, errors in this query.
Accordingly, see: Why should I provide an MCRE for what seems to me to be a very simple SQL query?

MYSQL select max date from joined tables

I have 2 tables which I want to join and retrieve some specific data. These are my tables.
tbl_user (reg_id, l_name, f_name, status)
tbl_payments (pay_id, reg_id, mem_plan, from_date, to_date, bill_no, payed_date)
What I need to do is select and view the users who have due payments. To do that I want to get the user details where "status=0" from tbl_user and join the 2 tables together and the conditions are to_date< current date, difference between [current date and the to_date] < 31 and filter by the Max value of to_date.
What I did so far gives me a result according to above mentioned conditions except it dosen't filter by the MAX(to_date). This is my query.
SELECT
A.reg_id,
A.f_name,
A.l_name,
B.mem_plan,
B.from_date,
Max(B.to_date) AS to_date,
B.bill_no,
B.payed_date
FROM
tbl_user A,
tbl_payments B
WHERE
A.status = 0
AND A.reg_id = B.reg_id
AND Date(Now()) >= Date(B.to_date)
AND Datediff(Date(Now()), Date(b.to_date)) < 31
GROUP BY
a.reg_id, b.mem_plan, b.from_date, b.bill_no, b.payed_date;
I'm not very familiar with MYSQL, So please someone tell me what I did wrong or if this query is not up to the standard.
Here are some sample data to work on.
tbl_user ( [M1111,Jon, Doe,0], [M1112,Jane,Doe,1],[M1113,Jony,Doe,0] )
tbl_payment ( [1,M1111,Monthly,2018-05-14,2018-06-14,b123,2018-05-14],[2,M1112,3Months,2018-02-03,2018-05-03,b112,2018-02-03],[3,M1113,Monthly,2018-06-14,2018-07-14,b158,2018-06-14],[4,M1111,Monthly,2018-06-15,2018-07-15,b345,2018-06-15],[5,M1113,Monthly,2018-06-06,2018-07-06,b158,2018-06-06],[6,M1111,Monthly,2018-07-05,2018-08-05,b345,2018-07-05] )
Assuming current date is 2018-07-17, The expecting result should be this
[M1111,Jon,Doe,Monthly,2018-06-15,2018-07-15,b345,2018-06-15],[M1113,Jony,Doe,Monthly,2018-06-14,2018-07-14,b158,2018-06-14]
Instead of that, my query gives me this.
[M1111,Jon,Doe,Monthly,2018-06-15,2018-07-15,b345,2018-06-15],[M1113,Jony,Doe,Monthly,2018-06-06,2018-07-06,b158,2018-06-06],
[M1113,Jony,Doe,Monthly,2018-06-14,2018-07-14,b158,2018-06-14]
I wrote another query which gives me the result set exactly as i want. But I'm not sure whether it's up to the standards. If someone can simplify this or make it better, appreciate very much.
SELECT A.reg_id,A.f_name,A.l_name,D.mem_plan,D.from_date,D.to_date,D.bill_no,D.payed_date
FROM tbl_user A
JOIN (SELECT B.reg_id,B.mem_plan,B.from_date,B.to_date,B.bill_no,B.payed_date
FROM tbl_payments B
JOIN (
SELECT reg_id, MAX(to_date) as to_date
FROM tbl_payments
WHERE DATE(NOW()) >= DATE(to_date) AND DATEDIFF(DATE(NOW()), DATE(to_date))<31
GROUP BY reg_id) C
ON B.reg_id = C.reg_id AND B.to_date= C.to_date) D
ON A.reg_id = D.reg_id
WHERE A.status=0;
I believe having won't work here and that your second query is about as good as it gets. I've condensed it a little here:
SELECT A.reg_id,f_name,l_name,mem_plan,from_date,to_date,bill_no,payed_date
FROM #tbl_user A
JOIN #tbl_payments B ON A.reg_id = b.reg_id
JOIN (
SELECT reg_id, MAX(to_date) as max_to_date
FROM #tbl_payments
WHERE DATE(NOW()) >= DATE(to_date) AND DATEDIFF(DATE(NOW()), DATE(to_date))<31
GROUP BY reg_id
) C ON B.reg_id = C.reg_id AND B.to_date= C.max_to_date
WHERE A.status=0;

Optimize Join, Sum, Subqueries

I'm building a Tinder clone for a study project and I'm trying to do something very simple conceptually but it appears that my request is really too heavy.
Data Structure
I've created this simple fiddle to visualize the database structure.
I've tried to put indexes on user.id user.gender * user.orientation match.user1 match.user2 match.createdAt with no luck.
Expected result
I want to find the people who have the less number of matches depending on gender, orientation, lastLogin and calendar date.
Users musn't be part of more than 4 matches during 24h so I look for users with <= 3 matches during the last 24h.
Values in the following are hard coded for easy editing of the request and because I didn't took time to do this part for now.
A match is composed of 2 users (user1 and user2).
The limit of 4 matches on the same day is a sum of when they appear as user1 and user2.
SELECT total_sum, userId
FROM (
SELECT u.id as userId, u.orientation as userOrientation, u.gender as userGender, m1.sum1, m2.sum2, (m1.sum1 + m2.sum2) AS total_sum
FROM user u
INNER JOIN (
SELECT user1, COUNT(user1) as sum1
FROM `match`
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user1
) m1
ON m1.user1 = u.id
INNER JOIN (
SELECT user2, COUNT(user1) as sum2
FROM `match`
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user2
) m2
ON m2.user2 = u.id
WHERE u.gender IN ('female')
AND u.orientation IN ('hetero', 'bi')
AND u.lastLogin > 1512873464582
) as total
WHERE total_sum < 4
ORDER BY total_sum ASC
LIMIT 8
The issue
With tiny tables, request takes few ms but with medium tables (50k users, 200k matches), request takes ages (170s).
Optimizing
According to #Thorsten Kettner response, this is the explain plan of his request when I run it into my test db after setting the indexes he advised:
Solution
I've ended up doing something easier.
First I flatened my match table by removing user2 column. It double the size because now 1 match become 2 rows but allow me to do something very simpler and very efficient with proper indexes.
The first query is to manage users with no matches and the second one to handle user with matches. I don't have anymore the matchesLimit into the query as it add extra work for mysql and I just need to check the first result to see if matchNumber is <= 3.
(SELECT u.id, mc.id as nb_match, u.gender, u.orientation
FROM user u
LEFT JOIN match_composition mc
ON (mc.matchedUser = u.id AND mc.createdAt > DATE('2017-12-11 00:00:00'))
WHERE u.lastLogin > 1512931740721
AND u.orientation IN ('bi', 'hetero')
AND u.gender IN ('female')
AND mc.id IS NULL
ORDER BY u.lastLogin DESC)
UNION ALL
(SELECT u.id, count(mc.id) as nb_match, u.gender, u.orientation
FROM match_composition mc
JOIN user u
ON u.id = matchedUser
WHERE mc.createdAt > DATE('2017-12-11 00:00:00')
AND u.lastLogin > 1512931740721
AND u.orientation IN ('bi', 'hetero')
AND u.gender IN ('female')
GROUP BY matchedUser
ORDER BY nb_match ASC
LIMIT 8)
thanks for your help
A user can be matched as user1 or user2. We can use UNION ALL to get one record per user:
select user1 as userid from match union all select user2 as userid from match;
The complete query:
select
u.id as userid,
coalesce(um.total, 0) as total
from user u
left join
(
select userid, count(*) as total
from
(
select user1 as userid from match where createdat > date '2017-12-11'
union all
select user2 as userid from match where createdat > date '2017-12-11'
) m
group by userid
) um on um.userid = u.id
where u.gender IN ('female')
and u.orientation in ('hetero', 'bi')
and u.lastlogin > 1512873464582
and coalesce(um.total, 0) < 4
order by coalesce(um.total, 0);
You would have the following indexes for this:
create index idx_m1 on match (createdat, user1);
create index idx_m2 on match (createdat, user2);
create index idx_u on user (lastlogin, gender, orientation, id);
I guess you were right about your SQL skills. This is what I came up with:
SELECT u.id as userId,
u.orientation as userOrientation,
u.gender as userGender,
count(m.user1) total_sum
FROM user u
LEFT JOIN `match` m on (u.id in (m.user1, m.user2)
and m.createdAt > DATE('2017-12-11 00:00:00'))
WHERE u.gender IN ('female')
AND u.orientation IN ('hetero', 'bi')
AND u.lastLogin > 1512873464582
having count(m.user1) <=4
ORDER BY total_sum ASC
LIMIT 8;
Edit: Covered also cases with no matches
Try to play around with indexing match table columns user1, user1 and also with User table columns(or column combinations) you use in filters (gender for example), see what brings better performance.
From what you provide, I would create indexes on:
- match.user1
- match.user2
- match.createdAt
- user.id (unique, and probably a PK)
- user.lastLogin
I would also try to replace COUNT(user1) by COUNT(*), but it won't probably have a big impact.
Indexes on user.gender and user.orientation are probably useless: the efficiency of an index is somehow proportional to the variance of its underlying values. Therefore an index on a field with 2-3 distinct values is more costly than useful.
As for the DLL, try the following. I tried to force the filtering on user to be done BEFORE the joins with match, in case the query optimizer does not work properly (I have little experience with non MS databases)
SELECT total_sum, userId
FROM (SELECT u.id as userId, u.orientation as userOrientation, u.gender as userGender, m1.sum1, m2.sum2, (m1.sum1 + m2.sum2) AS total_sum
FROM (SELECT * FROM user
WHERE gender = 'female'
AND orientation IN ('hetero', 'bi')
AND lastLogin > 1512873464582
) u
INNER JOIN (SELECT user1, COUNT(*) as sum1
FROM `match`
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user1
) m1 ON m1.user1 = u.id
INNER JOIN (SELECT user2, COUNT(*) as sum2
FROM `match`
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user2
) m2 ON m2.user2 = u.id
) as total
WHERE total_sum < 4
ORDER BY total_sum ASC
LIMIT 8

COUNT(*) does not count correctly

I'm having trouble with a mysql query.
SELECT switch_id, port_id, isp_service.service, isp_service.id
FROM traffic, isp_service
WHERE datetime>='2013-09-01 00:00:00'
AND datetime<'2013-09-02 00:00:00'
AND isp_service.id=traffic.isp_service_id
GROUP BY switch_id, port_id
This query returns me 1000 rows.
Now I am trying to count how many users each service has so I did:
SELECT ris.id, COUNT(*) as numberOfUsers
FROM
(SELECT switch_id, port_id, isp_service.service, isp_service.id
FROM traffic, isp_service
WHERE datetime>='2013-09-01 00:00:00'
AND datetime<'2013-09-02 00:00:00'
AND isp_service.id=traffic.isp_service_id
GROUP BY switch_id, port_id)ris
GROUP BY ris.id
ORDER BY ris.id
Now, how is possible that if I sum up the column numberOfUser the results is bigger than 1000?
You should learn about proper join syntax and to prefix all columns with table aliases. A better way to write the second query is:
SELECT ris.id, COUNT(*) as numberOfUsers
FROM (SELECT switch_id, port_id, s.service, s.id
FROM traffic t join
isp_service s
on s.id = t.isp_service_id
WHERE datetime >= '2013-09-01 00:00:00' AND datetime < '2013-09-02 00:00:00'
GROUP BY switch_id, port_id
) ris
GROUP BY ris.id
ORDER BY ris.id;
This query has a problem, because s.service and s.id are included in the select, but they are not in the group by. That means that MySQL takes arbitrary values for them.
It is unclear what specifies a "user". If the switch_id/port_id pair identifies a user, then the query should produce correct results. However, you are likely to be missing id values that have users. You might be able to do this in one query:
SELECT s.id, count(*) as NumberOfUsers, count(distinct switch_id, port_id) as NumberOfUsers2
FROM traffic t join
isp_service s
on s.id = t.isp_service_id
WHERE datetime >= '2013-09-01 00:00:00' AND datetime < '2013-09-02 00:00:00'
GROUP BY s.id;
I am not sure which count is most appropriate.

Getting rows between 2 given dates (included rows on those dates)

I know this should be simple, but Its proving to be quite complicated, I have two tables:
USERS id | name | url
COMMENTS id | id_user | text | lang | date(datetime)
And I want to retrieve all records (comments) between this two given dates (both dates included) I have tried in two different ways but they dont work as spected, returning no results where it should:
OPTION A
The following sentence returns nothing, and there are comments and this two comments should appear as the dates are '2014-01-09 16:34:58' and '2014-01-13 10:09:24'
SELECT
comments.text,
users.url,
users.name
FROM comments
JOIN users
ON comments.id_user = users.id
WHERE comments.date BETWEEN '2014-01-13'
AND '2014-01-09'
AND comments.lang = 'es'
ORDER BY comments.date DESC
OPTION B
THe following sentence returns commments written on day '2014-01-09' BUT not the ones written on '2013-01-09'
SELECT
comments.text,
users.url,
users.name
FROM comments
JOIN users
ON comments.id_user = users.id
WHERE comments.date <= '2014-01-13'
AND comments.date > '2014-01-09'
AND comments.lang = 'es'
ORDER BY comments.date DESC
What am I doing wrong?
Change this:
WHERE comments.date BETWEEN '2014-01-13' AND '2014-01-09'
To this:
WHERE comments.date BETWEEN '2014-01-09' AND '2014-01-13 23:59:59'
The MySQL BETWEEN clause expects the min and max parameters to be ordered properly so smaller value must be specified first. Secondly, to include records for 2014-01-13 you need to add the time portion (the time part in datetime does not contain milliseconds so checking for 23:59:59 is sufficient). Alternately you can write:
WHERE comments.date >= '2014-01-09'
AND comments.date < '2014-01-13' + INTERVAL 1 DAY
-- ^---------------------------^ evaluates to 2014-01-14
Instead of BETWEEN use this
SELECT
comments.texto,
usuarios.url,
users.nombre
FROM comments
JOIN usuarios
ON comments.id_usuario = users.id
WHERE DATE(comments.date) <= '2014-01-14'
AND DATE(comments.date) >= '2014-01-09'
AND comments.lang = 'es'
ORDER BY comments.fecha DESC
OR
SELECT
comments.texto,
usuarios.url,
users.nombre
FROM comments
JOIN usuarios
ON comments.id_usuario = users.id
WHERE comments.date <= '2014-01-14 59:59:59'
AND comments.date >= '2014-01-09 00:00:00'
AND comments.lang = 'es'
ORDER BY comments.fecha DESC