2

Context

I'm trying to create a "feed" system on my website where users can go to their feed and see all their new notifications across the different things they can do on the website. For example, in the "feed" section, users are able to see if the users they follow have created articles and if the users have commented on articles. My current feed system simply uses two separate queries to obtain this information. However, I want to combine these two queries into one so that the user can view the activity of those they follow chronologically. The way my system works now, I get five articles from each person the user follows and put it in the "feed" section and then get five article comments and post it in the same area in the "feed" section. Instead of the queries being separate, I want to combine them so that, instead of seeing five article posts in a row and then five article comments in a row, they will see the feed posts that happened in chronological order, whether the other users created an article first, then commented, then created another article, or whatever the order is, instead of always seeing the same order of notifications.

Question

First, let me show you my code for table creation if you would like to recreate this. The first thing to do is to create a users table, which my articles and articlecomments tables reference:

CREATE TABLE users (
    idUsers int(11) AUTO_INCREMENT PRIMARY KEY NOT NULL,
    uidUsers TINYTEXT NOT NULL,
    emailUsers VARCHAR(100) NOT NULL,
    pwdUsers LONGTEXT NOT NULL,
    created DATETIME NOT NULL, 
    UNIQUE (emailUsers),
    FULLTEXT(uidUsers)
) ENGINE=InnoDB;

Next, let's create the articles table:

CREATE TABLE articles (
    article_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    title TEXT NOT NULL,
    article TEXT NOT NULL,
    date DATETIME NOT NULL,
    idUsers int(11) NOT NULL,
    topic VARCHAR(50) NOT NULL,
    published VARCHAR(50) NOT NULL,
    PRIMARY KEY (article_id),
    FULLTEXT(title, article),
    FOREIGN KEY (idUsers) REFERENCES users (idUsers) ON DELETE CASCADE ON UPDATE 
CASCADE
) ENGINE=InnoDB;

Finally, we need the articlecomments table:

CREATE TABLE articlecomments (
    comment_id INT(11) AUTO_INCREMENT PRIMARY KEY NOT NULL,
    message TEXT NOT NULL,
    date DATETIME NOT NULL,
    article_id INT(11) UNSIGNED NOT NULL,
    idUsers INT(11) NOT NULL,
    seen TINYTEXT NOT NULL,
    FOREIGN KEY (article_id) REFERENCES articles (article_id) ON DELETE CASCADE ON UPDATE CASCADE,
    FOREIGN KEY (idUsers) REFERENCES users (idUsers) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;

To populate the tables sufficiently for this example, we will use these statements:

INSERT INTO users (uidUsers, emailUsers, pwdUsers, created) VALUES ('genericUser', 'genericUser@hotmail.com', 'password', NOW());

INSERT INTO articles (title, article, date, idUsers, topic, published) VALUES ('first article', 'first article contents', NOW(), '1', 'other', 'yes');
INSERT INTO articles (title, article, date, idUsers, topic, published) VALUES ('second article', 'second article contents', NOW(), '1', 'other', 'yes');
INSERT INTO articles (title, article, date, idUsers, topic, published) VALUES ('third article', 'third article contents', NOW(), '1', 'other', 'yes');

INSERT INTO articlecomments (message, date, article_id, idUsers, seen) VALUES ('first message', NOW(), '1', '1', 'false');
INSERT INTO articlecomments (message, date, article_id, idUsers, seen) VALUES ('second message', NOW(), '1', '1', 'false');
INSERT INTO articlecomments (message, date, article_id, idUsers, seen) VALUES ('third message', NOW(), '1', '1', 'false');

The two queries that I'm using to obtain data from the articles and articlecomments tables are below:

Query 1:

SELECT 
  articles.article_id, articles.title, articles.date, 
  articles.idUsers, users.uidUsers 
FROM articles 
JOIN users ON articles.idUsers = users.idUsers 
WHERE articles.idUsers = '1' AND articles.published = 'yes' 
ORDER BY articles.date DESC 
LIMIT 5

Query 2:

SELECT
   articlecomments.comment_id, articlecomments.message,
   articlecomments.date, articlecomments.article_id, users.uidUsers 
FROM articlecomments 
JOIN users ON articlecomments.idUsers = users.idUsers 
WHERE articlecomments.idUsers = '1' 
ORDER BY articlecomments.date DESC 
LIMIT 5

How would I combine these two queries that contain different information and columns so that they are ordered based on the date of creation (articles.date and articlecomments.date, respectively)? I want them to be in separate rows, not the same row. So, it should be like I queried them separately and simply combined the resulting rows together. If there are three articles and three article comments, I want there to be six total returned rows.

Here's what I want this to look like. Given there are three articles and three article comments, and the comments were created after the articles, this is what the result should look like after combining the queries above (I'm not sure if this portrayal is possible given the different column names but I'm wondering if something similar could be accomplished):

+-------------------------------+-------------------+---------------------+----------------------------------------------------------------+---------+-------------+
| id (article_id or comment_id) |   title/message   |        date         | article_id (because it is referenced in articlecomments table) | idUsers |  uidUsers   |
+-------------------------------+-------------------+---------------------+----------------------------------------------------------------+---------+-------------+
|                             1 | first message     | 2020-07-07 11:27:15 |                                                              1 |       1 | genericUser |
|                             2 | second message    | 2020-07-07 11:27:15 |                                                              1 |       1 | genericUser |
|                             3 | third message     | 2020-07-07 11:27:15 |                                                              1 |       1 | genericUser |
|                             2 |    second article | 2020-07-07 10:47:35 |                                                              2 |       1 | genericUser |
|                             3 |    third article  | 2020-07-07 10:47:35 |                                                              3 |       1 | genericUser |
|                             1 |    first article  | 2020-07-07 10:46:51 |                                                              1 |       1 | genericUser |
+-------------------------------+-------------------+---------------------+----------------------------------------------------------------+---------+-------------+

Things I have Tried

I have read that this might involve JOIN or UNION operators, but I'm unsure of how to implement them in this situation. I did try combining the two queries by simply using (Query 1) UNION (Query 2), which at first told me that the number of columns were different in my two queries, so I had to remove the idUsers column from my articlecomments query. This actually got me kind of close, but it wasn't formatted correctly:

+------------+-------------------+---------------------+---------+-------------+
| article_id |       title       |        date         | idUsers |  uidUsers   |
+------------+-------------------+---------------------+---------+-------------+
|          2 | first message     | 2020-07-07 10:47:35 |       1 | genericUser |
|          3 | third article     | 2020-07-07 10:47:35 |       1 | genericUser |
|          1 | first article     | 2020-07-07 10:46:51 |       1 | genericUser |
|          1 |    second article | 2020-07-07 11:27:15 |       1 | genericUser |
|          2 |    third article  | 2020-07-07 11:27:15 |       1 | genericUser |
|          3 |    first article  | 2020-07-07 11:27:15 |       1 | genericUser |
+------------+-------------------+---------------------+---------+-------------+

Any ideas? Let me know if there is any confusion. Thanks.

Server type: MariaDB

Server version: 10.4.8-MariaDB - mariadb.org binary distribution

Carson D
  • 81
  • 13
  • 1
    First of all you need to imagine how a final result of this "combine operation" should look like - and document that vision (just prepare a specification). You are trying to combine two queries but you are not quite sure what do you want to get. I vote to close this question. – krokodilko Jul 07 '20 at 06:55
  • 2
    `UNION` should be the right answer, from most of your description, so it would help *immensely* if you could add some sample data and expected results to your question so that it's clear in what way you both want to "combine queries" and have "rows separately" which sound like they contradict each other. – Damien_The_Unbeliever Jul 07 '20 at 07:35
  • 1
    Please in code questions give a [mre]--cut & paste & runnable code, including smallest representative example input as code; desired & actual output (including verbatim error messages); tags & versions; clear specification & explanation. For SQL that includes DBMS & DDL (including constraints & indexes) & input as code formatted as a table. [ask] When giving a business relation(ship)/association or table (base or query result), say what a row in it states about the business situation in terms of its column values. When stuck give parts you can code & say what they do & explain re being stuck. – philipxy Jul 07 '20 at 08:34
  • Thanks for the feedback. I just made it a reproducible answer. Could you check it out again? – Carson D Jul 07 '20 at 18:59
  • "When giving a business relation(ship)/association or table (base or query result), say what a row in it states about the business situation in terms of its column values." I have a row of values here, (id, tm, date, article_id, idUsers, uidUsers) & I have some values for tables users, articles & articlecomments. Under what condition does my row go in the result? PS Ask 1 question. Don't write other questions about things you are wondering about, because then it's not clear what your post is asking. If you want to ask other (specific researched non-duplicate) questions, post another post. – philipxy Jul 09 '20 at 04:50
  • I need one clarification here. Let's assume last 5 articles created are `a2` to `a6`, but 1 out of last (most recent) 5 comments belongs to article `a1`. So shall I consider that comment in final result & if Yes, than where it'll go as far as order goes. – Harshil Doshi Jul 10 '20 at 19:18

3 Answers3

4

This seems like MySQL. You could do something like this:

select * from (SELECT articles.article_id as id_article_comment, articles.title as title_message, articles.date as created, 'article' AS contenttype, articles.article_id as article_id, articles.idUsers, users.uidUsers FROM articles JOIN users ON articles.idUsers = users.idUsers WHERE articles.idUsers = '1' AND articles.published = 'yes' ORDER BY articles.date DESC LIMIT 5) a
union all
select * from (SELECT articlecomments.comment_id, articlecomments.message, articlecomments.date, 'article comment' AS contenttype, articlecomments.article_id, articlecomments.idUsers, users.uidUsers FROM articlecomments JOIN users ON articlecomments.idUsers = users.idUsers WHERE articlecomments.idUsers = '1' ORDER BY articlecomments.date DESC LIMIT 5) b
order by created DESC

See example here: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=26280a9c1c5f62fc33d00d93ab84adf3

Result like this:

id_article_comment | title_message  | created             | article_id | uidUsers   
-----------------: | :------------- | :------------------ | ---------: | :----------
                 1 | first article  | 2020-07-09 05:59:18 |          1 | genericUser
                 2 | second article | 2020-07-09 05:59:18 |          1 | genericUser
                 3 | third article  | 2020-07-09 05:59:18 |          1 | genericUser
                 1 | first message  | 2020-07-09 05:59:18 |          1 | genericUser
                 2 | second message | 2020-07-09 05:59:18 |          1 | genericUser
                 3 | third message  | 2020-07-09 05:59:18 |          1 | genericUser

Explanation

Since we want to use order by and limit, we'll create a subquery out of the first line and select all columns from that first subquery. We'll name each field the way we want in the output.

We do the same thing with the 2nd query and add a union all clause between them. Then, we apply ordering based on created date (which was an alias in the first query) to get the results you desired in the order you desired.

If you use union, duplicate rows will be eliminated from the result. If you use union all, duplicate rows - if they exist - will be retained. union all is faster since it combines 2 datasets (as long as columns are same in queries. union has to, additionally, look for duplicate rows and remove them from the query.

Carson D
  • 81
  • 13
zedfoxus
  • 35,121
  • 5
  • 64
  • 63
  • The question doesn't say it is MySQL. You should not have edited it to add that tag. You should comment on the question for clarification. – philipxy Jul 09 '20 at 09:06
  • 1
    Thanks for the response. I'll check this out tonight. – Carson D Jul 09 '20 at 23:03
  • 1
    I edited your response a bit to make it correct for what I'm looking for. One of the things I did was add a row called "contentype" and I specified whether the content type was an article or article comment. Is this a valid way to differentiate between content types when using a UNION statement? – Carson D Jul 11 '20 at 01:46
  • Hi @CarsonD, I will need to see some examples of what you were thinking of for contenttype to answer your question. Based on your question, it sounds like what you are saying is a valid way to differentiate content types between an article and comment. – zedfoxus Jul 12 '20 at 00:24
1

You don't mention the version of MySQL you are using, so I'll assume it's a modern one (MySQL 8.x). You can produce a row number on each subset using ROW_NUMBER() and then a plain UNION ALL will do the trick.

I fail to understand the exact order you want, and what does the fourth column article_id (because it is referenced in articlecomments table) means. If you elaborate I can tweak this answer accordingly.

The query that produces the result set you want is:

select *
from ( (
  SELECT 
    a.article_id as id, a.title, a.date, 
    a.article_id, u.uidUsers,
    row_number() over(ORDER BY a.date DESC) as rn
  FROM articles a
  JOIN users u ON a.idUsers = u.idUsers 
  WHERE a.idUsers = '1' AND a.published = 'yes' 
  ORDER BY a.date DESC 
  LIMIT 5
  ) union all (
  SELECT
    c.comment_id, c.message, c.date, 
    c.article_id, u.uidUsers,
    5 + row_number() over(ORDER BY c.date DESC) as rn
  FROM articlecomments c
  JOIN users u ON c.idUsers = u.idUsers 
  WHERE c.idUsers = '1' 
  ORDER BY c.date DESC 
  LIMIT 5
  )
) x
order by rn

Result:

id  title           date                 article_id  uidUsers     rn
--  --------------  -------------------  ----------  -----------  --
 1  first article   2020-07-10 10:37:00           1  genericUser   1
 2  second article  2020-07-10 10:37:00           2  genericUser   2
 3  third article   2020-07-10 10:37:00           3  genericUser   3
 1  first message   2020-07-10 10:37:00           1  genericUser   6
 2  second message  2020-07-10 10:37:00           1  genericUser   7
 3  third message   2020-07-10 10:37:00           1  genericUser   8

See running example in db<>fiddle.

The Impaler
  • 45,731
  • 9
  • 39
  • 76
  • Thanks for the response. What would using `ROW_NUMBER()` do? Is it just something that works as an identifier for a specific row I might want? Also, the order I want is just the descending order of the date in which the articles/comments were created, if that's what you mean. The `article_id` column is there because I need that information in the 'feed" section of my website so that when the user clicks on the post that contains the article comment, it takes the user to the corresponding article, which I need the `article_id` to locate. – Carson D Jul 10 '20 at 02:38
  • @CarsonD See updated query. Yes, the row number is artificially produced to sort the rows in the right order after the union, according to the article/comment dates. I added the article_id. – The Impaler Jul 10 '20 at 10:41
0

you can cross join like this=

select select(1) from FROM [job] WITH (NOLOCK)
WHERE MemberCode = 'pay'
    AND CampaignID = '2'

    cross  join

      select(1)
        FROM [product] WITH (NOLOCK)
        WHERE MemberCode = 'pay'
            AND CampaignID = '2'