SQL Separate Table Keys from Group_Concat - php

I'm trying to separate the IDPosts column as a single row for each ID (attempting to create a favorite post list). The column next to it has all of the proper columns listed but are grouped, I want to obtain a row for each one grouped together in the favuserposts column. See my picture and query below. Please let me know if you need more information.
Displays everything but cannot display multiple favorite post IDs:
SELECT Users.IDUser,
Users.username,
Users.profile_picture,
Favorites.IDPosts,
GROUP_CONCAT(DISTINCT coalesce(Favorites.IDPosts,'')) as "favuserposts",
( Select body
from Posts
where Favorites.IDPosts=Posts.IDPosts
AND Users.IDUser=Favorites.IDUser)
FROM Users
LEFT OUTER JOIN Favorites ON Favorites.IDUser = Users.IDUser
GROUP BY Users.IDUser HAVING COUNT(IDPosts)>0;

You group by IDUser. So all other fields you select are aggregates in some way. Either you specify the aggregate you want (MIN, MAX, GROUP_CONCAT, etc.) or you get a random match. So let's look at what you select:
Users.username = a "random" username for the user id, but as there is just one name per userid of course, you get the one user name for the id
Users.profile_picture = same as username; you get the one picture for the user
Favorites.IDPosts = a random one of the user's post IDs
GROUP_CONCAT(DISTINCT coalesce(Favorites.IDPosts,'')) = string of all distinct IDs
( Select body from Posts ...) = a random one of the user's post bodies
So if you want a string listing all bodies, then use group_concat, just as you do with the post IDs:
GROUP_CONCAT(DISTINCT
( Select body
from Posts
where Favorites.IDPosts=Posts.IDPosts
AND Users.IDUser=Favorites.IDUser
)) as bodies

Related

Retrieving data from multiple tables and listing them in a list

I'm having some problems retrieving data from two tables and then listing them. I'd like to list the user's feed posts and their likes activity all in one.
Feeds - Table for users posts
Likes - Table for users likes (So when a use likes a post, a record is added to likes (Table likes contains data which contains the feeds ID of the post liked)
What I'm attempting to make: List BOTH feeds and user's Like activity in an ACTIVITY WALL.
So it should output like (ordered by timestamp desc):
"THIS IS A POST by user A"
Shows that user C liked user B's post
"THIS IS A POST by user B"
"THIS IS A POST by user L"
Shows that user A liked user F's post
"THIS IS A POST by user F"
-and it goes on-
My current SQL:
SELECT * FROM feeds,likes WHERE feeds.deleted!=0 or likes.deleted!=0 ORDER BY feeds.timestamp, likes.timestamp
However, my problem is I have no idea how to link both tables, since the IDs in my 'feeds' differ from those in 'likes'
To combine the two sets, you can use a UNION ALL set operator.
Something like this:
SELECT f.timestamp AS `timestamp`
, 'feed' AS `src`
, f.feed_id AS `id`
, f.feed_content AS `content`
FROM feeds f
WHERE f.deleted!=0
UNION ALL
SELECT l.timestamp AS `timestamp`
, 'like' AS `src`
, l.like_id AS `id`
, l.note AS `content`
FROM likes l
WHERE l.deleted!=0
ORDER BY 1 DESC
Note the the queries (on either side of the UNION ALL operator) need to match, in terms of the number of columns returned, and the datatype of each column.
To accommodate differences, such as extra columns returned from one table, but not from the other, you can add literal expressions in place of the "missing" columns.
The return of the extra src column is one way we can use to distinguish which query a row was returned by. It's not mandatory to return such a column, but it's something I often find useful. (The src column could be removed from each query, if it's not useful for your use case.)
Note that it's also possible to combine the results from more than two queries in this way, we'd just add another UNION ALL and another query.
The column names in the combined resultset are determined from the first query. The column names and aliases in the second query are ignored.
The ORDER BY applies to the entire set, and follows the last select.
Query should be linked via postID
F=feeds table, L=likes table, U1=usertable linked to owned feeds, U2=usertable linked to likes table
SELECT F.postTitle+' posted by '+ U1.username,'liked by'+U2.username
FROM likes L
LEFT JOIN feeds F on (F.postID=L.postID)
LEFT JOIN users U1 on (U1.userID=F.userID)
LEFT JOIN users U2 on (U2.userID=L.userID)
ORDER BY L.date,L.postID DESC
When you write SELECT * FROM feeds,likes... you are implicitly CROSS JOINing both tables. The engine will combine every record in each table with every record in the other. That is far from what you want.
I don't think you should be "linking" both tables, either. What you need, roughly speaking, is to get every post and every like, and then order that big set according to timestamps.
It sounds more like a UNION between two queries, and an ORDER BY applied to the whole UNION. UNIONs are never easy on the eye, by the way...
The thing with UNIONs is that both sub-queries need to return the same amount of columns. Not knowing exactly which columns you have, I'll show you one possible solution:
SELECT activity, timestamp FROM (
( SELECT CONCAT(u.name,' posted ',f.content) as activity, timestamp
FROM user u
JOIN feed f on (f.user_id=u.id)
WHERE f.deleted!=0
) UNION
( SELECT CONCAT(u.name, ' liked a post by ',u2.name) as activity, timestamp
FROM user u
JOIN likes l on (l.user_id=u.id)
JOIN feed f on (l.feed_id=f.id)
JOIN user u2 on (f.user_id=u2.id)
WHERE l.deleted!=0
)
) as whole_result
ORDER by timestamp desc
You should, of course, modify this to match your structure.
Hope this helps!
I think, it's better to use 3rd table, say, "actions", and insert to it real actions. Then just select rows from this table, joined to "posts" & "users" table.
When user posts articles, o likes an article, insert corresponding row to "actions" table.
actions table:
|id|action_name|user_id|post_id| date |
1 posted 3 3 5/7/2014
2 liked 5 3 5/7/2014
3 liked 4 3 6/7/2014
4 posted 5 6 7/7/2014
5 liked 3 6 7/7/2014
SELECT user_name a, post_title b, action_name c FROM actions c LEFT JOIN users a ON a.id=c.user_id LEFT JOIN posts b ON b.id = c.post_id ORDER BY c.date DESC LIMIT 10
Then, in loop, choose how to display this data, according to "action_name".
In such way you can expand your wall for other activities, +use indexes for better database performance.

My query doesn't work. What can I do? [duplicate]

This question already has answers here:
Can I concatenate multiple MySQL rows into one field?
(16 answers)
Closed 8 years ago.
I would like to display a <table> with these <th>-s:
project title
description
status
start date
end date
users
The problem is, that my subquery returns more than 1 row.
Usually 4 people belong to a project, an owner, a manager, a developer and a tester. The owner can create new projects, so he can see the project table I would like to create, but I can't put the users at the end of the table, because more than one belongs to every row.
This is the query:
SELECT project.id, title, description, status, start,end
FROM project
LEFT JOIN users ON (SELECT userid FROM roles,project WHERE projectid=project.id)=users.id
My tables are:
PROJECT
id
title
description
status
start
end
USERS
id
name
pass
email
ROLES
id
projectid
userid
rolename
Try join all tables instead of subquery like this :
SELECT project.id, title, description, status, start,`end` ,GROUP_CONCAT( users.name) AS usernames
FROM project
LEFT JOIN roles On roles.projectid = project.id
LEFT JOIN users ON roles.userid =users.id
GROUP BY project.id
Use an IN clause, e.g.
JOIN users ON users.id IN (SELECT userid FROM ....)
= is for comparing two single values. IN is used to check if a single value is in a set of values.
I assume you want all the users in a cell of your result table but only one copy of the other fields in their cells. If you really want this in one table you need to parse the results in code. Use a query that has all the fields you want like so:
SELECT project.id, title, description, status, start,end, users.name
FROM project
LEFT JOIN roles On roles.projectId = project.id
LEFT JOIN users ON roles.userId = users.id
Then in your code check for the duplicates and don't display them. Pseudo code example:
loop over all result rows {
if( new_id <> old_id ){
if not first row close your previous row </tr>
display <tr>
display all the fields
}else{
only display the addition of the user name (with a <br> or whatever to separate)
}
}
close your last row </tr>
This will probably display it the way you want but the data is not aligned for one table of display. Consider a link to show the user list as a popup DIV.

Showing users who liked an item in an item list

This is an issue that I've deemed impractical to implement but I would like to get some feedback to confirm.
I have a product and users database, where users can like products, the like data is stored in a reference table with just pid and uid.
The client request is to show 3 users who have liked every product in the product listing.
The problem is, its not possible to get this data in one query for the product listing,
How I once implemented and subsequently un-implemented it was to perform a request for the users who have liked the products during the loop through the product list.
ie.
foreach($prods as $row):
$likers = $this->model->get_likers($row->id);
endforeach;
That works, but obviously results in not only super slow product listings, and also creates a big strain on the database/cpu.
The final solution that was implemented was to only show the latest user who has liked it (this can be gotten from a join in the products list query) and have a link showing how many people have liked, and upon clicking on it, opens a ajax list of likers.
So my question is, is there actually a technique to show likers on the product list, or is it simply not possible to execute practically? I notice actually for most social media sites, they do not show all likers on the listings, and do employ the 'click to see likers' method. However, they do show comments per items on the listing, and this is actually involves the same problem doesn't it?
Edit: mock up attached on the desired outcome. there would be 30 products per page.
By reading your comment reply to Alex.Ritna ,yes you can get the x no. of results with per group ,using GROUP_CONCAT() and the SUBSTRING_INDEX() it will show the likers seperated by comma or whatever separator you specified in the query (i have used ||).ORDER BY clause can be used in group_concat function.As there is no schema information is available so i assume you have one product table one user table and a junction table that maintains the relation of user and product.In the substring function i have used x=3
SELECT p.*,
COUNT(*) total_likes,
SUBSTRING_INDEX(
GROUP_CONCAT( CONCAT(u.firstname,' ',u.lastname) ORDER BY some_column DESC SEPARATOR '||'),
'||',3) x_no_of_likers
FROM product p
LEFT JOIN junction_table jt ON(p.id=jt.product_id)
INNER JOIN users u ON(u.id=jt.user_id)
GROUP BY p.id
Fiddle
Now at your application level you just have to loop through the products and split the x_no_of_likers by separator you the likers per product
foreach($prods as $row):
$likers=explode('||',$row['x_no_of_likers']);
$total_likes= $row['total_likes'];
foreach($likers as $user):
....
endforeach;
endforeach;
Note there is a default 1024 character limit set on GROUP_CONCAT() but you can also increase it by following the GROUP_CONCAT() manual
Edit from comments This is another way how to get n results per group, from this you can get all the fields from your user table i have used some variables to get the rank for product group ,used subquery for junction_table to get the rank and in outer select i have filtered records with this rank using HAVING jt.user_rank <=3 so it will give three users records per product ,i have also used subquery for products (SELECT * FROM product LIMIT 30 ) so the first 30 groups will have 3 results for each,for below query limit cannot be used at the end so i have used in the subquery
SELECT p.id,p.title,u.firstname,u.lastname,u.thumbnail,jt.user_rank
FROM
(SELECT * FROM `product` LIMIT 30 ) p
LEFT JOIN
( SELECT j.*,
#current_rank:= CASE WHEN #current_rank = product_id THEN #user_rank:=#user_rank +1 ELSE #user_rank:=1 END user_rank,
#current_rank:=product_id
FROM `junction_table` j ,
(SELECT #user_rank:=0,#current_rank:=0) r
ORDER BY product_id
) jt ON(jt.product_id = p.id)
LEFT JOIN `users` u ON (jt.`user_id` = u.`id`)
HAVING jt.user_rank <=3
ORDER BY p.id
Fiddle n results per group
You should be able to get a list of all users that have liked all products with this sql.
select uid,
count(pid) as liked_products
from product_user
group by uid
having liked_products = (select count(1) from products);
But as data grows this query gets slow. Better then to maintain a table with like counts that is maintained through a trigger or separately. On every like/dislike the counter is updated. This makes it easy to show the number of likes for each product. Then if the actual users that liked that product is wanted do a separate call (on user interaction) that fetches the specific likes for one product). Don't do this for all products on a page until actually requested.
I am assuming the size of both these tables is non-trivially large. You should create a new table (say LastThreeLikes), where the columns would be pid,uid_1,uid_2 and uid_3, indexed by pid. Also, add a column to your product table called numLikes.
For each "like" that you enter into your reference table, create a trigger that also populates this LastThreeLikes table if the numLikes is less than 3. You can choose to randomly update one of the values anyway if you want to show new users once in a while.
While displaying a product, simply fetch the uids from this table and display them back.
Note that you also need to maintain a trigger for the "Unlike" action (if there is any) to re-populate the LastThreeLikes table with a new user id.
Problem
The problem is the volume of data. From the point of view that you need two integer value as a answer you should forget about building a heavy query from your n<->n relations table.
Solution
Generates a storable representation using the file_put_contents() with append option each time a user likes a product. I don't have enough room to write the class in here.
public function export($file);
3D array format
array[product][line][user]
Example:
$likes[1293][1][456]=1;
$likes[82][2][656]=1;
$likes[65][3][456]=1;
.
.
.
Number of users who like this particular product:
$number_users_like_this_product = count($likes[$idProduct]);
All idUser who like this particular product:
$users_like_this_product = count($likes[$idProduct][$n]);
All likes
$all_likes = count($likes);
Deleting a like
This loop will unset the only line where $idProduct and $IdUser you want. Since all the variables are unsigned integer it is very fast.
for($n=1, $n <= count($likes[$idProduct]), $n++)
{
unset($likes[$idProduct][$n][$idUser]);
}
Conclusion
Get all likes will be easy as:
include('likes.php');
P.S If you want to give a try i will be glad to optimize my stuff and share it. I've created the class in 2012.

Using an array in an SQL query

Okay, so I want to have a news feed on my website. I have 3 tables named Users, Follow, and Posts. Basic user data goes into the Users table, who is following who is stored in the Follow table, and whatever the user posts goes into Posts. Now, I know how to select every post from a database table and limit it using the WHERE clause. But how would I write my query to select all all of the posts from only user's they are following, and display them by DESC date? Thanks.
Here's a general layout for you, make three tables like you mentioned, I've outlined below just as an example
[TABLES]
users
followers
posts
In the users table you should have at least columns like userid (auto incremented value / primary key).
The followers table should have something like userid which would match to the users table userid, a followid column which would also have the id # for the follower from the users table.
Then for your posts table you would want to have userid too so when each post is made it would have the id from users table.
Then you would need to do something like:
SELECT p.*
FROM posts AS p
WHERE p.userid IN (SELECT followid FROM followers WHERE userid = ###)
ORDER BY p.date DESC
Now it really depends on how you are getting the users id to figure this out. If your passing the users id using a session after they logged in similar to something like Facebook, then you could change userid = ### to something like userid = ".$_SESSION['userid']." But again it really depends on how you pass the users id but the above should at least get you somewhat started.
Make sure to put indexes on the userid, followid columns so that when the table becomes larger it will do the joins quickly.
An alternative to #Shane's answer is to use the JOIN operator:
'SELECT p.* // I prefer to name all the fields, but for brevity's sake I'll use the shorthand
FROM Posts AS p
INNER JOIN Follow AS f ON p.userid = f.followid
WHERE f.userid = '.$selectedUserID.'
ORDER BY p.date DESC;'
For an inputed User ID ($selectedUserID), it will find all User ID's of the people they follow (matching follow ID to user ID on the Follow x-ref table) and then find their respective posts from the Post table in descending order by date. It will return empty if they do not follow anyone or the people they follow have no posts.
I also hope I do not need to remind you to sanitize your input to the database and escape your output to the web.
Is this what you're looking for?

Mysql relationships and getting linked data

I have been doing this for a while now, via some php, first lets say we have two tables:
Users
user_id name email
Images
image_id user_id url
user_id and user_id from images table would be linked with a relationship.
Now what I would do is select the user by their Id, check if the user is found, if so then make another query to images table, and check for num rows and loop through the return, is there a function that I could use that would allow me to just select the user and all the images that are linked to the user without doing a joint query.
Thank you for any help
When you say "without doing a joint query" I think you mean "without doing two queries."
In fact, what you want is probably a LEFT JOIN. The idea is that you select users from the user table matching some ID, and LEFT JOIN the images table. The left join will give you null values if no images exist for the user. If you use a normal join, the fact that no matching records exist in the images table will result in no rows returned.
Here is an example:
SELECT u.name, u.email, i.url
FROM Users u
LEFT JOIN Images i ON (i.user_id = u.user_id)
WHERE u.id = #SpecificUserID;
Assuming the user id is found and there are some images for that user, you will get a result that looks like this:
name email url
----- ----- -----
John j#a.com abc.jpg
John j#a.com def.jpg
John j#a.com ghi.jpg
Now as you can see, the name and email values keep repeating. You get a unique image url for each row and the matching username and email.
If you only select one user at a time, this is simple to process in a loop. On the first iteration read all three values. On subsequent iterations just read the url, adding it to your list or array.
Here is a useful tutorial on joins: Understanding JOINs in MySQL and Other Relational Databases
This can be done in one query rather than two by using an inner join to get your result set.
$sql = "SELECT u.user_nid, i.url
FROM tbl_user u
INNER JOIN i.user_nid = u.user_nid
WHERE user_nid = ?"
With this query you will receive a list of the users images and if there are no images returned or the user does not exist, than you will have a row return of zero.
You'll have to use a join to retrieve data from multiple tables in a single query.
The foreign key relationships enforce constraints. Ex: You can't insert a record into Table A referring to a key in Table B without the record actually being in Table B.

Categories