Suppose that I have 3 tables,
student(user_id, occupation)
thumbsup(user_id, post_id)
post(post_id, user_id)
I need to compute the number of good posts that one student has.
The conditions are like this
a post can receive multiple thumbs up
but once it receives at least one thumbup it is a goodpost
goodposts are how many posts that one user posted that received thumbsup
For example, if user_id 1 posts 3 posts with post_id, 3,4,5, 6 and post_id 3 receives 2 thumbs up, and post_id 4 receives 5 thumbs up and post_id 5 receives 10 thumbs up, 6 receives 0 thumbs up, that means user_id 1 has 3 good posts because 6 in this case has no thumbs up
I can sccuessfully project this by doing this. But there is one additional requirement:
if a student does not have any good posts, their number of good posts should be zero.
This is my current solution that projects student if they have good posts
(SELECT temp2.user_id, S.occupation, goodpost
FROM
(SELECT P.user_id, COUNT (P.user_id) as goodpost
FROM
(SELECT T.post_id, Count(*) AS likes
FROM swoosh.thumbsup T
group by T.post_id) As TEMP, swoosh.post P
WHERE P.post_id = temp.post_id
GROUP BY P.user_id) temp2, swoosh.Student S
WHERE temp2.user_id = S.user_id)
But I need to project those user_id who dont receive thumbs up too in this table. What is the approach
Related
profile
---
id name
1 John
2 Jane
3 Jill
...
swipe
---
id profile_1_id profile_2_id liked
1 2 1 true
2 3 1 false
...
If you've used Tinder before, you might recognize that it seems to fetch an initial card deck that consists of:
users who already like you that you can instantly match with, pushed to the top
other users
(out of scope for this question but it also sprinkles in some more attractive users)
If we extend the example to 100+ users, id=1 John was looking at the app, and we fetched with a limit of 20, it would guarantee Jane comes back (since Jane already likes John and John could match right away) + 19 others to fill the rest of John's deck to keep John swiping for more.
What is the SQL for "get people who like John first then fill the rest with random users"? Would this be a WHERE(case if else) or some other statement?
Here is a query that should meet your need.
It works by using a conditional sorting with CASE. Users that liked John will are given higher priority, and will appear sorted by id. Other users are given a lower, random, priority ; this also means, for a given user, this part of list will not always be the same (which, I believe, fits your purpose). The number of output records is then controlled by a LIMIT clause.
I tested the query in this db fiddle. You need to replace the question mark (?) in the CASE clause with the id of user for which you are generating a card (1 for John in your sample data).
SELECT
p.id,
p.name
FROM
profile p
LEFT JOIN swipe s on s.profile_1_id = p.id
ORDER BY
CASE s.profile_2_id
WHEN ? THEN 0
ELSE FLOOR(random() * 10) + 1
END,
p.id
LIMIT 20
You could try something like this but I think you're oversimplifying. Do you want to exclude not liked people from the others?
select * from profile p
left outer join swipe s on (p.id=profile_1_id and s.profile_2_id = 1 and liked = true)
where
p.id<>1
order by coalesce(profile_2_id , random()*-1000000) desc
limit 20
I have a table stories and a table blockings which has the columns story_id (referencing a story), and a blocked_story_id (also referencing a story, which is blocked by the story_id)
I'm trying to construct a query to return all the stories in order of precedence based on their blockers - so blockers first, traversing down the tree.
One story can be blocked by many stories, and can itself be a blocker for many stories.
I've been reading and re-reading the PostgreSQL docs on WITH RECURSIVE but I'm a little lost on where I should be going with this, and how to construct the relevant query.
I have got as far as:
select s.id, b.story_id as blocker_id
from stories s
left outer join blockings b on s.id = b.blocked_story_id
where s.deleted_at is null
as for getting a list of stories and their blockers, but some pointers as to what I need to join/union to get the desired result would be helpful.
Context
I want to know which stories I can work on first. So I want an output that contains all stories in an order that allows me to work top down and never hit a blocked story.
The content of the blockings table gives me a simple join table between stories that block one another. The story_id being the blocker, the blocked_story_id being the one being blocked.
Sample Data
Stories
id | title
------------------
1 | Story title 1
2 | Story title 2
3 | Story title 3
4 | Story title 4
5 | Story title 5
Blockings
story_id | blocked_story_id
---------------------------
4 | 2
4 | 3
3 | 1
3 | 5
I would expect to see the following result:
id | title
------------------
4 | Story title 4
2 | Story title 2
3 | Story title 3
1 | Story title 1
5 | Story title 5
Disclaimer: Because it is not clear to me why you need a recursion for finding the blocked stories (Which can be achieved easily by SELECT blocked_story_id FROM blocking) I would ask you for further information. A real recursion case could be: "All blocking that are reachable from story 4" or something like that.
Here's what I've done so far as I understood your problem:
Your blocking table says: story 4 blocks stories 2 and 3. Story 3 blocks stories 1 and 5. So there are blocked stories 1, 2, 3, 5. Because of the recursion, story 4 can block 1 and 5 via 3. So there a two ways of blocking them (directly with starting point 3 and and from starting point 4 via 3). I gave out all possible paths with this query:
WITH RECURSIVE blocks AS (
SELECT blocked_story_id, ARRAY[story_id]::int[] as path FROM blockings
UNION
SELECT bk.blocked_story_id, b.path || bk.story_id
FROM blockings bk INNER JOIN blocks b ON b.blocked_story_id = bk.story_id
)
SELECT b.blocked_story_id, s.title, b.path
FROM blocks b INNER JOIN stories s ON s.id = b.blocked_story_id;
Result:
blocked_story_id title path
2 Title 2 {4}
3 Title 3 {4}
1 Title 1 {3}
5 Title 5 {3}
1 Title 1 {4,3}
5 Title 5 {4,3}
demo: db<>fiddle
#S-Man I figured it out thanks to your help pointing me in the right direction.
WITH recursive blockings_tree(id, title, path) AS (
SELECT stories.id, title, ARRAY[blockings.blocked_story_id, blockings.story_id]
FROM stories
LEFT OUTER JOIN blockings ON blockings.story_id = stories.id
UNION ALL
SELECT stories.id, stories.title, path || stories.id
FROM blockings_tree
JOIN blockings ON blockings.story_id = blockings_tree.id
JOIN stories ON blockings.blocked_story_id = stories.id
WHERE NOT blockings.blocked_story_id = any(path)
)
SELECT stories.*
FROM stories
JOIN (SELECT id, MAX(path) AS path FROM blockings_tree GROUP BY id) bt ON bt.id = stories.id
ORDER BY path
I have a table that looks like (user_id, movie_id, start_timestamp, end_timestamp, reason_for_end). Movies can either end because the user turns them off (reason_for_end == 'user_initiated') or because the credits finish (reason_for_end == 'inactivity'). The credits always last exactly two hours.
I want to know the total number of times that a user started a new movie while another movie was in the credits phase.
Separately, I also want to know the total amount of time that a user spends watching a movie while the credits of another movie are still rolling.
EDIT:
Example given table:
user_id movie_id start_timestamp end_timestamp reason_for_end
1 1 2012-11-18 05:53:36.0 2012-11-18 12:46:40.0 'inactivity'
1 2 2012-11-18 11:34:23.0 2012-12-18 13:21:57.0 'user_initiated'
Example result table 1:
user_id times_new_started_while_old_in_credits
1 1
Example result table 2:
user_id total_overlap_in_seconds
1 4320
How about that?
You should tweak date-time handling to your target database syntax. Also, I do not know how you wish to handle situations when user jumps between more films simultaneously - that's up to you ;-)
Otherwise, this should do the job:
SELECT movie1.user_id, count(*), sum(movie1.end_timestamp - movie2.start_timestamp)
FROM movies movie1
LEFT JOIN movies movie2
ON movie1.user_id = movie2.user_id
AND movie1.reason_for_end = 'inactivity'
WHERE movie2.start_timestamp BETWEEN movie1.end_timestamp - '2hours'::interval AND movie1.end_timestamp
AND movie1.user_id = 1 /* optional */
GROUP BY movie1.user_id
I'm working on a project for my University with Rails 3/PostgreSQL, where we have Users, Activities and Venues. An user has many activities, and a venue has many activities. An activity belongs to an user and to a venue and has therefore an user_id and a venue_id.
What I need is a SQL query (or even a method from Rails itself?) to find mutual venues between several users. For example, I have 5 users that have visited different venues. And only 2 venues got visited by the 5 users. So I want to retrieve the 2 venues.
I've started by retrieving all activities from the 5 users:
SELECT a.user_id as user, a.venue_id as venue
FROM activities AS a
WHERE a.user_id=116 OR a.user_id=227 OR a.user_id=229 OR a.user_id=613 OR a.user_id=879
But now I need a way to find out the mutual venues.
Any idea?
thx,
tux
I'm not entirely familiar with sql syntax for postgresql, but try this:
select venue_id, COUNT(distinct user_id) from activities
Where user_id in (116,227,229,613,879)
group by venue_id
having COUNT(distinct user_id) = 5
EDIT:
You will need to change the '5' to however many users you care about (how many you are looking for).
I tested this on a table structure like so:
user_id venue_id id
----------- ----------- -----------
1 1 1
2 6 2
3 3 3
4 4 4
5 5 5
1 2 6
2 2 7
3 2 8
4 2 9
5 2 10
The output was:
venue_id
----------- -----------
2 5
You would have to come up with some parameters for your search. For example, 5 user may have 2 Venues in common, but not 3.
If you want to see what Venues these five users have in common, you can start by doing this:
SELECT a.venue_id, count(1) as NoOfUsers
FROM activities AS a
WHERE a.user_id=116 OR a.user_id=227 OR a.user_id=229 OR a.user_id=613 OR a.user_id=879
group by a.venue_id
That would bring you, for those users, how many users have that venue. So you have degrees of "Venue sharing".
But if you want to see ONLY the venues who were visited by the five users, you'd add a line in the end:
SELECT a.venue_id, count(1) as NoOfUsers
FROM activities AS a
WHERE a.user_id=116 OR a.user_id=227 OR a.user_id=229 OR a.user_id=613 OR a.user_id=879
group by a.venue_id
having count(1) = 5 --the number of users in the query
You should also consider changing your WHERE statement from
WHERE a.user_id=116 OR a.user_id=227 OR a.user_id=229 OR a.user_id=613 OR a.user_id=879
to
WHERE a.user_id in (116, 227, 229, 613, 879)
in sql it would be something like:
Select distinct v.venue_id
from v.venues
join activities a on a.venue_id = v.venue_id
Join users u on u.user_id = a.user_id
Where user_id in (116,227,229,613,879)
You need to join up your tables so to get all the venues that have had activities that have had users. When you are just learning it is sometimes simpler to visualize it if you use subqueries. At leasts thats what I found for me.
I have the following scenario:
Table 1:
articles
id article_text category author_id
1 "hello world" 4 1
2 "hi" 5 2
3 "wasup" 4 3
Table 2
authors
id name friends_with
1 "Joe" "Bob"
2 "Sue" "Joe"
3 "Fred" "Bob"
I want to know the total number of authors that are friends with "Bob" for a given category.
So for example, for category 4 how many authors are there that are friends with "Bob".
The authors table is quite large, in some cases I have a million authors that are friends with "Bob"
So I have tried:
Get list of authors that are friends with bob, and then loop through them and get the count for each of them of that given category and sum all those together in my code.
The issue with this approach is it can generate a million queries, even though they are very fast, it seems there should be a better way.
I was thinking of trying to get a list of authors that are friends with bob and then building an IN clause with that list, but I fear that would blow out the amt of memory allowed in the query set.
Seems like this is a common problem. Any ideas?
thanks
SELECT COUNT(DISTINCT auth.id)
FROM authors auth
INNER JOIN articles art ON auth.id = art.author_id
WHERE friends_with = 'bob' AND art.category = 4
Count(Distinct a.id) is required as articles might hit multiple rows for each author.
But if you have any control over the database I would use a link table for friends_with as your cussrent solution either have to use a comma seperated list of names which will be disastrous for performance and require a completly different query or each author can only have one friend.
Friends
id friend_id
then the query would look like this
SELECT COUNT(DISTINCT auth.id)
FROM authors auth
INNER JOIN articles art ON auth.id = art.author_id
INNER JOIN friends f ON auth.id = f.id
INNER JOIN authors fauth ON fauth.id = f.friend_id
WHERE fauth.name = 'bob' AND art.category = 4
Its more complex but will allow for many friends, just remeber, this construct calls for 2 rows in friends for each pair, one from joe to bob and one from bob to joe.
You could build it differently but that would make the query even more complex.
Maybe something like
select fr.name,
fr.id,
au.name,
ar.article_text,
ar.category,
ar.author_id
from authors fr, authors au, articles ar
where fr.id = ar.author_id
and au.friends_with = fr.name
and ar.category = 4 ;
Just the count...
select count(distinct fr.name)
from authors fr, authors au, articles ar
where fr.id = ar.author_id
and au.friends_with = fr.name
and ar.category = 4 ;
A version without using joins (hopefully will work!)
SELECT count(distinct id) from authors where friends_with = 'Bob' and id in(select author_id from articles where category = 4)
I found it is easier to understand statements with 'IN' in when I started out with SQL.