Retrieve all users even if there is no user log entries - mysql

I want this query to show all users no matter what. And the calculate number of logins that happened in a certain time period (or just retun zero if there are no entries).
Right now, it only show users that have entries in the log in that time period.
SELECT
COUNT(user_log.type) AS logins,
users.name,
users.email,
users.user_id
FROM
users
LEFT JOIN
user_log
ON
users.user_id = user_log.user_id
WHERE
user_log.type = 8
AND
user_log.date >= :from
AND
user_log.date <= :to
GROUP BY
users.user_id
user_log.type = 8 means the log entry is a login
:from is a timestamp with the start date
:to is a timestamp with the end date
I use PDO

First of all, you're misusing a pernicious MySQL extension to GROUP BY. You'll have a hard time debugging. So you need to GROUP BY users.name, users.email. users.id.
The way your query is written, with items from your LEFT JOINed table in your WHERE clause, converts LEFT JOIN to INNER JOIN. So you're losing the unmatched rows from your first table.
Move those WHERE items to the ON clause and things will work the way you want. Like so.
SELECT
COUNT(user_log.type) AS logins,
users.name,
users.email,
users.user_id
FROM users
LEFT JOIN user_log
ON (
users.user_id = user_log.user_id
AND
user_log.type = 8
AND
user_log.date >= :from
AND
user_log.date <= :to
)
GROUP BY
users.name, users.email. users.id

If you use a where constraint on the second table with a LEFT JOIN, you'll have the same result as if you have used an INNER JOIN, because you don't allow NULL value.
Here a solution you can try, using a subrequest :
SELECT users.name,
users.email,
users.user_id,
IF (logins IS NOT NULL, logins, 0) AS logins
FROM users
LEFT JOIN (
SELECT user_id, COUNT(type) AS logins
FROM user_log
WHERE
type = 8
AND
date >= :from
AND
date <= :to
GROUP BY
user_id
) AS l ON l.user_id = users.user_id

Related

Retrieve data based on COUNT() and MONTH() in MySQL

I have users and likes tables. A foreign key of the latter references id from users table. The task at hand is to retrieve all distinct users who have more than 100 likes in March 2018. I'm trying to extract date-related values from a column with a type TIMESTAMP
I've come up with only seeing that pretty much all of them have some likes in that period:
SELECT DISTINCT u.name
FROM users AS u
JOIN likes AS l ON u.id = l.user_id
WHERE MONTH(l.timestamp) = 3 AND YEAR(l.timestamp) = 2018;
I guess I have to make use of COUNT() and GROUP BY somehow, but all my struggles were leading to syntax errors. Please give a hand.
You don't want select distinct. You want group by and having:
SELECT u.name
FROM users u JOIN
likes l
ON u.id = l.user_id
WHERE MONTH(l.timestamp) = 3 AND YEAR(l.timestamp) = 2018
GROUP BY u.name
HAVING COUNT(*) > 100;
To be honest, it is better to write the WHERE clause as:
WHERE l.timestamp >= '2018-03-01' AND l.timestamp < '2018-04-01'
This allows the SQL engine to use an index on timestamp, if one is available.

Data duplicate after join of four tables in Windows Form c# with MS Access

I am joining four tables i.e
users have user_history which workshop he is attending, so as room history where he is staying joining with room is to get name of the room which he is staying
Here is the query that is generate after joining above four tables in Ms Access query Design
SELECT
users.ID,
users.user_name,
users.father_name,
users.phone,
users.email,
users.cnic,
users.address,
users.user_type,
users.department,
users.designation,
users.emergency_no,
users.img,
room_history.ID,
room_history.room_id,
room_history.occupant_id,
room_history.start_date,
room_history.end_date,
rooms.room_name,
user_history.ID,
user_history.workshop
FROM (users
INNER JOIN (rooms
INNER JOIN room_history
ON rooms.room_id = room_history.room_id)
ON users.ID = room_history.occupant_id)
INNER JOIN user_history
ON users.ID = user_history.user_id;
Now the problem is that query works fine, but if user have multiple record in room_history and user_history table it returns four records i.e
In mysql this duplication is fixed by "GROUP BY" clause, that's not working here
I try to add "GROUP BY" to all fields it still cant work.
Note:
Üsers.Img data type is ole object so we cant add GROUP BY to it
Here is my query with GROUP BY clause, Still the result is same
SELECT DISTINCT
users.ID,
users.user_name,
users.father_name,
users.phone,
users.email,
users.cnic,
users.address,
users.user_type,
users.department,
users.designation,
users.emergency_no,
room_history.ID,
room_history.room_id,
room_history.occupant_id,
room_history.start_date,
room_history.end_date,
rooms.room_name,
user_history.ID,
user_history.workshop,
user_history.user_id
FROM (users
INNER JOIN (rooms INNER JOIN room_history ON rooms.room_id = room_history.room_id) ON users.ID = room_history.occupant_id)
INNER JOIN user_history ON users.ID = user_history.user_id
GROUP BY
users.ID,
users.user_name,
users.father_name,
users.phone,
users.email,
users.cnic,
users.address,
users.user_type,
users.department,
users.designation,
users.emergency_no,
room_history.ID,
room_history.room_id,
room_history.occupant_id,
room_history.start_date,
room_history.end_date,
rooms.room_name,
user_history.ID,
user_history.workshop,
user_history.user_id;
My comment captured part of what I wanted to say, but it's hard to articulate that in a tweet.
In essence, GROUP BY and/or SELECT DISTINCT, even if they work, are kind of masking the problem. The problem is that there are four records, and you want to pick one of them. If there is any distinction in the records, this won't work. More importantly, I think it's important that when you do this, and you want one record, you specify which record you want.
Let's assume, in your example if the room_history has multiple records for one occupant/room that you only want the most recent stay. In that case, assuming your DBMS supports the with clause and windowing functions, this would get you that:
with most_recent_history as (
select
ID, room_id, occupant_id, start_date, end_date,
max (end_date) over (partition by room_id, occupant_id) as last_date
from room_history
)
SELECT
users.ID,
users.user_name,
users.father_name,
users.phone,
users.email,
users.cnic,
users.address,
users.user_type,
users.department,
users.designation,
users.emergency_no,
users.img,
h.ID,
h.room_id,
h.occupant_id,
h.start_date,
h.end_date,
rooms.room_name,
user_history.ID,
user_history.workshop
FROM
users
INNER JOIN rooms
INNER JOIN most_recent_history h on
rooms.room_id = h.room_id and
users.ID = h.occupant_id and
h.end_date = h.last_date
INNER JOIN user_history on
users.ID = h.user_id;
If your DBMS does not support windowing functions and/or with, there are other ways to do this, but they're a lot more clumsy. If this is Oracle, SQL Server or PostgreSQL, this will work for sure.
Also, it doesn't seem possible in the real world, but in the event there can be a tie (a person is in two rooms that end on the exact same date), then you can add the tiebreaker to windowing function and use row_number instead.

How to make complicated SQL query in doctrine

I have 2 tables: users and their points.
Users have fields:
id
name
Points have fields:
id
start_date
end_date
count_of_points
user_id
So some users may have or not have points. Points entries limited by time interval (from start_date to end_date) and have count of points that user have at this interval.
I need display table of users sorted by total sum of points at this moment (timestamp must be between start_date and end_date) and display this sum value later in view. If user have no points this count must be equals 0.
I have something like this:
$qb = $this->getEntityManager()
->getRepository('MyBundle:User')
->createQueryBuilder('u');
$qb->select('u, SUM(p.count_of_points) AS HIDDEN sum_points')
->leftJoin('u.points', 'p')
->orderBy('sum_points', 'DESC');
$qb->groupBy('u');
return $qb->getQuery()
->getResult();
But this have no limits by date interval and have no field for sum points that I can use in view from object.
I tried to find how to solve this task and I made something like this in SQL:
SELECT u.*, up.points FROM users AS u LEFT OUTER JOIN
(SELECT u.*, SUM(p.count_of_points) AS points FROM `users` AS u
LEFT OUTER JOIN points AS p ON p.user_id = u.id
WHERE p.start_date <= 1463578691 AND p.end_date >= 1463578691
) AS up ON u.id = up.id ORDER BY up.points DESC
But this query give me only users with entries in points table, so I think I must use another JOIN to add users without points. It's complicated query. I have no idea how implements this in doctrine because DQL can't use inner queries with LEFT JOIN.
Maybe there are other ways to solve this task? Maybe my tables schema is wrong and I can do this different way?
EDIT: forgot the date conditions. Corrected answer:
In plain MySQL your query would look like this:
SELECT u.id, u.name, COALESCE(SUM(p.count_of_points),0) AS sum_points
FROM Users u
LEFT JOIN Points p ON p.user_id=u.id
WHERE (p.start_date <= 1463578691 AND p.end_date >= 1463578691) OR p.id IS NULL
GROUP BY u.id
ORDER BY sum_points DESC
The COALESCE function sends back the first not NULL argument, so if a user doesn't have points, the sum would result in NULL, but the COALESCE in 0.
I'm not sure of the translation using the Doctrine query builder, but you could try:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->addSelect('COALESCE(SUM(p.count_of_points),0) AS sum_points')
->from('User', 'u')
->leftjoin('u.points', 'p')
->where('(p.start_date <= ?1 AND p.end_date >= ?1) OR p.id IS NULL')
->groupBy('u.id')
->orderBy('sum_points','DESC')
->setParameter(1, $date_now);

Using an alias in where clause with a group by

Using a SQL query, I am trying to find the number of users that have had page views greater than 5 in a given month.
What I have so far is exactly the above except, I can't add the condition of a minimum of 5 page views. It is currently showing the number of users who have had at least 1 page view in a given month.
SELECT CONCAT(MONTH(analytics.date),'/',YEAR(analytics.date)) AS DATE,
COUNT(analytics.id) AS views,
COUNT(DISTINCT users.id) AS num_users
FROM users
LEFT JOIN analytics ON users.id = analytics.user_id
WHERE users.banned = 0
AND analytics.id IS NOT NULL
GROUP BY YEAR(analytics.date), MONTH(analytics.date)
I tried adding AND views > 5 in the where clause but that didn't work as I get an unknown column.
I don't think a HAVING clause will work as this is applied after the GROUP BY and I need to find individual users who have had more than 5 page views.
How else can I achieve this?
If this is your requirement, then you need to aggregate twice, once at the user level and second at the analytics level. Or, use a subquery in the where clause. Here is what you may need:
SELECT CONCAT(MONTH(a.date),'/',YEAR(a.date)) AS DATE,
COUNT(a.id) AS views,
COUNT(DISTINCT u.id) AS num_users
FROM users u LEFT JOIN
analytics a
ON u.id = a.user_id
WHERE u.banned = 0 AND a.id IS NOT NULL AND
5 <= (SELECT COUNT(*) FROM analytics a2 WHERE a2.user_id = u.userid)
GROUP BY YEAR(a.date), MONTH(a.date);
This uses the overall count for the limit.
EDIT: TO speed the subquery, be sure you have an index on analytis(user_id, date).
You have to use a subquery for this, since you're selecting which users feed into the GROUP BY. Here, we do a subquery in the WHERE clause to ask for each row if the user has at least five entries in the analytics table.
SELECT CONCAT(MONTH(analytics.date),'/',YEAR(analytics.date)) AS DATE,
COUNT(analytics.id) AS views,
COUNT(DISTINCT users.id) AS num_users
FROM users
LEFT JOIN analytics ON users.id = analytics.user_id
WHERE users.banned = 0
AND (SELECT COUNT(*) FROM analytics AS a WHERE a.user_id = users.id) > 5
AND analytics.id IS NOT NULL
GROUP BY YEAR(analytics.date), MONTH(analytics.date)
If you want there to be more than 5 views for the user in the given month, then you have to modify your query and you'll need to use an inner join:
SELECT CONCAT(MONTH(analytics.date),'/',YEAR(analytics.date)) AS DATE,
COUNT(analytics.id) AS views,
COUNT(DISTINCT users.id) AS num_users
FROM users
JOIN analytics ON users.id = analytics.user_id
WHERE users.banned = 0
AND (SELECT COUNT(*) FROM analytics AS a WHERE a.user_id = users.id AND EXTRACT(YEAR_MONTH FROM a.date) = EXTRACT(YEAR_MONTH FROM analytics.date)) > 5
AND analytics.id IS NOT NULL
GROUP BY YEAR(analytics.date), MONTH(analytics.date)

MySQL Join missing results

I am trying to select all users and get the amount of hours each user has logged from the joined times table. However, if the user has no times logged at all, I am not getting their name returned in the results. I would still like to get the name of every user, even if they have nothing logged in the times table.
I can get the results for all users if I remove the line:
AND (times.time_date BETWEEN '2014-03-01' AND '2014-03-31')
Here is my full query, What am I doing wrong?
SELECT users.user_id,
users.user_name,
SUM(times.time_used) as hours
FROM users
LEFT JOIN times on users.user_id = times.user_id
WHERE users.user_reminder = 1
AND users.status_id = 1
AND (times.time_date BETWEEN '2014-03-01' AND '2014-03-31')
GROUP BY users.user_id
ORDER BY users.user_name
Move your AND clause in ON() clause, using AND filter after WHERE clause will filter out the whole resultset,so if any user whose status_id is one and user_reminder is one but no time entries will be filtered,but if using AND in your on clause will join the users specific to the conditions and will return the uesrs even if they have no time entries
SELECT users.user_id,
users.user_name,
SUM(times.time_used) as hours
FROM users
LEFT JOIN times on (users.user_id = times.user_id
AND (times.time_date BETWEEN '2014-03-01' AND '2014-03-31') )
WHERE users.user_reminder = 1
AND users.status_id = 1
GROUP BY users.user_id
ORDER BY users.user_name