I have two models users and posts. An user can votes and views a post
#users
id
name
#posts
id
count_votes
count_views
users_id
created_at
updated_at
I want the user who received the most votes and views on his posts from the last 24 hours. The biggest sum of views and votes win.
WHAT I TRIED
I have this SQL query, it's good but I would like to have the user with the max of votes, this one give me all users and I don't know how to add count_views
select u.name as "Name", sum(p.count_votes)
from posts p
inner join users u on p.user_id = u.id
where p.created_at >= DATE_SUB(NOW(), INTERVAL 1 day)
group by user_id;
ActiveRecord version
Post.select("users.id, sum(posts.count_votes) as nb_votes")
.joins(:user).where("posts.created_at >= ?", 1.day.ago.beginning_of_day)
.group("posts.user_id")
# Results on IRB
=> #<ActiveRecord::Relation [#<Post id: 1>, #<Post id: 3>]>
How can I combine a sum and a max on these two sums ? Is there a way to have an activerecord code or only raw SQL ?
You current query does a grouping on user. So you would get one record for each user in the output. By limiting the output to just 1 record and ordering by votes+views total count, you can get the top user.
Raw SQL:
select u.id, sum(p.count_votes + p.count_views) total
from posts p
inner join users u on p.user_id = u.id
where p.created_at >= DATE_SUB(NOW(), INTERVAL 1 day)
group by u.id
order by total DESC
limit 1 ;
ActiveRecord version: Start from your User model, so you will get the user object in the output instead of Post object, like you have mentioned in the question.
User.select("users.id, sum(posts.count_votes + posts.count_views) as nb_votes")
.joins(:post).where("posts.created_at >= ?", 1.day.ago.beginning_of_day)
.group("posts.user_id").order("nb_votes DESC").limit(1)
ActiveRecord makes sense once you work with an object, in that case you need to have a User object only, so here is your solution below:
sql = %Q(SELECT * FROM users
WHERE id = (SELECT users_id FROM posts
WHERE DATE(created_at) = CURDATE()
ORDER BY count_votes DESC, count_views DESC
LIMIT 1
)
LIMIT 1)
ActiveRecord::Base.connection.execute(sql)
Related
I have the following query to try to count the number of comments made within 30 minutes of a post getting made. There are two tables, posts and comments which are INNER JOINed on posts.id and comments.post_id like so:
SELECT id, count(post_id) as num_comm from posts
INNER JOIN comments on id = post_id
WHERE (UNIX_TIMESTAMP(posts.time_posted) - UNIX_TIMESTAMP(comments.time_posted)) < (30 * 60)
AND comments.reply_to_id = 0
GROUP BY id
ORDER BY num_comm ASC;
The problem I'm having is that the query is returning the the total number of num_comm results and not just the count of those comments that were made within 30 minutes of the original post and that has comments.reply_to_id set to 0.
How do I change the query to return only the number of comments that meet the criteria in the WHERE clause?
Presumably, the post is posted before the comment. So, your value is always negative. Try:
SELECT id, count(*) as num_comm
FROM posts p INNER JOIN
comments c
ON p.id = c.post_id
WHERE (UNIX_TIMESTAMP(c.time_posted) - UNIX_TIMESTAMP(p.time_posted)) < (30 * 60) AND
c.reply_to_id = 0
GROUP BY p.id
ORDER BY num_comm ASC;
I don't really like the conversion to Unix timestamps. I think the code is much clearer as:
WHERE c.time_posted < p.time_posted + interval 30 minute AND
c.reply_to_id = 0
I'm struggling to make a query efficient enough. I'm using Doctrine2 ORM (the query is build with QueryBuilder) and part of my query is running very slow - takes about 4s with table of 5000 rows.
This is the relevant part of db schema:
TABLE user
id (primary)
... (plenty of rows, not relevant to the query)
TABLE slot
id (primary)
user_id (foreign for user)
date (datetime)
And this is how my query looks like (it's the basic version, there's a lot of filters to be applied, but these work like fine for now)
SELECT
u.id AS uid,
COUNT(DISTINCT s_order.id) AS sclr_1,
COUNT(DISTINCT s_filter.id) AS sclr_2
FROM
user u
LEFT JOIN slot s_order ON (s_order.user_id = u.id)
LEFT JOIN slot s_filter ON (s_filter.user_id = u.id)
WHERE
(
(
(
s_order.date BETWEEN ?
AND ?
)
AND (
s_filter.date BETWEEN ?
AND ?
)
)
AND (u.deleted_at IS NULL)
)
AND u.userType IN ('2')
GROUP BY
u.id
HAVING
sclr_2 > 0
ORDER BY
sclr_1 DESC
LIMIT
12
Let me explain what I'm trying to achieve here:
I need to filter users who has any slots between 1 week ago and 1 week ahead, then order them by count of slots available between now and 1 week ahead. The part of query causing issues is LEFT JOIN of s_filter and I'm wondering whether perhaps there's a way to improve the performance of that query?
Any help appreciated really, even if it's only plain SQL I'll try to convert it to DQL myself!
#UPDATE
Just an additional info that I forgot, the LIMIT in query is for pagination purposes!
#UPDATE 2
After a while of tweaking the query I figured out that I can use JOIN for filtering instead of LEFT JOIN + COUNT, so my query does look like that now:
SELECT
u.id AS uid, COUNT(DISTINCT s_order.id) AS ordinal
FROM
langu_user u
LEFT JOIN
slot s_order ON (s_order.user_id = u.id) AND s_order.date BETWEEN '2017-02-03 14:03:22' AND '2017-02-10 14:03:22'
JOIN
slot s_filter ON (s_filter.user_id = u.id) AND s_filter.date BETWEEN '2017-01-27 14:03:22' AND '2017-02-10 14:03:22'
WHERE
u.deleted_at IS NULL
AND u.userType IN ('2')
GROUP BY u.id
ORDER BY ordinal DESC
LIMIT 12
And it went down from 4.1-4.3s to 3.6~
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')
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
I'm stuck, I tried to grab statistics from DB.
My query needs to return count of users that at least have 1 entry in connections table.
So I tried:
SELECT DISTINCT (u.id) AS total_has_atleast_one_word FROM users AS u
LEFT JOIN connections AS c
ON u.id = c.user_id
WHERE c.word_id IS NOT null;
This returns correct user_id, I've 3 rows with correct id which is all fine.
But when I do count(u.id) it returns 35 which instead should be 3. My understanding is it is counting non DISTINCT number of rows. So what should I do?
And as a last part of my question, how do I unite this with other stat queries of mine?
/*SELECT COUNT(u.id) AS total_users,
sum(u.created < (NOW() - INTERVAL 7 DAY)) as total_seven_day_period,
sum(u.verified = 1) as total_verified,
sum(u.level = 3) as total_passed_tut,
sum(u.avatar IS NOT null) as total_with_avatar,
sum(u.privacy = 0) as total_private,
sum(u.privacy = 2) as total_friends_only,
sum(u.privacy = 3) as total_public,
sum(u.sex = "F") as total_female,
sum(u.sex = "M") as total_male
FROM users AS u;*/
Testing playground: http://www.sqlfiddle.com/#!2/c79a6/63
SELECT COUNT(DISTINCT user_id) FROM connections
for the first part :
/* user ids of those who have ever connected */
Select user_id from connections group by user_id
/* to get those who have connected after a particular date time ... */
Select user_id from connections group by user_id where connection_time > '2013-11-23'
/* join with user table to get user details e.g. .. */
Select u.name , u.address from user u
join on (Select user_id from connections group by user_id) c on c.user_id =u.user_id