How to retrieve all posts with all comments using 2 SQL queries - php

I am trying to make a page with a list of posts, and underneath each post all the comments belonging to that post. Initially I wanted to use just one query to retrieve all the posts + comments using the SQL's JOIN, but I find it impossible to for example retrieve a post with multiple comments. It only displays the posts with a maximum of 1 comment per post, or it just show a post multiple times depending on the amount of comments.
In this related question, somebody talked about using 2 queries:
How to print posts and comments with only one sql query
But how do I do this?
I've got the query and a while loop for posts, but I obviously don't want to run a query for comments for each post inside that loop.
$getPost = mysql_query('SELECT p.post_id,
p.user_id,
p.username,
p.content
FROM post p
ORDER BY p.post_id DESC');
while($row = mysql_fetch_array($getPost))
{
...
}
Table structure (reply is the table for storing comments):
POST (post_id (primary key), user_id, username, content, timestamp)
REPLY (reply_id (primary key), post_id, username, reply_content, timestamp)

You can do it in a single query, which is OK if the amount of data in your original posts is small:
$getPost = mysql_query('SELECT
p.*,
r.reply_id, r.username r_username, r.reply_content, r.timestamp r_timestamp
FROM post p
left join reply r
ORDER BY p.post_id DESC'
);
$posts = array();
$last_id = 0;
while($row = mysql_fetch_array($getPost))
{
if ($last_id != $row['post_id']) {
$posts[] = array(
'post_id' => $row['post_id'],
'user_id' => $row['user_id'],
'username' => $row['username'],
'content' => $row['content'],
'timestamp' => $row['timestamp'],
'comments' => array()
);
}
$posts[sizeof($posts) - 1]['comments'][] = array(
'reply_id' => $row['reply_id'],
'username' => $row['r_username'],
'reply_content' => $row['reply_content'],
'timestamp' = $row['r_timestamp']
);
}
Otherwise, break it into two queries like so:
$getPost = mysql_query('SELECT
p.*,
FROM post p
ORDER BY p.post_id DESC'
);
$rows = array();
$ids = array();
$index = array();
while($row = mysql_fetch_assoc($getPost)) {
$row['comments'] = array();
$rows[] = $row;
$ids[] = $row['post_id'];
$index[$row['post_id']] = sizeof($rows) - 1;
}
$getComments = mysql_query('select r.* from replies r where r.post_id in ("'
. join('","', $ids)
. '")');
while ($row = mysq_fetch_assoc($getComments)) {
$rows[$index[$row['post_id']]]['comments'][] = $row;
}
... Or something like that. Either option allows you to litter your first query with WHERE clauses and so forth to your heart's content. The advantage of the 2nd approach is that you don't re-transmit your original post data for each comment!

In order to also get those posts without comments, you need to use a LEFT OUTER JOIN. In that case, any row in the first table without any corresponding rows in the second table will be paired with a row consisting of null values.
SELECT * FROM posts
LEFT OUTER JOIN comments ON posts~post_id = comments~post_id;
By the way: there is also a RIGHT OUTER JOIN. When you would use that, you would get all comments, including those where the parent post got lost somehow, but no posts without comments.

Related

Have 2 queries and wondering if they can be combined

So i have 2 queries, I am trying to run one inside of a loop (unsuccessfully), but I'm thinking there might be a possibility that I can combine the 2 of them.
Table profile_posts
ID
post_title
post_body
user_id
post_date
Table profile_posts_likes
ID
likes
user_like_id
post_id
$baseURL = 'Data.php'; //Grab page for pagination
$id = $_GET['id']; //Grab id from link
$limit = 10; //Limit returns to 10
//Select statement that grabs results from profile_posts table
$postQuery = "SELECT * FROM profile_posts WHERE user_id = :id ORDER BY post_date DESC LIMIT $limit"; //First Query
// Count of all records
$rowCount = countRecords($conn, $id);
// Initialize pagination class
$paginationConfig = array(
'postID' => $id,
'baseURL' => $baseURL,
'totalRows' => $rowCount,
'perPage' => $limit,
'contentDiv' => 'postContent',
'link_func' => 'searchFilterProfile'
);
$pagination = new Pagination($paginationConfig);
$profilePost = $conn->prepare($postQuery);
$profilePost->bindParam(":id", $id);
$profilePost->execute();
if($profilePost->rowCount() > 0){
foreach($postDisplay as $row){
$likeQuery = "SELECT id, COUNT(likes) as likeCount, user_Like_id, post_id FROM profile_posts_likes WHERE post_id = :postID"; //Second Query
$likeQuery = $conn->prepare($likeQuery);
$likeQuery->bindParam(":postID", $row['id']); //Grab id from first query
$likeQuery->execute();
$postlikeQuery = $likeQuery->fetchAll();
if($likeQuery->rowCount() > 0){
//Display like buttons, when user clicks "Like" button, send data through Ajax
//and update page with "Liked"
}
}
}
What this does is displays the posts on the users profile page, and then when a user views their page, they can 'Like' the post or 'unlike it'...Using Ajax to update page
Now is there a way that I can combine those 2 queries together instead of running one inside of the loop. I tried tossing a WHERE EXISTS in there to combine the Select statements, but no luck.
Appreciate any help. Thanks in advance.
You may express your query using a join:
SELECT ppl.id, ppl.user_Like_id, ppl.post_id
FROM profile_posts_likes ppl
INNER JOIN
(
SELECT *
FROM profile_posts
WHERE user_id = :id
ORDER BY post_date DESC
LIMIT $limit
) pp
ON pp.id = ppl.post_id;
Selecting COUNT in the second query makes no sense, as you are not using GROUP BY.

How to group my results and have all rows shown?

SELECT i.itemsname
, i.itemsprice
, i.itemsdescrip
, c.catname
, c.catdes
, c.status
, c.collapse
, c.catid
FROM items i
LEFT
JOIN categories c
ON c.catid = i.catid
WHERE i.restid
AND c.restid =12
GROUP
BY c.catid
that is my query at the moment but I would like to have something like this....
but this is what I'm getting:
Ok, I lied in the comments, so With PDO (haven't tested it)
$stmt = $PDO->prepare('SELECT
categories.catname,
items.itemsname,
items.itemsprice,
items.itemsdescrip,
categories.catdes,
categories.status,
categories.collapse,
categories.catid
FROM items
LEFT JOIN categories ON items.catid=categories.catid
WHERE items.restid AND categories.restid = :restid');
$stmt->execute([':restid' => 12]);
$data = $stmt->fetchAll(\PDO::FETCH_GROUP);
foreach($data as $catname => $rows){
//echo group html stuff
//echo "<dl>";
//echo "<dt>$catname</dt>".;
foreach($rows as $row){
//echo row data stuff
// echo "<dd> {stuff} </dd>";
}
//echo "</dl>";
}
I'll leave the html up to you. But as I said you want a data structure like this
[
'BREAKFASTS' => [
0 => [ name => "wimpy hamburger", description => "bla bla", price => "$100,000"],
1 => [ ... ]
],
'SINGLE BURGERS' => [ ...]
]
note that the first field after "SELECT" is by default the field used by FETCH_GROUP
See in this way, the first foreach can output the title of the category, which is BREAKFASTS for example. Then the inner foreach can do the individual rows in the table.
Personally I would use a dl, dt, dd tag setup as my structure (hinted in the comments, i really am to lazy today to code all the html, <sigh>)
https://www.w3schools.com/tags/tag_dt.asp
UPDATE
You may want to check your query
...
WHERE
items.restid AND ...
Seems to be flawed, just saying. I saw this while optomizing the query for sorting.
SELECT
c.catname,
i.itemsname,
i.itemsprice,
i.itemsdescrip,
c.catdes,
c.status,
c.collapse,
c.catid
FROM
(
SELECT c0.catid FROM categories AS c0 WHERE c0.restid = :restid SORT BY c0.catname
) AS t
JOIN
categories AS c ON t.catid=c.catid
LEFT JOIN
items AS i ON items.catid=categories.catid
WHERE
items.restid = ? //< this is the error/omission/strangeness i pointed out above.
So a few things to note, first you should base the query off the categories, as an empty category should be shown, while an item without a category will blow it all to bits ( basically, ie how can you group them by the category if they have none ) You'll wind up with some hodgepoge of items with no category at the end, of course based on your example I'm assuming a Many to One relationship. For example One category can have Many items, and Many items can belong to a category. (it's probably more ideal to do a Many to Many, but that's another story for another day)
The reason the above query is more optimized is the inner query, creates only a small temp table using the catid, And sorts on just the data from the cat table and only the data that is pulled by the where.
Then as we move to the outer query, they basically inherent the sort from the join, and we can pull the rest of the data from that. It's typically about 2-10x faster this way (of course I haven't test this particular query) in theory. Of course this is a bit more complex/advanced query and is optional, but it should improve sort performance if my mind is in the right place tonight... lol
Also I abbreviated your table names (alias), as I said I am lazy like that. Sadly my answers are always so long, dont ask me how I see all these issues, it's just experience or how my dyslexic brain works?
Lastly, if you really must use mysqli, you can manually group them with something like this.
$data = [];
while(false !== ($row = $res->fetch_assoc())){
$key = $row['catname'];
if(!isset($data[$key])) $data[$key] = [];
$data[$key][] = $row;
}
It's all so prosaic (common place, non-poetic) at this point for me.
Good luck.
$cat = mysqli_query($connect, "SELECT
categories.catname,
items.itemsname,
items.itemsprice,
items.itemsdescrip,
categories.catdes,
categories.catid
FROM items
LEFT JOIN categories ON items.catid=categories.catid
WHERE items.restid AND categories.restid = 12");
if($cat === FALSE) {
die(mysqli_error());
}
$data = [];
while ($rowb = mysqli_fetch_array($cat)) {
$key = $rowb['catname'];
if(!isset($data[$key])) $data[$key] = [];
$data[$key][] = $rowb;
foreach($data as $catname => $rowbs){
echo "
<dl><button class='accordiontry'><dt>$catname</dt></button>";
<div class='panel1'>
foreach($rowbs as $rowb){
echo"<div class='rmenu'>
<dd><span class='item'>{$rowb['itemsname']}</span>
<span class='price'>£{$rowb['itemsprice']}</span><br>
<span class='des'>{$rowb['itemsdescrip']}</span> ";
}
echo"</div></dd>
</div></dl>";
}
}
}

mysql query to an array

I am new to php. I can't figure out how to query my db to list this mockup. For each array, I want to query the db to list only what is related to the left in ''.
My db consist of 2 tables.
table1 = album
id, name
table2 = picture
id, album, picPath
$q_albums = array(
'People' => array(
ALBUM_PATH.'4.jpg',
ALBUM_PATH.'11.jpg',
ALBUM_PATH.'10.jpg'),
'Nature' => array(
ALBUM_PATH.'2.jpg',
ALBUM_PATH.'3.jpg',
ALBUM_PATH.'5.jpg',
ALBUM_PATH.'6.jpg',
ALBUM_PATH.'7.jpg'),
'Art' => array(
ALBUM_PATH.'1.jpg',
ALBUM_PATH.'8.jpg',
ALBUM_PATH.'9.jpg',
ALBUM_PATH.'2.jpg'),
'Wilderness' => array(
ALBUM_PATH.'3.jpg',
ALBUM_PATH.'2.jpg',
ALBUM_PATH.'5.jpg',
ALBUM_PATH.'7.jpg',
ALBUM_PATH.'6.jpg'),
'Photography' => array(
ALBUM_PATH.'8.jpg',
ALBUM_PATH.'1.jpg',
ALBUM_PATH.'9.jpg',
ALBUM_PATH.'12.jpg'),
'Fashion' => array(
ALBUM_PATH.'11.jpg',
ALBUM_PATH.'4.jpg',
ALBUM_PATH.'10.jpg'),
);
As dev-null-dweller notes, you need to build the array from the query results in a loop. Fortunately, it's pretty easy:
$sql = <<<END
SELECT album.name AS albumName, picture.picPath AS picPath
FROM album JOIN picture ON album.id = picture.album
END;
$res = $mysqli->query( $sql );
$q_albums = array();
while ( $row = $res->fetch_object() ) {
// this line actually build the array, entry by entry:
$q_albums[ $row->albumName ][] = $row->picPath;
}
$res->close();
Edit: As Mr. Radical notes, if you want your results to include empty albums, you should change the JOIN to a LEFT JOIN. If you do that, the query will return a row with a null picPath for any empty albums, so you'll have to deal with those null paths somehow. One way would be to include something like the following code after building the array:
foreach ( $q_albums as $album => &$pics ) {
// remove dummy null entries from empty albums
if ( !isset( $pics[0] ) ) array_shift( $pics );
}
#Ilmari Karonen I would use LEFT JOIN instead of JOIN.
SELECT album.name AS albumName, picture.picPath AS picPath
FROM album LEFT JOIN picture ON album.id = picture.album

Combine mysql query with sub query results into one PHP array

I have a table that contains events, to list these events I loop through the event table, check the event type and look up it's value in it's specific table with it's eventId.
At the moment this uses one query to get the 20 events and then up to 3 queries to get the data on those events. I currently have it coded procedurally but need to change it so that at the end it just returns the data in array form.
Here's a Pseduo code example of what I need to achieve:
while(eventQuery):
if commentQueryResult;
$array .= commentQueryResult;
if forumPostQueryResult;
$array .= forumPostQueryResult;
if uploadItemQueryResult;
$array .= uploadItemQueryResult;
endwhile;
return $array; // Combined returned results as one array
I will then be able to access the data and just foreach loop through it.
I'm just not sure the best way to combine multiple result sets into an array?
OR you could try and combine them into one query ...
$eventResult = mysql_query(
'SELECT userevent.event, userevent.eventId, userevent.friendId
FROM userevent
WHERE userevent.userId = 1 OR userevent.friendId = 1
ORDER BY userevent.id DESC
LIMIT 20'
);
while ($eventRow = mysql_fetch_row($eventResult)){
if($eventRow[0] == 1){
$result = mysql_fetch_array(mysql_query("
SELECT forumRooms.id, forumRooms.title
FROM forumPosts
INNER JOIN forumRooms ON forumPosts.`forumTopic` = forumRooms.`id`
WHERE forumPosts.id = '$eventRow[1]'"));
}
elseif($eventRow[0] == 2){
$result = mysql_fetch_array(mysql_query("
SELECT game.id, game.uriTitle, game.title
FROM usergamecomment
INNER JOIN game ON usergamecomment.`gameId` = game.id
WHERE usergamecomment.id = $eventRow[1]"));
}
elseif($eventRow[0] == 4){
$result = mysql_fetch_array(mysql_query("
SELECT usercomment.comment, UNIX_TIMESTAMP(usercomment.TIME), `user`.id, `user`.username, `user`.activate
FROM usercomment
INNER JOIN `user` ON usercomment.`userId` = `user`.id
WHERE usercomment.id = $eventRow[1]
AND `user`.activate = 1"));
}
elseif($eventRow[0] == 5){
$result = mysql_fetch_array(mysql_query("
SELECT game.id, game.title, game.uriTitle
FROM game
WHERE game.id = $eventRow[1]"));
}
// Combined Results as array
}
I'm in the process of converting all of these to PDO, that's the next step after working out the best way to minimise this.
Challenge accepted. ;)
Since you are actually only interested in the results inside the while loop, you could try this single query. Due to the LEFT JOINS it might not be faster, pretty much depends on your database. The final $result contains all elements with their respective fields.
$result = array();
$q = 'SELECT userevent.event AS userevent_event,
userevent.eventId AS userevent_eventId,
userevent.friendId AS userevent_friendId,
forumRooms.id AS forumRooms_id,
forumRooms.title AS forumRooms_title,
game.id AS game_id,
game.uriTitle AS game_uriTitle,
game.title AS game_title,
usercomment.comment AS usercomment_comment,
UNIX_TIMESTAMP(usercomment.TIME) AS usercomment_time,
user.id AS user_id,
user.username AS user_username,
user.activate AS user_activate,
g2.id AS game2_id,
g2.uriTitle AS game2_uriTitle,
g2.title AS game2_title
FROM userevent
LEFT JOIN forumPosts ON forumPosts.id = userevent.eventId
LEFT JOIN forumRooms ON forumPosts.forumTopic = forumRooms.id
LEFT JOIN usergamecomment ON usergamecomment.id = userevent.eventId
LEFT JOIN game ON usergamecomment.gameId = game.id
LEFT JOIN usercomment ON usercomment.id = userevent.eventId
LEFT JOIN user ON usercomment.userId = user.id
LEFT JOIN game g2 ON userevent.eventId = g2.id
WHERE (userevent.userId = 1 OR userevent.friendId = 1)
AND userevent.eventId >= (SELECT userevent.eventId
WHERE userevent.userId = 1 OR userevent.friendId = 1
ORDER BY userevent.id DESC LIMIT 1,20);';
$r = mysql_query($q);
while ( $o = mysql_fetch_row($r) ) {
switch($o['userevent_event']) {
case 1:
$result[] = array(
'id' => $o['forumsRooms_id'],
'title' => $o['forumsRooms_title'],
);
break;
case 2:
$result[] = array(
'id' => $o['game_id'],
'uriTitle' => $o['game_uriTitle'],
'title' => $o['game_title'],
);
break;
case 4:
$result[] = array(
'comment' => $o['usercomment_comment'],
'time' => $o['usercomment_time'],
'id' => $o['user_id'],
'username' => $o['user_username'],
'activate' => $o['user_activate'],
);
break;
case 5:
$result[] = array(
'id' => $o['game2_id'],
'uriTitle' => $o['game2_uriTitle'],
'title' => $o['game2_title'],
);
break;
}
}
Note: Eventually, the query has to be edited slightly, I just wrote that out of my head w/o testing. Optimization can surely be done if I'd knew more about the db structure.
Also, this is merely a proof of concept that it can indeed be done with a single query. ;)

php mysql advanced selecting

table 1 = events -> holds a list of events
table 2 = response -> holds a list of users responses has a foreign key of eid which corresponds with events table
I need to join the tables together so it can return an array similar to this on php.
array(
0 => array(
'title' =>'', //title of events table
'contents' =>'this is a demo', //contents of events table
'users' => array( //users comes from response table
0 = array(
'firstname'=>'John',
),
1 = array(
'firstname'=>'James',
)
)
)
);
can this be done? using mysql only? coz i know you can do it on php.
You can gather all the necessary data in MySQL with a single JOIN query.
However, PHP will not return an array like your example by default. You would have to loop over the query result set and create such an array yourself.
I'm pretty sure the answer is no, since mysql always returns a "flat" resultset. So, you can get all the results you're looking for using:
SELECT e.title, e.contents, r.firstname
FROM events e LEFT JOIN response r ON e.id = r.eid
ORDER BY e.id, r.id
And then massaging it into the array with php, but I imagine this is what you're doing already.
EDIT:
By the way, if you want 1 row for each event, you could use GROUP_CONCAT:
SELECT e.title, e.contents, GROUP_CONCAT(DISTINCT r.firstname ORDER BY r.firstname SEPARATOR ',') as users
FROM events e LEFT JOIN response r ON e.id = r.eid
GROUP BY e.id
Just as Jason McCreary said. For you convenience, here is the query you need (though the field names might not be matching your db structure, as you did not provide this information)
SELECT
*
FROM
events
LEFT JOIN
responses ON (events.id = responses.eid)
The SQL is:
SELECT events.id, events.title, events.contents,
response.id AS rid, response.firstname
FROM events LEFT JOIN response
ON events.id = response.eid
I thought I would show you how to massage the results in to the array as you wished:
$query = "SELECT events.id, events.title, events.contents, response.id AS rid, response.firstname
FROM events LEFT JOIN response ON events.id = response.eid";
$result = mysql_query($query);
$events = array();
while ($record = mysql_fetch_assoc($result)) {
if (!array_key_exists($record['id'], $events)) {
$events[$record['id']] = array('title' => $record['title'], 'contents' => $record['contents'], 'users' => array());
}
if ($record['rid'] !== NULL) {
$events[$record['id']]['users'][$record['rid']] = array('firstname' => $record['firstname']);
}
}
mysql_free_result($result);

Categories