10

I am trying to get the latest 1 or 2 comments related to each post I download, a bit like instagram does as they show the latest 3 comments for each post, So far I am getting the posts & the likes counts.

Now all I need to do is figure out how to get the latest comments, not too sure how to approach it and that is why I hoping someone with a lot more expertise can help me!

This is my current query:

(SELECT
        P.uuid,
        P.caption,
        P.imageHeight,
        P.path,
        P.date,
        U.id,
        U.fullname,
        U.coverImage,
        U.bio,
        U.username,
        U.profileImage,
        coalesce(Activity.LikeCNT,0),
        Activity.CurrentUserLiked
        FROM USERS AS U
        INNER JOIN Posts AS P 
        ON P.id = U.id
        LEFT JOIN (SELECT COUNT(DISTINCT Activity.uuidPost) LikeCNT, Activity.uuidPost, Activity.id, sum(CASE WHEN Activity.id = $id then 1 else 0 end) as CurrentUserLiked
        FROM Activity Activity
        WHERE type = 'like' 
        GROUP BY Activity.uuidPost) Activity
        ON Activity.uuidPost = P.uuid
        AND Activity.id = U.id
        WHERE U.id = $id)
UNION
        (SELECT 
        P.uuid,
        P.caption,
        P.imageHeight,
        P.path,
        P.date,
        U.id,
        U.fullname,
        U.coverImage,
        U.bio,
        U.username,
        U.profileImage,
        coalesce(Activity.LikeCNT,0),
        Activity.CurrentUserLiked
        FROM Activity AS A
        INNER JOIN USERS AS U 
        ON A.IdOtherUser=U.id
        INNER JOIN Posts AS P 
        ON P.id = U.id
        LEFT JOIN (SELECT COUNT(DISTINCT Activity.uuidPost) LikeCNT, Activity.uuidPost, Activity.id, sum(CASE WHEN Activity.id = $id then 1 else 0 end) as CurrentUserLiked
    FROM Activity Activity
    WHERE type = 'like' 
    GROUP BY Activity.uuidPost) Activity
    ON Activity.uuidPost = P.uuid
    AND Activity.id = U.id
    WHERE A.id = $id)
    ORDER BY date DESC
    LIMIT 0, 5

Basically the comments are store in the same table as the likes.

So the table is Activity, then I have a column comment which stores the comment text, and then the "type" is equal to "comment".

Possibly not very well explained but I am willing to try and give as much detail as possible!

If anyone can help it's very much appreciated!!

UPDATE

On this query given by https://stackoverflow.com/users/1016435/xqbert I am currently getting this error:

Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8_unicode_ci,IMPLICIT) for operation '='

SELECT Posts.id,
    Posts.uuid,
    Posts.caption,
    Posts.path,
    Posts.date,
    USERS.id,
    USERS.username,
    USERS.fullname,
    USERS.profileImage,
    coalesce(A.LikeCNT,0),
    com.comment
FROM Posts 
INNER JOIN USERS 
  ON Posts.id = 145 
 AND USERS.id = 145
LEFT JOIN (SELECT COUNT(A.uuidPost) LikeCNT, A.UUIDPost
    FROM Activity A
    WHERE type =  'like' 
    GROUP BY A.UUIDPOST) A
 on A.UUIDPost=Posts.uuid
LEFT JOIN (SELECT comment, UUIDPOST, @row_num := IF(@prev_value=UUIDPOST,@row_num+1,1) as row_number,@prev_value := UUIDPOST
           FROM Activity 
           CROSS JOIN (SELECT @row_num := 1) x
           CROSS JOIN (SELECT @prev_value := '') y
           WHERE type = 'comment'
           ORDER BY UUIDPOST, date DESC) Com
  ON Com.UUIIDPOSt = Posts.UUID
 AND row_number <= 2
ORDER BY date DESC
LIMIT 0, 5

Latest Edit

Table structures:

Posts

    ----------------------------------------------------------
    | id         | int(11)      |                 | not null |
    | uuid       | varchar(100) | utf8_unicode_ci | not null |
    | imageLink  | varchar(500) | utf8_unicode_ci | not null |
    | date       | timestamp    |                 | not null |
    ----------------------------------------------------------

USERS

    -------------------------------------------------------------
    | id            | int(11)      |                 | not null |
    | username      | varchar(100) | utf8_unicode_ci | not null |
    | profileImage  | varchar(500) | utf8_unicode_ci | not null |
    | date          | timestamp    |                 | not null |
    -------------------------------------------------------------

Activity

    ----------------------------------------------------------
    | id           | int(11)      |                 | not null |
    | uuid         | varchar(100) | utf8_unicode_ci | not null |
    | uuidPost     | varchar(100) | utf8_unicode_ci | not null |
    | type         | varchar(50)  | utf8_unicode_ci | not null |
    | commentText  | varchar(500) | utf8_unicode_ci | not null |
    | date         | timestamp    |                 | not null |
    ----------------------------------------------------------

Those are some examples, in the "Activity" table in this case "type" will always be equal to "comment".

Sum up of everything and desire result:

When I query the users posts, I would like to be able to go into the "Activity" table and get the latest 2 comments for every posts he has. Maybe there will be no comments so obviously it would return 0, maybe there could be 100 comments for that post. But I only want to get the latest/most recent 2 comments.

An example could be looking at how Instagram does it. For every post the display the most recent comments 1, 2 or 3....

Hope this helps!

Fiddle link

Community
  • 1
  • 1
  • 2
    See http://meta.stackoverflow.com/questions/333952/why-should-i-provide-an-mcve-for-what-seems-to-me-to-be-a-very-simple-sql-query – Strawberry Oct 26 '16 at 18:18
  • 2
    Would be very well explained if you include the table definition in the question. – Alfabravo Oct 26 '16 at 18:29
  • What's the name of the date column in activity? OR how do you determine what the most recent 1-2 comments are?) is there a unique sequence for each activity so the higest IDs if so what's that column name? or is there a date which denotes the most recent activity if so what's its name? – xQbert Oct 27 '16 at 17:50
  • @xQbert the column is date I order them like this ORDER BY date DESC I saw your answer and thank you very much for it, I shall try it out now! –  Oct 27 '16 at 18:55
  • 1
    Please include sample table data and expected query output. – shmosel Oct 28 '16 at 20:27
  • 1
    Please provide `SHOW CREATE TABLE` for each relevant table. – Rick James Oct 29 '16 at 01:30
  • Can you successfully get the "most recent 1-2" rows out of the table with `date`? After that, we can discuss how to `JOIN` to the other tables. – Rick James Oct 29 '16 at 01:31
  • [SHOW CREATE TABLE Syntax](http://dev.mysql.com/doc/refman/5.7/en/show-create-table.html) – Paul Spiegel Oct 29 '16 at 10:15
  • There are good reasons, why we ask for `SHOW CREATE TABLE` and not for `DESCRIBE`. For example - Is `id` allways AUTO_INCREMENT? What keys are defined? `SHOW CREATE TABLE` would give us all that information. Please read the answer that is linked in the first comment. – Paul Spiegel Oct 29 '16 at 11:17

6 Answers6

4

This error message

Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8_unicode_ci,IMPLICIT) for operation '='

is typically due to the definition of your columns and tables. It usually means that on either side of an equal sign there are different collations. What you need to do is choose one and include that decision in your query.

The collation issue here was in the CROSS JOIN of @prev_value which needed an explicit collation to be used.

I have also slightly changed the "row_number" logic to a single cross join and moved the if logic to the extremes of the select list.

Some sample data is displayed below. Sample data is needed to test queries with. Anyone attempting to answer your question with working examples will need data. The reason I am including it here is twofold.

  1. so that you will understand any result I present
  2. so that in future when you ask another SQL related question you understand the importance of supplying data. It is not only more convenient for us that you do this. If the asker provides the sample data then the asker will already understand it - it won't be an invention of some stranger who has devoted some of their time to help out.

Sample Data

Please note some columns are missing from the tables, only the columns specified in the table details have been included.

This sample data has 5 comments against a single post (no likes are recorded)

CREATE TABLE Posts 
(
`id` int, 
`uuid` varchar(7) collate utf8_unicode_ci,
`imageLink` varchar(9) collate utf8_unicode_ci, 
`date` datetime
 );
    
INSERT INTO Posts(`id`, `uuid`, `imageLink`, `date`)
VALUES
(145, 'abcdefg', 'blah blah', '2016-10-10 00:00:00') ;

CREATE TABLE   USERS
(
`id` int, 
`username` varchar(15) collate utf8_unicode_ci,
 `profileImage` varchar(12) collate utf8_unicode_ci,
 `date` datetime
) ;
        
INSERT INTO     USERS(`id`, `username`, `profileImage`, `date`)
VALUES
(145, 'used_by_already', 'blah de blah', '2014-01-03 00:00:00') ;
    
    
CREATE TABLE Activity
(
`id` int, 
`uuid` varchar(4) collate utf8_unicode_ci, 
`uuidPost` varchar(7) collate utf8_unicode_ci,
 `type` varchar(40) collate utf8_unicode_ci, 
`commentText` varchar(11) collate utf8_unicode_ci, `date` datetime
) ;
        
INSERT INTO Activity (`id`, `uuid`, `uuidPost`, `type`, `commentText`, `date`)
 VALUES
(345, 'a100', 'abcdefg', 'comment', 'lah lha ha', '2016-07-05 00:00:00'),
(456, 'a101', 'abcdefg', 'comment', 'lah lah lah', '2016-07-06 00:00:00'),
(567, 'a102', 'abcdefg', 'comment', 'lha lha ha', '2016-07-07 00:00:00'),
(678, 'a103', 'abcdefg', 'comment', 'ha lah lah', '2016-07-08 00:00:00'),
(789, 'a104', 'abcdefg', 'comment', 'hla lah lah', '2016-07-09 00:00:00') ;

[SQL Standard behaviour: 2 rows per Post query]

This was my initial query, with some corrections. I changed the column order of the select list so that you will see some comment related data easily when I present the results. Please study those results they are provided so you may understand what the query will do. Columns preceded by # do not exist in the sample data I am working with for reasons I have already noted.

SELECT
      Posts.id
    , Posts.uuid
    , rcom.uuidPost
    , rcom.commentText
    , rcom.`date` commentDate 
    #, Posts.caption
    #, Posts.path
    , Posts.`date`
    , USERS.id
    , USERS.username
    #, USERS.fullname
    , USERS.profileImage
    , COALESCE(A.LikeCNT, 0) num_likes
FROM Posts
INNER JOIN USERS ON Posts.id = 145
            AND USERS.id = 145
LEFT JOIN (
          SELECT
                COUNT(A.uuidPost) LikeCNT
              , A.UUIDPost
          FROM Activity A
          WHERE type = 'like'
          GROUP BY
                A.UUIDPOST
          ) A ON A.UUIDPost = Posts.uuid 
LEFT JOIN (
      SELECT
            @row_num := IF(@prev_value=UUIDPOST,@row_num+1,1) as row_number
          , commentText
          , uuidPost
          , `date`
          , @prev_value := UUIDPOST
      FROM Activity
      CROSS JOIN ( SELECT @row_num := 1, @prev_value := '' collate utf8_unicode_ci  ) xy
      WHERE type = 'comment'
      ORDER BY
            uuidPost
          , `date` DESC
      ) rcom ON rcom.uuidPost  = Posts.UUID
            AND rcom.row_number <= 2
ORDER BY
      posts.`date` DESC
      ;
      
      

See a working demonstration of this query at SQLFiddle

Results:

|  id |    uuid | uuidPost | commentText |                   date |                      date |  id |        username | profileImage | num_likes |
|-----|---------|----------|-------------|------------------------|---------------------------|-----|-----------------|--------------|-----------|
| 145 | abcdefg |  abcdefg | hla lah lah | July, 09 2016 00:00:00 | October, 10 2016 00:00:00 | 145 | used_by_already | blah de blah |         0 |
| 145 | abcdefg |  abcdefg |  ha lah lah | July, 08 2016 00:00:00 | October, 10 2016 00:00:00 | 145 | used_by_already | blah de blah |         0 |

There are 2 ROWS - as expected. One row for the most recent comment, and another rows for the next most recent comment. This is normal behaviour for SQL and until a comment was added under this answer readers of the question would assume this normal behaviour would be acceptable.

The question lacks a clearly articulated "expected result".


[Option 1: One row per Post query, with UP TO 2 comments, added columns]

In a comment below it was revealed that you did not want 2 rows per post and this would be an easy fix. Well it kind of is easy BUT there are options and the options are dictated by the user in the form of requirements. IF the question had an "expected result" then we would know which option to choose. Nonetheless here is one option

SELECT
      Posts.id
    , Posts.uuid
    , max(case when rcom.row_number = 1 then rcom.commentText end) Comment_one
    , max(case when rcom.row_number = 2 then rcom.commentText end) Comment_two
    #, Posts.caption
    #, Posts.path
    , Posts.`date`
    , USERS.id
    , USERS.username
    #, USERS.fullname
    , USERS.profileImage
    , COALESCE(A.LikeCNT, 0) num_likes
FROM Posts
INNER JOIN USERS ON Posts.id = 145
            AND USERS.id = 145
LEFT JOIN (
          SELECT
                COUNT(A.uuidPost) LikeCNT
              , A.UUIDPost
          FROM Activity A
          WHERE type = 'like'
          GROUP BY
                A.UUIDPOST
          ) A ON A.UUIDPost = Posts.uuid 
LEFT JOIN (
      SELECT
            @row_num := IF(@prev_value=UUIDPOST,@row_num+1,1) as row_number
          , commentText
          , uuidPost
          , `date`
          , @prev_value := UUIDPOST
      FROM Activity
      CROSS JOIN ( SELECT @row_num := 1, @prev_value := '' collate utf8_unicode_ci  ) xy
      WHERE type = 'comment'
      ORDER BY
            uuidPost
          , `date` DESC
      ) rcom ON rcom.uuidPost  = Posts.UUID
            AND rcom.row_number <= 2
GROUP BY
      Posts.id
    , Posts.uuid
    #, Posts.caption
    #, Posts.path
    , Posts.`date`
    , USERS.id
    , USERS.username
    #, USERS.fullname
    , USERS.profileImage
    , COALESCE(A.LikeCNT, 0)
ORDER BY
      posts.`date` DESC
      ;

See the second query working at SQLFiddle

Results of query 2:

|  id |    uuid | Comment_one | Comment_two |                      date |  id |        username | profileImage | num_likes |
|-----|---------|-------------|-------------|---------------------------|-----|-----------------|--------------|-----------|
| 145 | abcdefg | hla lah lah |  ha lah lah | October, 10 2016 00:00:00 | 145 | used_by_already | blah de blah |         0 |

** Option 2, concatenate the most recent comments into a single comma separated list **

SELECT
      Posts.id
    , Posts.uuid
    , group_concat(rcom.commentText) Comments_two_concatenated
    #, Posts.caption
    #, Posts.path
    , Posts.`date`
    , USERS.id
    , USERS.username
    #, USERS.fullname
    , USERS.profileImage
    , COALESCE(A.LikeCNT, 0) num_likes
FROM Posts
INNER JOIN USERS ON Posts.id = 145
            AND USERS.id = 145
LEFT JOIN (
          SELECT
                COUNT(A.uuidPost) LikeCNT
              , A.UUIDPost
          FROM Activity A
          WHERE type = 'like'
          GROUP BY
                A.UUIDPOST
          ) A ON A.UUIDPost = Posts.uuid 
LEFT JOIN (
      SELECT
            @row_num := IF(@prev_value=UUIDPOST,@row_num+1,1) as row_number
          , commentText
          , uuidPost
          , `date`
          , @prev_value := UUIDPOST
      FROM Activity
      CROSS JOIN ( SELECT @row_num := 1, @prev_value := '' collate utf8_unicode_ci  ) xy
      WHERE type = 'comment'
      ORDER BY
            uuidPost
          , `date` DESC
      ) rcom ON rcom.uuidPost  = Posts.UUID
            AND rcom.row_number <= 2
GROUP BY
      Posts.id
    , Posts.uuid
    #, Posts.caption
    #, Posts.path
    , Posts.`date`
    , USERS.id
    , USERS.username
    #, USERS.fullname
    , USERS.profileImage
    , COALESCE(A.LikeCNT, 0)
ORDER BY
      posts.`date` DESC
      

See this third query working at SQLFiddle

Results of query 3:

|  id |    uuid | Comments_two_concatenated |                      date |  id |        username | profileImage | num_likes |
|-----|---------|---------------------------|---------------------------|-----|-----------------|--------------|-----------|
| 145 | abcdefg |    hla lah lah,ha lah lah | October, 10 2016 00:00:00 | 145 | used_by_already | blah de blah |         0 |

** Summary **

I have presented 3 queries, each one shows only the 2 most recent comments, but each query does that in a different way. The first query (default behaviour) will display 2 rows for each post. Option 2 adds a column but removes the second row. Option 3 concatenates the 2 most recent comments.

Please note that:

  • The question lacks table definitions covering all columns
  • The question lacks any sample data, which makes it harder for you to understand any results presented here, but also harder for us to prepare solutions
  • The question also lacks a definitive "expected result" (the wanted output) and this has led to further complexity in answering

I do hope the additional provided information will be of some use, and that by now you also know that it is normal for SQL to present data as multiple rows. If you do not want that normal behaviour please be specific about what you do really want in your question.


Postscript. To include yet another subquery for "follows" you may use a similar subquery to the one you already have. It may be added before or after that subquery. You may also see it in use at sqlfiddle here

LEFT JOIN (
          SELECT
                COUNT(*) FollowCNT
              , IdOtherUser
          FROM Activity
          WHERE type = 'Follow'
          GROUP BY
                IdOtherUser
          ) F ON USERS.id = F.IdOtherUser

Whilst adding another subquery may resolve your desire for more information, the overall query may get slower in proportion to the growth of your data. Once you have settled on the functionality you really need it may be worthwhile considering what indexes you need on those tables. (I believe you would be advised to ask for that advice separately, and if you do make sure you include 1. the full DDL of your tables and 2. an explain plan of the query.)

Community
  • 1
  • 1
Paul Maxwell
  • 33,002
  • 3
  • 32
  • 51
  • Hey thanks for your answer! I am currently getting this after trying your code: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'collation utf8_general_ci LEFT JOIN ( SELECT @row_num := IF(@p' at line 24 –  Oct 29 '16 at 10:39
  • I have updated my question by the way... Maybe it explains better my request! –  Oct 29 '16 at 11:00
  • Sorry I should have used COLLATE not collation. I updated the query. – Paul Maxwell Oct 29 '16 at 11:29
  • This error again: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8_unicode_ci,IMPLICIT) for operation '=' –  Oct 29 '16 at 11:32
  • yes, I am looking at it now, the problem was inside the `cross join` – Paul Maxwell Oct 29 '16 at 12:10
  • Wow nice answer....I am just getting problems with the date now. It says Unknown column 'rcom.date' in 'field list', I am also getting that on the posts.date...Not sure why I shall add an image of how my date it set up –  Oct 29 '16 at 13:46
  • anyway ++(1) for the effort, I hope we can get it working! –  Oct 29 '16 at 13:49
  • I just tried it without the ordering by date, it worked. It is showing the 2 latest comments, but its getting 1 row for each comment. So basically it get the post twice for the 2 comments. And as I shall be appending this to a tableView I don't want the posts showing twice but only once...I'm sure this is an easy problem to fix –  Oct 29 '16 at 14:34
  • @Jack - Marcin Bator already mentioned this problem in his answer. – Paul Spiegel Oct 29 '16 at 16:10
  • Thanks for updating again, I'm still getting this error with the date : #1054 - Unknown column 'posts.date' in 'order clause' –  Oct 30 '16 at 00:30
  • posts.date exists in the select clause, in exists also in WORKING queries that I have specified above. Try this: ADD an alias to posts.date in the select clause then use that alias in the order by clause. pleas note that all 3 queries above work, you can visit the sqlfiddle urls of each and press a button to run the query. Click this URL http://sqlfiddle.com/#!9/358473/14 and look for SOME_ALIAS_HERE in 2 places – Paul Maxwell Oct 30 '16 at 01:56
  • At some stage you should also read this: http://stackoverflow.com/help/mcve or http://SSCCE.org/ both will help you prepare most precise questions and that will help you get solutions faster – Paul Maxwell Oct 30 '16 at 01:59
  • Hi @Used_By_Already I am using this one [Option 1: One row per Post query, with UP TO 2 comments, added columns]. I changed the posts.date to ORDER BY date DESC, It seems all good so far. I would though like to be able to understand the query fully like what rcom is etc... If you have any links that would be great, I think you have done enough here and I don't expect you to explain it all too me! I am now going to add some other parts of my code, i.e getting the users posts i follow. So I shall add that code and see how everything goes! After so I shall reward you the bounty! –  Oct 30 '16 at 07:43
  • Actually before I waist my time and probably mess everything up. May I ask you where to start from to insert the following part of my code.* In my query I currently get the current user's posts, like count on post, and check if I have liked that post & then I UNION it basically with the same part of code but only according to the poeople I follow....I got help with this query of here on Stack, not sure how good it is but I do know that the count likes isn't working correctly but that's kind of obvious just by looking at the code. Anyway I was wondering if you could tell me how to add that ch –  Oct 30 '16 at 07:50
  • Anyway I was wondering if you could tell me how to add that check to get the people I am following? I shall add my current full query into my question –  Oct 30 '16 at 07:50
  • Before you add any more requirements and/or code; it would be far better to add `sample data` and `expected result`. Take note of how little data I used. A sample doe not need to be large but it should represent the problem(s) you want solved. The question still does not specify what it is you really want. This is coming to use by progressively via these small comments and it is simply not a good way to get assistance. – Paul Maxwell Oct 30 '16 at 08:38
  • @Used_By_Already I agree, and I can't thank you enough for showing/teaching me how to post good mysql questions. Regarding what I really want, I thought asking to get: 2 of the most recent comments for each post I query explained that. Sorry if I didn't but you obviously managed to understand what I needed as you seem to have given me the correct query, that gets the results I need. I shall try and use fiddle more now I have your examples to start from. Regarding completing my question, if you're able to help me or prefer me to post a new question.... –  Oct 30 '16 at 10:34
  • I basically want to achieve the same result you have got me with your query, but to include the posts that belong to the people I follow. –  Oct 30 '16 at 10:35
  • Would you like me to try and create another table in your fiddle to show you how I store my followers? –  Oct 30 '16 at 10:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126996/discussion-between-jack-and-used-by-already). –  Oct 30 '16 at 10:55
  • If you can add necessary data to a fiddle and be clear what you want as a result i can include the wanted sql. – Paul Maxwell Oct 30 '16 at 22:16
  • I have added how I store followers in Activity table here: http://sqlfiddle.com/#!9/5b04d/1 Final result = get all the posts that are equal to current user with the posts which belong to the users he is following and then just the comments and likes as above. –  Oct 30 '16 at 22:18
  • I have added the way I store followers, and inserted another user –  Oct 30 '16 at 22:19
  • If you look at the DDL editor you'll see what I have added –  Oct 30 '16 at 22:19
  • Provide the url please i t will be new – Paul Maxwell Oct 30 '16 at 22:28
  • I added it to the bottom of my question –  Oct 30 '16 at 22:35
  • Hi @Used_By_Already That is for counting how many users are following current user, I need to get the posts that belong to those users like the query I added does. –  Oct 31 '16 at 08:28
  • LEFT JOIN ( SELECT COUNT(*) FollowCNT , UUIDPost FROM Activity WHERE type = 'follow' GROUP BY UUIDPOST ) F ON F.UUIDPost = Posts.uuid – Paul Maxwell Oct 31 '16 at 09:00
  • try that instead I was really just trying to assist with a syntax issue, I wasn't expecting a long life warranty – Paul Maxwell Oct 31 '16 at 09:02
  • I don't want to count though –  Oct 31 '16 at 09:03
  • I want to get the other user's posts –  Oct 31 '16 at 09:03
  • Like I get the posts for my self –  Oct 31 '16 at 09:03
  • An another example could still be Instagram. On the HOMEPAGE you get your photos and the photos of people you are following? that is what I am trying to achieve...The code I added above does that, but not sure how to do it with the new code you gave me! –  Oct 31 '16 at 09:09
  • 1
    I don't use Instagram so it is not an helpful explanation. Truly, I believe we have reached the point where I say enough is enough. Please ask another question. Make sure you include sufficient information in that question for an answer; provide sample data + **provide the expected result**. – Paul Maxwell Oct 31 '16 at 09:10
  • Fair enough, I agree this is taking too long! but I cannot use that query unless I do the check to see who current user is following and get there posts... I shall ask another question if you prefer. Do you have the time to answer it now? –  Oct 31 '16 at 09:13
  • Facebook could be an example I'm sure you understand how homepages work... You get your posts and your friends posts on the same page! –  Oct 31 '16 at 09:14
  • Posted another question. http://stackoverflow.com/questions/40339438/mysql-query-include-posts-of-people-i-am-following-in-my-current-query I shall accept this once I have everything working...Thanks for all your help anyway! Appreciate it alot. –  Oct 31 '16 at 09:26
  • Good luck with your project, and the next question (btw: I don't use facebook either) – Paul Maxwell Oct 31 '16 at 10:02
  • Hey mate, hope everythings good! I currently have a question about mysql with a bounty for 50rep...Just wondering if youre interested http://stackoverflow.com/questions/40527140/mysql-and-swift-upload-image-would-it-be-better-to-use-afnetworking –  Nov 19 '16 at 20:29
2

I am a little bit lost in your query, but if you want to download data for multiple posts at once, it's not a good idea to include comment data in the first query since you would include all the data about post and posting user multiple times. You should run another query that would connect posts with comments. Something like:

SELECT 
A.UUIDPost, 
C.username,
C.profileImage, 
B.Comment,
B.[DateField]
FROM Posts A JOIN 
Activities B ON A.uuid = B.UUIDPost JOIN
Users C ON B.[UserId] = C.id 

and use that data to display your comments with commenting user id, name, image etc.

To get only 3 comments per post, you can look into this post:

Select top 3 values from each group in a table with SQL

if you are sure that there are going to be no duplicate rows in the comment table or this post:

How to select top 3 values from each group in a table with SQL which have duplicates

if you're not sure about that (although due to DateField in the table, it should not be possible).

Community
  • 1
  • 1
Marcin Bator
  • 341
  • 1
  • 8
  • 23
  • Hi thanks a lot for that, I shall look into it. I am using this server for my IOS app, so you reccomend calling seperate queries even though I need all that data at the same time? –  Oct 26 '16 at 18:11
  • If you include comment data in the main query, in result you would get about 15 fields, from which 10 would be the same for every post and only comment data and commenting user's data would change. As far as I'm concerned, It's not a particularly good query design and it could possibly be a performance issue in a long run. – Marcin Bator Oct 26 '16 at 18:20
  • Ok thanks a lot, I'll shall let you know how I get on with this –  Oct 26 '16 at 19:07
  • 1
    @Jack "trying to get best answer really" - but against WHAT? Your question should define exactly what you want THEN we submit ideas to solve it. You are going about this backwards, through an inexact question and reveal new requirements as comment while you inspect answers. – Paul Maxwell Oct 30 '16 at 04:32
  • @maraca I agree, I do lack the experience and that is why I am trying to learn. I am sure you found yourself in the same position too once. No point downvoting my question, all because I decided to accept User_By_Already's answer. As after I told you that I accepted his answer I suddenly got a downvote. Even if your answer did work I wouldn't accept it, as the OP has put alot more effort into this than you. I am not just looking for a working solution, but what seem's to be the best...And I can assure you his answer looks a lot more sufficient. Even with my 'lack of experience' I can see that. –  Oct 30 '16 at 19:06
  • @Jack some final words from me: I never said you should accept my answer (although it would be nice if you would upvote any answer contributing something to your problem because of the reasons already mentioned). And sentences like "he already sorted it out for me" show that you don't try to find the best answer, also effort is not a measure (Used_By_Already's answer is still good +1, don't get me wrong), compare the times on huge datasets... that would be an indicator or readability. – maraca Oct 30 '16 at 19:14
1

UNTESTED: I would recommend putting together an SQL fiddle with some sample data and your existing table structure showing the problem; that way we could play around with the responses and ensure functionality with your schema.

So we use a variables to simulate a window function (Such as row_number)

in this case @Row_num and @prev_Value. @Row_number keeps track of the current row for each post (since a single post could have lots of comments) then when the a new post ID (UUIDPOST?) is encountered the row_num variable is reset to 1. When the current records UUIDPOST matches the variable @prev_Value, we simply increment the row by 1.

This technique allows us to assign a row number based on the date or activity ID order descending. As each cross join only results in 1 record we don't cause duplicate records to appear. However, since we then limit by row_number < = 2 we only get the two most recent comments in our newly added left join.

This assumes posts relation to users is a Many to one, meaning a post can only have 1 user.

Something like This: though I'm not sure about the final left join I need to better understand the structure of the activity table thus a comment against the original question.

SELECT Posts.id,
        Posts.uuid,
        Posts.caption,
        Posts.path,
        Posts.date,
        USERS.id,
        USERS.username,
        USERS.fullname,
        USERS.profileImage,
        coalesce(A.LikeCNT,0)
        com.comment
    FROM Posts 
    INNER JOIN USERS 
      ON Posts.id = 145 
     AND USERS.id = 145
    LEFT JOIN (SELECT COUNT(A.uuidPost) LikeCNT, A.UUIDPost
        FROM Activity A
        WHERE type =  'like' 
        GROUP BY A.UUIDPOST) A
     on A.UUIDPost=Posts.uuid


  --This join simulates row_Number() over (partition by PostID, order by activityID desc)  (Nice article [here](http://preilly.me/2011/11/11/mysql-row_number/) several other examples exist on SO already.
   --Meaning.... Generate a row number for each activity from 1-X restarting at 1 for each new post but start numbering at the newest activityID)

    LEFT JOIN (SELECT comment, UUIDPOST, @row_num := IF(@prev_value=UUIDPOST,@row_num+1,1) as row_number,@prev_value := UUIDPOST

               FROM ACTIVITY 
               CROSS JOIN (SELECT @row_num := 1) x
               CROSS JOIN (SELECT @prev_value := '') y
               WHERE type = 'comment'
               ORDER BY UUIDPOST, --Some date or ID desc) Com
       on Com.UUIIDPOSt = Posts.UUID
       and row_number < = 2


  -- Now since we have a row_number restarting at 1 for each new post, simply return only the 1st two rows.

    ORDER BY date DESC
    LIMIT 0, 5

we had to put the and row_number < = 2 on the join itself. If it was put in the where clause you would lose those posts without any comments which I think you still want.

Additionally we should probably look at the "comment" field to make sure it's not blank or null, but lets make sure this works first.

xQbert
  • 34,733
  • 2
  • 41
  • 62
  • Wow have to say thats a great answer, tested it just and change "--Some date or ID desc" to ORDER BY date DESC (not sure if that's correct). But now I get this error: "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CROSS JOIN (SELECT @prev_value := 1) y WHERE type = 'comment'" –  Oct 27 '16 at 19:04
  • Did you remove the comment blocks --? and it appears I had an extra comma. The real reason why I want a SQL fiddle is so that I can debug what I've written to eliminate this type of back and forth :P oh and no comma after the corss join () x, – xQbert Oct 27 '16 at 19:09
  • Yep removed the comment block I shall add above what I have so far. I'll try to make an SQL fiddle...Never used it so not sure how do to so but I'll try –  Oct 27 '16 at 19:17
  • you can put the create table statements for the 3 tables (and fields) needed along with some insert statements. From there we can tinker with the SQL till it's what you're after (others who have Ideas can play around as well to ensure it results in a dataset you're after! – xQbert Oct 27 '16 at 19:18
  • Hmm, trying to do it not going well...Could take me a while do you mind? –  Oct 27 '16 at 19:29
  • After ORDER BY UUIDPOST, I removed this ORDER BY date DESC for now. and I am getting this error Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8_unicode_ci,IMPLICIT) for operation '=' –  Oct 27 '16 at 19:33
  • I shall continue trying to make the sqlfiddle –  Oct 27 '16 at 19:34
  • I'd just debug the inner select `SELECT comment, UUIDPOST, @row_num := IF(@prev_value=UUIDPOST,@row_num+1,1) as row_number,@prev_value := UUIDPOST FROM ACTIVITY CROSS JOIN (SELECT @row_num := 1) x CROSS JOIN (SELECT @prev_value := '') y WHERE type = 'comment' ORDER BY UUIDPOST, --Some date or ID desc` until we have this working, nothing else matters. If your not in a rush we can look at it later. to generate the tables should be available to you via a `show create table tableName`; – xQbert Oct 27 '16 at 19:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126862/discussion-between-jack-and-xqbert). –  Oct 27 '16 at 19:48
  • Got what you gave working apart from I get that illegal mix of collations error, I posted the code in my question that you gave me...Still trying fiddle not going well! –  Oct 27 '16 at 19:56
  • Adding bounty for +100 in 21 hours anyway! you deserve it!! –  Oct 27 '16 at 20:05
  • Hey you available? –  Oct 28 '16 at 16:16
  • Just about to head out for about 1 hr. There's lots of other people who may be able to help with a question: just pose it! – xQbert Oct 28 '16 at 16:18
  • what do you mean by pose it? –  Oct 28 '16 at 16:20
  • Ask, post a new question, update an existing one if you don't have the answer you want from it. (or if this open still needs work we can get to it (though I have to run again in 25 minutes) – xQbert Oct 28 '16 at 17:55
  • I am just going to try and get an answer here as I have added a bounty for +100 rep –  Oct 28 '16 at 19:01
  • Have you got time? give this another go? –  Oct 28 '16 at 19:33
  • I got your code working by the way apart from that one error: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8_unicode_ci,IMPLICIT) for operation '=' –  Oct 28 '16 at 19:34
0

This type of comment has been posted many times, and trying to get the "latest-for-each" always appears to be a stumbling block and join / subquery nightmare for most.

Especially for a web interface, you might be better to tack on a column (or 2 or 3) to the one table that is your active "posts" table such as Latest1, Latest2, Latest3.

Then, via an insert into your comment table, have an insert trigger on your table to update the main post with the newest ID. Then you always have that ID on the table without any sub-joins. Now, as you mentioned, you might want to have the last 2 or 3 IDs, then add the 3 sample columns and have your insert trigger to the post comment detail do an update to the primary post table something like

update PrimaryPostTable
   set Latest3 = Latest2,
       Latest2 = Latest1,
       Latest1 = NewDetailCommentID
   where PostID = PostIDFromTheInsertedDetail

This would have to be formalized into a proper trigger under MySQL, but should be easy enough to implement. You could prime the list with the latest 1, then as new posts go, it would automatically roll the most recent into their 1st, 2nd, 3rd positions. Finally your query could be simplified down to something like

Select
      P.PostID,
      P.TopicDescription,
      PD1.WhateverDetail as LatestDetail1,
      PD2.WhateverDetail as LatestDetail2,
      PD3.WhateverDetail as LatestDetail3
   from
      Posts P
         LEFT JOIN PostDetail PD1
            on P.Latest1 = PD1.PostDetailID
         LEFT JOIN PostDetail PD2
            on P.Latest2 = PD2.PostDetailID
         LEFT JOIN PostDetail PD3
            on P.Latest3 = PD3.PostDetailID
   where
      whateverCondition

Denormalizing data is typically NOT desired. However, in cases such as this, it is a great simplifier for getting these "latest" entries in a For-Each type of query. Good luck.

Here is a fully working sample in MySQL so you can see the tables and the results of the sql-inserts and the automatic stamping via the trigger to update the main post table. Then querying the post table you can see how the most recent automatically rolls into first, second and third positions. Finally a join showing how to pull all the data from each "post activity"

CREATE TABLE Posts
(   id int, 
    uuid varchar(7),
    imageLink varchar(9),
    `date` datetime,
    ActivityID1 int null,
    ActivityID2 int null,
    ActivityID3 int null,
    PRIMARY KEY (id)
);

CREATE TABLE Activity
(   id int, 
    postid int,
    `type` varchar(40) collate utf8_unicode_ci, 
    commentText varchar(20) collate utf8_unicode_ci, 
    `date` datetime,
    PRIMARY KEY (id)
);

DELIMITER //

CREATE TRIGGER ActivityRecAdded
AFTER INSERT ON Activity FOR EACH ROW
BEGIN
    Update Posts
        set ActivityID3 = ActivityID2,
            ActivityID2 = ActivityID1,
            ActivityID1 = NEW.ID
        where
            ID = NEW.POSTID;

END; //

DELIMITER ;



INSERT INTO Posts
    (id, uuid, imageLink, `date`)
    VALUES
    (123, 'test1', 'blah', '2016-10-26 00:00:00');

INSERT INTO Posts
    (id, uuid, imageLink, `date`)
    VALUES
    (125, 'test2', 'blah 2', '2016-10-26 00:00:00');


INSERT INTO Activity
    (id, postid, `type`, `commentText`, `date`)
VALUES
    (789, 123, 'type1', 'any comment', '2016-10-26 00:00:00'),
    (821, 125, 'type2', 'another comment', '2016-10-26 00:00:00'),
    (824, 125, 'type3', 'third comment', '2016-10-27 00:00:00'),
    (912, 123, 'typeAB', 'comment', '2016-10-27 00:00:00');

-- See the results after the insert and the triggers.
-- you will see that the post table has been updated with the 
-- most recent 
-- activity post ID=912 in position Posts.Activity1
-- activity post ID=789 in position Posts.Activity2
-- no value in position Posts.Activity3
select * from Posts;

-- NOW, insert two more records for post ID = 123.
-- you will see the shift of ActivityIDs adjusted
INSERT INTO Activity
    (id, postid, `type`, `commentText`, `date`)
VALUES
    (931, 123, 'type1', 'any comment', '2016-10-28 00:00:00'),
    (948, 123, 'newest', 'blah', '2016-10-29 00:00:00');

-- See the results after the insert and the triggers.
-- you will see that the post table has been updated with the 
-- most recent 
-- activity post ID=948 in position Posts.Activity1
-- activity post ID=931 in position Posts.Activity2
-- activity post ID=912 in position Posts.Activity3
-- notice the FIRST activity post 789 is not there as 
-- anything AFTER the 4th entry, it got pushed away.
select * from Posts;

-- Finally, query the data to get the most recent 3 items for each post.
select
        p.id,
        p.uuid,
        p.imageLink,
        p.`date`,
        A1.id NewestActivityPostID,
        A1.`type` NewestType,
        A1.`date` NewestDate,
        A2.id SecondActivityPostID,
        A2.`type` SecondType,
        A2.`date` SecondDate,
        A3.id ThirdActivityPostID,
        A3.`type` ThirdType,
        A3.`date` ThirdDate
    from
        Posts p
            left join Activity A1
                on p.ActivityID1 = A1.ID
            left join Activity A2
                on p.ActivityID2 = A2.ID
            left join Activity A3
                on p.ActivityID3 = A3.ID;

You can create a test database as to not corrupt yours to see this example.

DRapp
  • 47,638
  • 12
  • 72
  • 142
  • Ok thanks a lot for your time, and the answer. I shall let you know how I get on and what I decide to do –  Oct 26 '16 at 19:09
  • Hi DRapp, trying to get best answer really and that's why I have added a bounty. I don't fully get your answer but I have edited my question above, so maybe it's more understandable of what I would like to achieve! –  Oct 29 '16 at 10:59
  • @Jack, revised my answer with a fully functioning sample of create tables, insert trigger, and final results showing most recent 3 post activities. You can change the table names to not impact your production data, or create a temporary secondary database to test with. I can tweak more if need be, such as total activity records for a given post, just add another column and add 1 during every insert too. – DRapp Oct 29 '16 at 17:31
0

This will probably get rid of the illegal mix of collations... Just after establishing the connection, perform this query:

SET NAMES utf8 COLLATE utf8_unicode_ci;

For the question about the 'latest 2', please use the mysql commandline tool and run SHOW CREATE TABLE Posts and provide the output. (Ditto for the other relevant tables.) Phpmyadmin (and other UIs) have a way to perform the query without getting to a command line.

Rick James
  • 135,179
  • 13
  • 127
  • 222
0

You can get there with a pretty simple query by using sub-queries. First I specify the user in the where-clause and join the posts because it seems more logic to me. Then I get all the likes for a post with a sub-query.

Now instead of grouping and limiting the group size we join only the values we want to by limiting the count of dates after the date we are currently looking at.

INNER JOIN Activity if you only want to show posts with at least one comment.

SELECT
  u.id,
  u.username,
  u.fullname,
  u.profileImage,
  p.uuid,
  p.caption,
  p.path,
  p.date,
  (SELECT COUNT(*) FROM Activity v WHERE v.uuidPost = p.uuidPost AND v.type = 'like') likes,
  a.commentText,
  a.date
FROM
  Users u INNER JOIN
  Posts p ON p.id = u.id LEFT JOIN
  Activity a ON a.uuid = p.uuid AND a.type = 'comment' AND 2 > (
    SELECT COUNT(*) FROM Activity v
    WHERE v.uuid = p.uuid AND v.type = 'comment' AND v.date > a.date)
WHERE
  u.id = 145


That said a redesign would probably be best, also performance-wise (Activity will soon contain a lot of entries and they always have to be filtered for the desired type). The user table is okay with the id auto-incremented and as primary key. For the posts I would also add an auto-incremented id as primary key and user_id as foreign key (you can also decide what to do on deletion, e.g. with cascade all his posts would also be deleted automatically).

For the comments and likes you can create separated tables with the two foreign keys user_id and post_id (simple example, like this you can only like posts and nothing else, but if there are not many different kind of likes it could still be good to create a post_likes and few other ..._likes tables, you have to think about how this data is usually queried, if those likes are mostly independent from each other it's probably a good choice).

maraca
  • 8,468
  • 3
  • 23
  • 45
  • Hi thanks for showing interest, I am getting this erroe #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.profileImage, p.uuid, p.caption, p.path, p.date, (SELECT COUNT(*) FRO' at line 5 –  Oct 30 '16 at 00:24
  • Forget what I said... it was just a comma ;-) (after fullname, corrected now). – maraca Oct 30 '16 at 00:38
  • 1
    this URL may help here http://sqlfiddle.com/#!9/6b983/1 note this query returns 2 rows per post – Paul Maxwell Oct 30 '16 at 02:43
  • @Used_By_Already thanks, I will remove the comments about the DBMS. Yes max 2 rows per post is what I thought he wants to get. – maraca Oct 30 '16 at 10:10
  • Doesn't work, it shwos the collumn etc, but no comments...And @Used_By_Already already sorted it for me. –  Oct 30 '16 at 12:18
  • I actually do upvote helpfull answer's, but after your first response I did not. But if it's all about some rep, here's your +1. And regarding I don't try to find the best answer, that is stupidity. I wouldn't post a question If I didn't want a good answer, I shall look into your answer more and test it out and let you know. I still have not rewarded the bounty, If you're answer is better, speed wise and durability I shall accept it. But I have also been reccomend to use row_number by other mysql proffessionals, and that is why I assumed from the begging his answer is the best! –  Oct 30 '16 at 19:21
  • @Jack OK, thanks, but you have to agree the sentence is somewhat contradicting. Anyways I think you're right, sub-queries were slow in MySQL, although I don't know how it is in the latest version (but for oracle they are usually faster). I will upvote your question, can't now because it didn't change, the real reason I downvoted was you seemed to extend the question further and further (in comments) what is really frustrating for all those providing answers. Also I really believe you try to find the best answer, but it is not easy. One last time: if you still can, change of design is best. – maraca Oct 30 '16 at 19:37
  • May I ask what exactly do you mean by change teh design? –  Oct 30 '16 at 20:07
  • @Jack some final, final words ;-) I could upvote now, also included some more information, maybe too basic, maybe not. Good luck. – maraca Oct 30 '16 at 20:20
  • I actually found that quite interesting, the id in posts table is the user's id, and I delete posts via the uuid I create...You say to create an auto increment id for the posts and user_id as a foreign key. Not sure what the difference would be apart from if I deleted posts via the posts.id! –  Oct 30 '16 at 20:36
  • There the difference is not that big (except naming), the main focus was on how to decompose Activity. However there are still some advantages when using auto-incremented id, you don't have to generate an id, you can just leave that field empty when inserting. Also when specifying foreign keys and ON DELETE CASCADE then you don't have to do anything, deleting the user is enough and everything is deleted (you can also do nothing, if you don't want that), no extra work. And primary keys are indexed automatically which speeds up queries because you (should) join on them. – maraca Oct 30 '16 at 20:45
  • @Jack I forgot: probably most important of all is *consistency*. If you carefully design the tables with primary keys, foreign keys, unique fields, not null and maybe other restrictions, then you can guarantee that the data inserted is consistent (although in practice this is more wishful thinking, however you can at least drastically reduce errors or detect them early). – maraca Oct 30 '16 at 20:59
  • Ok well thanks a lot for the info, If I have any more questions I shall post them and let you know...Going to have a further look at your answer now. –  Oct 30 '16 at 21:20
  • Hey mate, hope everythings good! I currently have a question about mysql with a bounty for 50rep...Just wondering if youre interested http://stackoverflow.com/questions/40527140/mysql-and-swift-upload-image-would-it-be-better-to-use-afnetworking –  Nov 19 '16 at 20:30