mysql follow and retweet-like functionality - mysql

This is a bit challenging but fun question.
Consider having these tables
tweets
tweet_id | retweet_of_id | user_id
follow
user_id | followed_user_id
So we store each "retweet as a separate tweet" pointing to the original tweet's id (retweet_of_id). This is because I want to have comments under each one separately.
If something is not a retweet then retweet_of_id will be 0.
How do I retrieve the following using MySQL efficiently?
My own tweets
All the original tweets (from users that I follow)
And the first retweet (by a user I follow) of a tweet (from a user that I don't follow)
And that the result should a combination of both (in order) just like how twitter does it.
Please consider that there may be 1,000,000 tweets and we only need the most recent ones (e.g.: 10).
Here is an example (I'm user 1 and I follow user 2 & 3)
tweet_id | retweet_of_id | user_id
----------------------------------
1 0 4 <- EXCLUDE (I don't follow user 4)
2 0 2 <- INCLUDE (I follow user 2)
3 0 3 <- INCLUDE (I follow user 3)
4 1 2 <- INCLUDE (I follow user 2 & first RT)
5 1 3 <- EXCLUDE (I already have the first RT)
6 2 3 <- EXCLUDE (I already have the orignal)
7 0 1 <- INCLUDE (My own tweet)
So the final order should be these tweets: 7, 4, 3, 2 (starting with the most recent)

Here's how I solved it
(both of these assume that the tweets are ordered by their tweet_id ASC)
Solution 1 (correct, runs fast)
SELECT tweet_id,
FROM tweets
WHERE user = 1 OR user IN (2,3)
GROUP BY IF(retweet_of_id = 0, tweet_id, retweet_of_id)
ORDER BY tweet_id DESC
Solution 2 (gives correct results, but it's dog slow for 1,000,000 tweets)
SELECT p1.tweet_id FROM tweets p1
LEFT JOIN tweets p2
ON p2.user IN (2,3)
AND p1.tweet_id > p2.tweet_id
AND (p1.retweet_of_id = p2.tweet_id
OR p1.retweet_of_id AND p1.retweet_of_id = p2.retweet_of_id )
WHERE p2.tweet_id IS NULL
AND (p1.user = 1 OR p1.user IN (2,3))
ORDER BY p1.tweet_id DESC

all the original tweets (from users that I follow)
1 users that i follow:
select user_id from follow where followed_user_id= MyOwnID
2 all the original tweets:
select * from tweets where retweed_of_id=0
both combined:
select * from tweets where retweed_of_id=0 and
user_id in (select user_id from follow where followed_user_id= MyOwnID)
that should be it - or did i miss something?

Related

Get records from multiple tables, but only show 1 per ID?

First of all im sorry for the title, it's difficult to explain what I'm trying to achieve.
I have 2 tables, a table for property records, and a table for the images uploaded for each property.
In my listing_details table I enter 1 record per property that has a unique ID and property slug. I have a prop_gallery table where I can have hundreds of records that share the same property slug so I can relate it back to my my property.
I'm trying to write a query to pull the records from both tables, but I only want to show each property once, at the moment it's looping through all the records in the gallery and showing that property for as many records their are in the gallery. Hope this makes sense?
My query is...
$listings = $db->query('
SELECT *
FROM listing_details
JOIN prop_gallery
ON prop_gallery.prop_gallery_id = listing_details.prop_slug
WHERE (prop_slug LIKE prop_gallery_id OR prop_gallery_id LIKE prop_slug)
AND listing_details.prop_mandate = 1'
)->fetchAll();
If there's a property called Liams house then there will be a record for that in listing_details and if I've uploaded 10 pictures, there will be 10 records for that in prop_gallery.
When I loop through my results this means I'm now showing Liams house 10 times, when I want to show it just the once.
EDIT
Result of the above query
prop_id prop_agent prop_title prop_slug prop_mandate id prop_gallery_id prop_gallery
37 2 House in switzerland house-in-switzerland 1 4 6 main1.png
37 2 House in switzerland house-in-switzerland 1 4 6 main2.png
37 2 House in switzerland house-in-switzerland 1 4 6 main3.png
You can use the ROW_NUMBER() function. Assuming you have a [any] property in the table listting_details you can sort rows by you can do it cleanly; I assumed the property recorded_at.
For example:
SELECT *
FROM (
SELECT *,
row_number() over(partition by prop_slug order by recorded_at) as rn
FROM listing_details d
JOIN prop_gallery g
ON g.prop_gallery_id = l.prop_slug
WHERE prop_slug LIKE prop_gallery_id OR prop_gallery_id LIKE prop_slug
AND d.prop_mandate = 1
) x
where rn = 1

how to select specific rows in some condition

I tried to write a query that selects rows with steps that both user 1 and user 2 did, with combined number of times they did the step (i.e., if user 1 did step 1 3 times and user 2 did 1 time then the count should show 4 times.)
when I put condition as user_id=1, user_id=2 there is no error but it return nothing, when it should return some rows with values.
there is table step, and step taken
and table step has column id, title
table step_taken has column id, user_id(who performs steps), step_id
i want to find step that both of two user whose id 1,2 did
and also want to have the value as count added up how many times they performed that step.
for example if user id 1 did step named meditation 2 times,
and user id 2 did step named meditation 3 times,
the result i want to find should be like below ;
------------------------------
title | number_of_times
------------------------------
meditation| 5
------------------------------
here is my sql query
select title, count(step_taken.step_id)as number_of_times
from step join step_taken
on step.id = step_taken.step_id
where user_id = 1 and user_id=2
group by title;
it returns nothing, but it should return some rows of step both user1 and user 2 did.
when i wrote same thing only with user_id=1 or user_id=2, it shows selected information
how can I fix my code so it can show the information I want to get?
thanks in advance :)
user_id cannot be 1 and 2 at the same time. You need a second user table. Then join those on your criteria and count:
select title, count(u1.id) + count(u2.id) as number_of_times
from step u1 join step u2
on u1.id = u2.id
where u1.user_id = 1 and u2.user_id=2
group by title;
note: cannot tell what table title is in, or the purpose of step_taken was as step.id is identical.

How to get history of mapping for following table data

I have a history mapping table for UserId changes, where every time when UserId changes, a row for new UserId with old UserId inserted in the history table.
Below is the sample table and data:
UserIdNew | UserIdOld
---------------------
5 | 1
10 | 5
15 | 10
The above data explains that UserId 1 has gone with following transition from UserId 1 -> 5-> 10 -> 15.
I want to query all the Old Ids for a give UserIdNew, how can I do it in a single query?
For this case if UserIdNew = 15, then it should return 1,5,10
If UserIdNew are always greater then previous (older) in a UserIds chain, i.e. if cases like 10->20->5->1 never happen, this query can do the job (not fully tested, new and old used instead of your field names):
SELECT
CASE
WHEN new=7 THEN #seq:=concat(new,',',old)
WHEN substring_index(#seq,',',-1)=new THEN concat(#seq,',',old)
ELSE #seq
END AS SEQUENCE
FROM (SELECT * FROM UserIdsTable ORDER BY new DESC) AS SortedIds
ORDER BY SEQUENCE DESC
LIMIT 1

SQL-query with sorting

Please help by writing a SQL-script that will collate data.
A key difficulty - need to create an additional column on which sorting will take place.
I tried to describe the situation as detailed as possible.
Let's get started. There is a table of the following form:
We will receive a user ID and return data, only those who do not have he, but there are others.
Next step: sort by artificially created column.
Next, I'll step by step.
So what do I mean by artificial column:
This column will contain the difference between the estimates. So to get it - you need to first perform a number of actions:
According to the information which is like set the user and at other user to calculate the difference in assessment, and get an average score.
The following two pictures show the same data and then the calculation itself, it seems to me - it's pretty simple.
Calculation of this column is as follows:
User with 2nd id:
1: 5 - 1 = 4;
2: 2 - 9 = -7;
3: next data what is in user 1 - absent in user 2, and we ease pass it;
User with 3rd id:
1: 3 - 1 = 2;
2: the next data's is absent in user with 3rt id;
3: 8 – 9 = -1;
4: 6 – 2 = 4;
5: passed;
End in the end:
User_2 will have new mark = -1.5
User_3 will have new mark = 1.66666
And in the end I need to return the table:
But that's not all. Often, the data will be duplicated and I'd like to get average results from the data obtained. Please look at the following example:
And this is the end. I really need your help, experts. I teach sql code myself, but it is very difficult for me.
Had the idea of ​​making the script as follows:
SELECT d.data, (d.mark + myCount(d.user, 1)) newOrder
FROM info d
WHERE -- data from user_1 NOT equal data from other users
ORDER BY newOrder;
But the script will execute a lot of time, because it uses its own function that could do with a query to each user, and not to record. I hope someone will be able to cope with this task.
Following your steps:
First, we need to isolate the data from the selected user (let's assume it's 1):
CREATE TEMP TABLE sel_user AS
SELECT data, mark FROM info d WHERE user = 1;
Now, we calculate the mark for every other user (again, the selected user is 1):
SELECT d.user user, d.mark - s.mark mark
FROM info d JOIN sel_user s USING (data)
WHERE d.user <> 1;
Result:
user mark
---------- ----------
2 4
2 -7
3 2
3 -1
3 4
We can query just the average:
SELECT d.user user, AVG(d.mark - s.mark) mark
FROM info d JOIN sel_user s USING (data)
WHERE d.user <> 1 GROUP BY user;
user mark
---------- ----------
2 -1.5
3 1.66666666
But you still want to do calculations with the marks that do not correspond to user 1:
SELECT d.user user, mark FROM info d
WHERE d.user <> 1 AND d.data NOT IN (SELECT data FROM sel_user);
user mark
---------- ----------
2 4
3 3
3 10
Specifically, you want to add the previously calculated average to each row:
SELECT d.user user, d.data, d.mark + d2.mark AS neworder FROM info d JOIN (
SELECT d.user user, AVG(d.mark - s.mark) mark
FROM info d JOIN sel_user s USING (data)
WHERE d.user <> 1 GROUP BY user
) d2 USING (user)
WHERE d.data NOT IN (SELECT data FROM sel_user)
ORDER BY neworder DESC;
user data neworder
---------- ---------- ----------------
3 6 11.6666666666667
3 3 4.66666666666667
2 5 2.5
And your last request is to get the average for each data:
SELECT data, AVG(neworder) final FROM (
SELECT d.user user, d.data, d.mark + d2.mark AS neworder FROM info d JOIN (
SELECT d.user user, AVG(d.mark - s.mark) mark
FROM info d JOIN sel_user s USING (data)
WHERE d.user <> 1 GROUP BY user
) d2 USING (user)
WHERE d.data NOT IN (SELECT data FROM sel_user)
)
GROUP BY data
ORDER BY final DESC;
data final
---------- ----------------
6 11.6666666666667
3 4.66666666666667
5 2.5

SQL query - joining two tables with a conditional

For each area in my game I have many levels that can be achieved. Once a user earns a certain number of points in an area, his 'progress level' increases for that particular area. I have two tables in my database. One stores the progress of the user for a particular area of my game:
Table A
userID | areaID | progressLevel | total points earnt
1 1 1 1000
1 2 1 500
Another table, B, stores how many points are required to unlock increase the progress level
areaID | progressLevel | points required
1 2 5000
1 3 9000
1 4 11000
2 2 9999
When enough points are achieved by the user then I check table B and increase the progress level of the user in table A. For example, if user 1 earns over 5000 points in area 1, I would update table A and set progress level = 2.
My problem is I want to write a query to obtain, for a particular user, all their progress levels for each area as well as the number of points required for the next level. For example, for user with id 1, I would like:
areaID | progressLevelCurrent | total points earnt | points required for next progress level
1 1 1000 4000
2 1 500 9499
Is it possible to do this in a single query?
How about this:
select A.areaID, A.progressLevel as progressLevelCurrent, A.`total points earnt`, B.`points required` - A.`total points earnt` as `points required for next progress level`
from A
inner join B on A.areaID = B.areaID and (A.progressLevel + 1) = B.progressLevel
where B.`points required` > A.`total points earnt`;
SELECT
areaID,
progressLevel AS CurrentLevel,
`total points earnt` as TotalNow,
(
SELECT (pointsTilNext - TableA.TotalNow)
FROM TableB
WHERE TableB.progressLevel = (TableA.progressLevel+1)
) AS ToNextLevel
FROM TableA
WHERE userID = ##
Edited to add:
You could also use a join, which would be a more efficient use of server capacity. The left join will return a result for a person even if the person is at the highest level, ie there is no row matching TableA.progressLevel+1
SELECT
areaID,
progressLevel AS CurrentLevel,
`total points earnt` as TotalNow,
(pointsTilNext - TableA.TotalNow) AS ToNextLevel
FROM TableA
LEFT JOIN TableB ON TableB.progressLevel = (TableA.progressLevel+1)
WHERE userID = ##