See which posts user has viewed - mysql

I have a table user it has:
id INT PRIMARY AUTO_INCREMENT
name VARCHAR(30) NOT NULL
Also I have table post
id INT PRIMARY AUTO_INCREMENT
post TEXT NOT NULL
If user visits a certain post I would like to store somewhere in the database that he has already viewed the said post. How would I go about modifying the database to accommodate for that and how would I query for the posts that user has not seen. Should I create a new table to store that info? Or is there some other nifty trick?
The idea is that I simply want a way where if user views the post once he can not view it ever again.
Sorry I am just trying to learn databases and this is one challenge I find quite interesting and difficult.

You will need to implement a many-to-many relationship: a User visits multiple Posts, and a Post is visited by multiple users.
This will take the form of a table with only two columns, each of them being a foreign key to one of your existing tables:
CREATE TABLE user_post_visit (
user_id INT NOT NULL,
post_id INT NOT NULL,
PRIMARY KEY (user_id, post_id),
FOREIGN KEY (user_id) REFERENCES user(id),
FOREIGN KEY (post_id) REFERENCES post(id)
);
Add a record into this table every time a user views a post. In order to find posts that a given user has not viewed yet:
SELECT post.*
FROM post
LEFT JOIN user_post_visit AS upv
ON (upv.post_id = post.id AND upv.user_id = <your user ID here>)
WHERE upv.post_id IS NULL;

Use what RandomSeed has suggested for creating a bridge-table between user and post and then use the following to query out the posts viewed by a user:
SELECT post_id, post FROM user_post_visit, post WHERE user_post_visit.user_id=<user id>

It seems I follow behind #RandomSeed. :D
It seems to me that you want many-to-many relation so you need a new table. How you do it is not strict but for example
Table "read_post" or "unread_post":
user_id, post_id
If the table is unread_post (but you have to create new row to every user when new post created) you can show unread post:
SELECT text FROM post INNER JOIN unread_post ON post.id =
unread_post.post_id WHERE user_id = ?
If table is read_post get unread see RandomSeeds answer.

Related

How to select posts created by me or my friends in a news feed?

I've been trying to do this for quite a while now and then I figured that it is the SQL query that I cannot get it working properly in the first place.
I am trying to create a simple news feed page similar to Facebook where you can see the post you made on your profile as well as your friends profile and also you can see posts which your friends made on your profile as well as their own profile.
SELECT
friendsTbl.profile_one,
friendsTbl.profile_two,
user_profile.u_id as my_uid,
user_profile.f_name as my_fname,
user_profile.l_name as my_lname,
friend_profile.u_id as friend_uid,
friend_profile.f_name as friend_fname,
friend_profile.l_name as friend_lname,
profilePostTbl.post as post,
profilePostTbl.post_to as posted_profile,
profilePostTbl.post_by as posted_by
FROM friendsTbl
LEFT JOIN profileTbl AS user_profile ON user_profile.profile_name = IF(friendsTbl.profile_one = 'john123', friendsTbl.profile_one, friendsTbl.profile_two)
LEFT JOIN profileTbl AS friend_profile ON friend_profile.profile_name = IF(friendsTbl.profile_one = 'john123', friendsTbl.profile_two, friendsTbl.profile_one)
LEFT JOIN profilePostTbl ON (post_by = IF(profilePostTbl.post_to = friendsTbl.profile_one,profilePostTbl.post_to, profilePostTbl.post_by))
WHERE friendsTbl.profile_one = 'john123' OR friendsTbl.profile_two = 'john123'
Here's a fiddle: http://sqlfiddle.com/#!2/a10f39/1
For this example, john123 is the user that is currently logged in and is friends with hassey, smith and joee and therefore only those posts must show in the news feed which john123 posted on his own or his friend's post and the ones that his friends posted on their own profile as well as on john123's profile.
This question is a follow-up to PHP Sub-query to select all user's profile who are friends with me in Friends table.
I know you've already accepted an answer, but I was half-way writing this so I decided to post it anyway.
I'm going to go a little bit back before hopefully answering your question. When developing applications and constructing databases, you should ALWAYS try to structure things as descriptive and compact as possible. It would be really awkward to have a variable/column named color and store encrypted user passwords there (weird, right?). There are some standard database naming conventions which, when followed, make life a lot easier specially when developing complicated applications. I would advice you to read some blogs regarding the naming conventions. A good starting point may be this one.
I fully realize that with the suggested changes below you might need to partially/fully rewrite the application code you've written so far, but it's up to you if you really want things working better.
Let's begin by fixing the database structure. By the looks of it, you're doing an application similar to facebook's newsfeed. In this case, using FOREIGN KEYS is pretty much mandatory so you could guarantee some data consistency. The example database schema below shows how you can achieve that.
-- Application users are stored here.
CREATE TABLE users (
user_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(255),
last_name VARCHAR(255),
profile_name VARCHAR(255)
) ENGINE=InnoDb;
-- User friendship relations go here
CREATE TABLE friends (
friend_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
profile_one INT NOT NULL,
profile_two INT NOT NULL,
FOREIGN KEY (profile_one) REFERENCES users (user_id),
FOREIGN KEY (profile_two) REFERENCES users (user_id)
) ENGINE=InnoDb;
-- User status updates go here
-- This is what will be displayed on the "newsfeed"
CREATE TABLE statuses (
status_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
author_id INT NOT NULL,
recipient_id INT NOT NULL,
message TEXT,
-- created date ?
-- last updated date ?
FOREIGN KEY (author_id) REFERENCES users (user_id),
FOREIGN KEY (recipient_id) REFERENCES users (user_id)
) ENGINE=InnoDb;
-- Replies to user statuses go here. (facebook style..)
-- This will be displayed as the response of a user to a certain status
-- regardless of the status's author.
CREATE TABLE replies (
reply_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
status_id INT NOT NULL,
author_id INT NOT NULL,
message TEXT,
FOREIGN KEY (status_id) REFERENCES statuses (status_id),
FOREIGN KEY (author_id) REFERENCES users (user_id)
) ENGINE=InnoDb;
Now that this is fixed, we could proceed with the next step - selecting the newsfeed for john123 (who has user_id=1). This can be achieved with the query below:
SET #search_id:=1; -- this variable contains the currently logged in user_id so that we don't need to replace the value more than once in the whole query.
SELECT
statuses.*,
author.first_name AS author_first_name,
author.last_name AS author_last_name,
recipient.first_name AS recipient_first_name,
recipient.last_name AS recipient_last_name
FROM statuses
JOIN users AS author ON author.user_id = statuses.author_id
JOIN users AS recipient ON recipient.user_id = statuses.recipient_id
WHERE (statuses.author_id = #search_id OR statuses.recipient_id = #search_id)
ORDER BY status_id ASC
And here you could see it in action in an sqlfiddle. As you can see, just by structuring the database better, I've eliminated the need of a sub-query (which is what EXISTS / NOT EXISTS do according to the docs and EXPLAIN). Furthermore the above SQL code would be a lot easier to maintain and extend.
Anyway, I hope you find this useful.
I think you need to start with your posts and use EXISTS to limit result to the relevant ones:
SELECT * FROM
profilePostTbl post
WHERE
-- This post is by a friend of current user or his own post.
EXISTS (SELECT * FROM friendsTbl WHERE
post.post_by IN (profile_one, profile_two)
AND 'john123' IN (profile_one, profile_two))
-- This post is addressing the current user
OR post_to = 'john123';
If you want to render names of post authors and addressees, just join them to the post:
SELECT post.*,
post_by.u_id as by_uid,
post_by.f_name as by_fname,
post_by.l_name as by_lname,
post_to.u_id as to_uid,
post_to.f_name as to_fname,
post_to.l_name as to_lname
FROM
profilePostTbl post INNER JOIN
profileTbl post_by ON post.post_by = post_by.profile_name INNER JOIN
profileTbl post_to ON post.post_to = post_to.profile_name
WHERE
-- This post is by a friend of current user or his own post.
EXISTS (SELECT * FROM friendsTbl WHERE
post.post_by IN (profile_one, profile_two)
AND 'john123' IN (profile_one, profile_two))
-- This post is addressing the current user
OR post_to = 'john123';

RSS feed integration with mySQL database structure

I have a topics table that has user generated topics and RSS generated topics but in the existing mySQL database setup, when the RSS feed is inserted into the database, the user generated topic fields are NULL (e.g. topic_title), and when a user adds a topic, the RSS fields in the topic table are NULL (e.g. rss_title and rss_source_name). I also have foriegn keys in the topic table which become NULL (e.g. user_id) when the RSS stream is pulled through - this causes data to fail inserting as user_id is a foriegn key and cannot be NULL. Ideally, I'd like both user generated topics and RSS topics to be stored in the topics table without the fields becoming null, but not sure what would be the best way to structure the existing database (below is an example):
Category table
cat_id
Topic table (Should store all both user generated topics and RSS generated topics)
topic_id
cat_id
topic_title (becomes NULL when RSS feed is inserted into database)
user_id (becomes NULL when RSS feed is inserted into database - this causes data to fail inserting as user_id is a foriegn key and cannot be NULL)
rss_title (becomes NULL when user creates content)
rss_source_name (becomes NULL when user creates content)
Comments table
comments_id
topic_id
comments_title
user_id
Will the topics table have to be separated into two separate tables (one for user generated and one for RSS), but this would cause NULL issues in the comments table. Any advice?
If you want those fields to be null, just declare them to be null! FK constraint checks in SQL treat a null FK as ok.
EDIT part 1 Since user and rss topics both shave titles, just have one field/column instead of two (one for user & one for rss).
If you don't ever want a field to be null then you have to put it in a separate table. Have a table for a user topic topic_id and user_id and another for an RSS topic topic_id and source_name. Then use a table for all topics with columns shared by both user and rss topics, eg topic_id, title, cat_id etc. Each of user and rss topic tables has a topic_id FK to the all topics table. This is standard table subtyping. There are topics, user topics and rss topics.
EDIT part 2 FKs are not columns or pointers. They are constraints. They say that columns' values always appear as other columns' values.
You don't say what the Comments table means. Apparently a user creates a comment, and there is a constraint that its topic_id be of either a user or rss topic_id? Now have a FK to the shared table.
EDIT part 3
Here are the (renamed for clarity) tables per above:
All_topics(topic_id,title,cat_id)
pk (topic_id)
fk (cat_id) references Category (cat_id)
User_topics(topic_id,user_id)
pk (topic_id)
fk (topic_id) references All_topics (topic_id)
Rss_topics(topic_id,source_name)
pk (topic_id)
fk (topic_id) references All_topics (topic_id)
Comments(comments_id,topic_id,comments_title,user_id)
pk (comment_id)
fk (topic_id) references All_topics (topic_id)
If a user_topic or rss_topic row is added or deleted then you add or delete the topic row with its topic_id (which also holds it title).
If you need to keep user/rss titles separate (why??) then there's no all-topic title and topic_title and rss_title go in the user and rss topic tables.
Ideally also some constraints. (Or certain different design styles for table subtyping.)
select topic_id from All_topics = select topic_id from User_topics UNION select topic_id from User_topics
NOT EXISTS (select topic_id from User_topics JOIN USING (topic_id) Rss_topics)
No nulls.

How to set up two MySQL data fields so one or the other can be null but not both?

I have 5 MySQL data fields for a votes table:
post id
poll id
vote id
voter
voteid
You can vote in a poll or vote for a post. If you vote in a poll, the post/person field will be null. If you vote for a post, the vote field will be null.
I want to set up the table so it will allow you to make either the post id or vote id null, but not both. I'm using phpmyadmin to manage my database.
Any ideas?
I have to agree with jmilloy above the best thing to do is to create separate tables. This an example how this would work:
Table structure and sample data:
CREATE TABLE post (
post_id INT AUTO_INCREMENT PRIMARY KEY,
vote_id INT
);
CREATE TABLE poll (
poll_id INT AUTO_INCREMENT PRIMARY KEY,
vote_id INT
);
CREATE TABLE voter(
vote_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30)
);
INSERT INTO post (vote_id) VALUES(1),(2),(3),(6);
INSERT INTO poll (vote_id) VALUES(3),(5),(4),(7);
INSERT INTO voter(name) VALUES ('bob'),
('Jack'),
('Joe'),
('Shara'),
('Hillary'),
('Steven'),
('Sandra');
To retrieve the voter that has voted for a post you have to use a JOIN. This is an example how this would look like if you want to find the voters for a post number 2.
SELECT post.post_id, vote.name
FROM (post
JOIN post_vote
ON post_vote.post_id = post.post_id)
JOIN vote
ON vote.vote_id = post_vote.vote_id
WHERE post.post_id = 2;
SQL FIDDLE DEMO
Some explanation if you have a poll and a vote you have a many to many relationship, i.e. one voter can vote for more than one poll and one poll can have more than one voter. To bridge between the vote and poll table you use a bridge table. This tables contains all the poll numbers and vote combinations. So when you want to know who has voted for a particular poll you need to link the poll_id with the poll_id in the poll_vote table. The result is then matched with the vote table using the vote_id in the poll_vote table and the vote table. Hope this helps. Good luck with your project.
Look at the MySQL CREATE TABLE syntax http://dev.mysql.com/doc/refman/5.1/en/create-table.html
Notice that NOT NULL or NULL are part of a column definition. The default is NULL. This can only be applied to columns, not pairs of columns.
The solution here is to make two separate tables, one for post votes and one for poll votes. Then you can put the relevant fields in each table. This will also save you space, and make your data less error prone.

creating a new mysql table with a primary auto incrementing id that is linked to another table?

I want two tables to share a primary auto incrementing id, is this possible? how do i do this? is their anything i need to consider?
the reasons i am doing this, is because it is a better solution than adding groups column to the users table, and also better than creating a completly seperate groups table, because if they share a primary key, i can use the existing posts table for both groups and users. instead of having to create a two distinct posts tables, (group_posts table for group posts. and a user_posts table for user posts.)
existing users table is
id(primary, ai)
username
password
email
my groups table that i want to link to my users table with a shared ai primary key
id(primary, ai, linked to users table id)
group_name
created_by
creation_date
etc.
You should make you schema clearer by doing the following:
Create a table (e.g. people)
id, primary key, auto-increment
type, tells you if it's a user or a group
Make users and groups primary keys foreign keys on people
Insert records in people
Obtain the ID that was assigned using LAST_INSERT_ID()
Insert in users or groups appropriately, using the ID obtained above
Then you'd reference "people", and not "users" or "groups" in your posts table and so on.
Conceptually, thinking of it in an OO way, it means users and groups both extend people.

data model and app logic question?

basically i have this problem i have a table of posts(in my mysql database)
Post{user_id, post, vote_up(int), vote_down(int)}
because users log in, then they get to vote, but i wanted to check if the user has already voted, so then i can not allow them to vote again, and obviously im going to be using user session to control this oppose to tracking ips etc.
so i was thinking do i have to create another table or is thier a better approach.
You will need another table e.g. called "votes":
Vote{user_id, post_id}
I assume, that your "Post" table has a primary key (e.g. "id") that you have not shown in your question above? Then "post_id" should be a foreign key to Post#id.
/Carsten
You'll definately need another table, and some primary and foreign keys would help too:
Post{post_id(int), user_id(varchar), post(clob)}
Votes{vote_id(int), post_id, user_id, up_down(char)}
Your vote_up and vote_down column values are removed and are now calculated with queries:
-- vote_up
select count(*) from votes where post_id = n and up_down = '+';
-- vote_down
select count(*) from votes where post_id = n and up_down = '-';
There should be a unique index on votes(post_id, user_id) to prevent multiple votes by the same user on the same post.
The primary key vote_id does not have to be defined, but each table should have a primary key and if you don't want to use a surrogate key, then you can define the PK using the same columns as above and this will serve as the unique index too, so ot does not have to be defined.