twitter like relational db? - mysql

I'm currently trying to make a small web service, just for fun and learning. (I recently started to learn programming practically.)
The service is like twitter. users follow their friends and get statuses of them.
I have a question about relational db.
There are 3 tables, which are 'user','friendship' and 'status'.
here are codes to describe what these tables look like.
create table user (
id int unsigned auto_increment primary key,
username varchar(16) not null,
password varchar(255) not null,
created datetime default null
);
create table friendship (
id int unsigned auto_increment primary key,
userid int not null,
friend int not null,
created datetime default null
);
create table status (
id int unsigned auto_increment primary key,
userid int not null,
current_status int not null,
created datetime default null
);
'user.id' is equal to 'friendship.userid' and 'status.userid'.
・The situation is;
1, a user logged in, and will get his friends' status numbers.
2, php got the user's id and find his friends's userid, username, status and created (of status).
I tried some sql sentences but not working.
Could any super sql writer show me correct sql ?
Cheers!
Edited 1;
I tried sql like this;
select userid, username, current_status, created
from status
join users u1
on u1.id = status.userid
join friendship
on friendship.friend = u1.id
join user u2
on u2.id =friendship.userid
where u2.id = "the user's id from php";
and mysql throw this "#1146 - Table 'Application.user' doesn't exist."

Your first join statement has "users" while your table name is "user." This is a common newbie error. You should pick standard and follow it. Most DBs that I see use the singular form, so there is no confusion whether your tables names are "friends" or "friend."
Not to worry though, I think practically everyone makes this mistake when starting out. There might be other errors, but this one pops out immediately.
Try fixing that and letting us know how it goes.
Oh, and in table selects declare which table I am selecting from: i.e. SELECT from user.userid, …

Something like this should work:
select u1.userid, username, current_status, created
from status
inner join [user] u1 on u1.id = status.userid
inner join friendship on friendship.friend = u1.id
inner join [user] u2 on u2.id = friendship.userid
where u2.id = #User2Id
Like was mentioned in the comments: Table 'Application.user' doesn't exist
In SQL Server user is a reserved word so we add the square braces around it. (see code). Or you change the table name to be something else (IE Client, TwitterUser, etc...)

Related

Merging rows and updating related foreign key

I have a current users table. A distinct user is defined as when the email and phoneNumber together are unique. Currently the table looks like this:
And another table called giftee_info which has the foreign key on column userId to users.id:
The users table is going to be parsed out into 2 tables: users and user_metadata. Now a distinct user will be defined by the phoneNumber. So you can see in the data above, users with id's 4 and 5 are the same, and have to be merged.
The new users table will look like:
And the new user_metadata table will look like this:
Note how the 4th row has userId of 4, so users 4 and 5 have merged to one user.
And giftee_info will look like this:
See how the 3rd row in giftee_info contains userId 4, as the user with id 5 has been merged into one user.
The data I've provided is basic. In reality there are many rows, and a user with the same number may have 5 different email address (and so are currently treated as separate users in the current schema).
The part I'm having most trouble with is updating giftee_info. So any rows with userId's that have been merged down into one user need to be updated. How can I do this?
Since phonenumber can be NULL, I'm using externalid as the unique identifier below.
Start by creating the new users table from the distinct phone numbers in the old users table:
CREATE TABLE new_users (id INT PRIMARY KEY AUTO_INCREMENT, externalid VARCHAR(32), phonenumber VARCHAR(32))
SELECT DISTINCT NULL, externalid, phonenumber
FROM users
Then put all the emails into the user_metadata table, by joining the old and new users tables to get the emails along with the new user IDs.
CREATE TABLE user_metadata (id INT PRIMARY KEY AUTO_INCREMENT, userid INT, email VARCHAR(100), subscribe INT(1))
SELECT NULL, nu.id, u.email, 0
FROM new_users as nu
JOIN users AS u ON nu.externalid = u.externalid
Now you can update giftee_info by replacing the old user IDs with the new user IDs.
UPDATE giftee_info AS g
JOIN users as u ON g.userid = u.userid
JOIN new_users As nu ON nu.externalid = u.externalid
SET g.userid = nu.userid
Once this is all done you can rename the tables so new_users is now users.

How can I get all business data AS WELL as if current user is following them?

In mysql how can I write a query that will fetch ALL business data, and at the same time (or not if it is better another way) check if user is following that business? I have the following relationship table to determine if a user is following a business (status=1 would mean that person is following):
CREATE TABLE IF NOT EXISTS `Relationship_User_Follows_Business` (
`user_id` int(10) unsigned NOT NULL,
`business_id` int(10) unsigned NOT NULL,
`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '1=following, 0=not following'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `Relationship_User_Follows_Business`
ADD UNIQUE KEY `unique_user_business_id` (`user_id`,`business_id`);
Assume business table just holds data on different businesses like name, phone number, etc. I would want to return all of the business data in my query (Business.*). I want to append the status (0 or 1) to the end of each business row to determine if the user is following that business. I have tried the following query but it does not work because it is narrowing the results to only show a business if there is a relationship row. I wish to show ALL businesses regardless if a relationship row exists or not because I only create the relationship row if a user follows:
SELECT Business.*, Relationship_User_Follows_Business.status FROM Business, Relationship_User_Follows_Business WHERE 104=Relationship_User_Follows_Business.user_id AND Business.id=Relationship_User_Follows_Business.business_id
Note that I am using 104 as a test user id. The user id would normally be dependent on user, not a static 104.
You are looking for a LEFT JOIN and not an INNER JOIN which keeps all the records from the master table and all the matching rows from the details table . Also, avoid using implicit join syntax(comma separated) and use the proper syntax of a join :
SELECT Business.*, Relationship_User_Follows_Business.status
FROM Business
LEFT JOIN Relationship_User_Follows_Business
ON Business.id = Relationship_User_Follows_Business.business_id
AND Relationship_User_Follows_Business.user_id = 104

MySQL Delete-Select row

I'm trying to delete a row from table subscription where there is two foreign Keys (id_user and id_journal). The information that I have is email from table user and nome from table journal. The deleted row needs to match user.email and journal.nome. I can't find a solution. How can I do it?
Table user:
id
name
email
password
Table journal:
id
name
Table Subscription:
id
id_user
id_journal
The last two queries that I tried:
DELETE FROM assinatura WHERE (
SELECT tbluser.id, journal.id
FROM tbluser, journal
WHERE email = '$email' AND nome = '$nome')
DELETE FROM assinatura
INNER JOIN tbluser on (tbluser.email = '$email')
INNER JOIN journal on (journal.nome = '$nome')
I've tried many others queries, but unsuccessful. I think it's important to say that I'm new at MySQL.
DELETE
FROM Subscription
WHERE id_user IN (
SELECT usr.id
FROM user AS usr
WHERE usr.email = INPUT_EMAIL
)
AND id_journal IN (
SELECT jrnl.id
FROM journal AS jrnl
WHERE jrnl.name = INPUT_NAME
)
On another topic ...
Try to avoid excess subscriptions for same user/journal combo by
CREATE TABLE subscription
(
id int NOT NULL AUTO_INCREMENT primary key,
id_user int not null,
id_journal int not null,
UNIQUE KEY `user_journal_intersect` (`id_user`,`id_journal`)
-- note Alan stated FK RI in place already
);
U can PK on composite instead, of course (ditching the id column), programmer pref

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';

Defining a two-way link

I have a users table, and I want to define a "friends" relationship between two arbitrary users.
Up until now, I've used two different methods for this:
The friends table contains user1 and user2. Searching for users involves a query that looks like
... WHERE #userid IN (`user1`,`user2`), which is not terribly efficient
The friends table contains from and to fields. Initiating a friend request creates a row in that direction, and if it accepted then a second row is inserted with the opposite direction. There is additionally a status column that indicates that this has happened, making the search something like:
... WHERE `user1`=#userid AND `status`=1
I'm not particularly satisfied with either of these solutions. The first one feels messy with that IN usage, and the second seems bloated having two rows to define a single link.
So that's why I'm here. What would you suggest for such a link? Note that I don't need any more information saved with it, I just need two user IDs associated with each other, and preferably some kind of status like ENUM('pending','accepted','blocked'), but that's optional depending on what the best design for this is.
There are in general two approaches:
Store each friend pair once, storing the friend with the least id first.
CREATE TABLE
friend
(
l INT NOT NULL,
g INT NOT NULL,
PRIMARY KEY
(l, g),
KEY (g)
)
Store each friend pair twice, both ways:
CREATE TABLE
(
user INT NOT NULL,
friend INT NOT NULL,
PRIMARY KEY
(user, friend)
)
To store additional fields like friendship status, acceptance dates etc. you usually utilize a second table, for reasons I'll describe below.
To retrieve a list of friends for each user, you do:
SELECT CASE #myuserid WHEN l THEN g ELSE l END
FROM friend
WHERE l = #myuserid
OR
g = #myuserid
or
SELECT g
FROM friend
WHERE l = #myuserid
UNION
SELECT l
FROM friend
WHERE g = #myuserid
for the first solution; and
SELECT friend
FROM friend
WHERE user = #friend
To check if two users are friends, you issue this:
SELECT NULL
FROM friend
WHERE (l, g) =
(
CASE WHEN #user1 < #user2 THEN #user1 ELSE #user2 END,
CASE WHEN #user1 > #user2 THEN #user1 ELSE #user2 END
)
or
SELECT NULL
FROM friend
WHERE (user, friend) = (#user1, #user2)
Storage-wise, the two solutions are almost the same. The first (least/greatest) solution stores twice as few rows, however, for it to work fast you should have a secondary index on g, which, in fact, has to store g plus the part of the table's primary key which is not in the secondary index (that is, l). Thus, each record is effectively store twice: once in the table itself, once again in the index on g.
Performance-wise, the solutions are almost the same too. The first one, though, requires two index seeks followed by index scans (for "all friends"), the second one just one index seek, so for the L/G solution I/O amount might be slighly more. This might be mitigated a little by the fact that the one single index may become one level deeper than two independent ones, so the initial search may take one page read more. This may slow down "are they friends" query a little for the "both pairs" solution, compared to L/G.
As for the additional table for extra data, you most probably want it because it's usually much less used than the two query I described above (and usually only for history purposes).
Its layout also depends on the kind of queries you are using. Say, if you want "show my last ten friendships", then you may want to store the timestamp in "both pairs" so that you don't have to do filesorts, etc.
Consider the following schema:
CREATE TABLE `users` (
`uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(30) NOT NULL,
PRIMARY KEY (`uid`)
);
INSERT INTO `users` (`uid`, `username`) VALUES
(1, 'h2ooooooo'),
(2, 'water'),
(3, 'liquid'),
(4, 'wet');
CREATE TABLE `friends` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid_from` int(10) unsigned NOT NULL,
`uid_to` int(10) unsigned NOT NULL,
`status` enum('pending','accepted','blocked') NOT NULL,
PRIMARY KEY (`id`),
KEY `uid_from` (`uid_from`),
KEY `uid_to` (`uid_to`)
);
INSERT INTO `friends` (`id`, `uid_from`, `uid_to`, `status`) VALUES
(1, 1, 3, 'accepted'), -- h2ooooooo sent a friend request to liquid - accepted
(2, 1, 2, 'pending'), -- h2ooooooo sent a friend request to water - pending
(3, 4, 1, 'pending'), -- wet sent a friend request to h2ooooooo - pending
(4, 4, 2, 'pending'), -- wet sent a friend request to water - pending
(5, 3, 4, 'accepted'); -- liquid sent a friend request to wet - accepted
I'd use something like the following:
SELECT
fu.username as `friend_username`,
fu.uid as `friend_uid`
FROM
`users` as `us`
LEFT JOIN
`friends` as `fr`
ON
(fr.uid_from = us.uid OR fr.uid_to = us.uid)
LEFT JOIN
`users` as `fu`
ON
(fu.uid = fr.uid_from OR fu.uid = fr.uid_to)
WHERE
fu.uid != us.uid
AND
fr.status = 'accepted'
AND
us.username = 'liquid'
Result:
friend_username | friend_uid
----------------|-----------
h2ooooooo | 1
wet | 4
Here us would be the user you want to query for friends, and fu would be the users friends. You could easily change the WHERE statement to select the user in whatever whay you want. The status could be changed to pending (and should only join on uid_to) if you want to find friends request that the users hasn't answered.
DEMO ON SQLFIDDLE
The EXPLAIN if we use us.uid to match the user (as it's indexed):
Performance considerations aside, another option might be a "friend" table in which one row represents a friend (does not matter which way around), together with a view which produces two result rows (one in each direction) for any friend row. In use, it would simplify queries because it could be used in the same way as the "two row" solution while only requiring one data row per "friendship".
The only drawback could be performance... depending on how the query optimizer works.
I tried to be creative, here are some results.
Easier drawn than said,
A simple trigger on table friends would do a nice service, ordering (user1,user2) without forgeting who requested friendship.
CREATE TRIGGER `friends_insert` BEFORE INSERT ON friends
FOR EACH ROW BEGIN
DECLARE X INT UNSIGNED;
IF NEW.user1 > NEW.user2 THEN
SET X = NEW.user1;
SET NEW.user1 = NEW.user2;
SET NEW.user2 = X;
SET NEW.invited_by = 1;
END IF;
END$$
Finally, let's say a user U has id = x. We can say U divides table users in two parts: users with id < x and ones with id > x. Before inserting a tuple into table friends, we order its ids, and so a certain information won't be explicitly written twice.
We obtain friends of our user U (id = x) through union of U's friends with id < x and ones with id > x:
SELECT user1 AS `friend_id` FROM friends
WHERE user1<#id AND user2=#id
UNION
SELECT user2 AS `friend_id` FROM friends
WHERE user2>#id AND user1=#id;
The main goal here is query performance. Dividing in these two cases would help MySQL to use the right index for each situation.
[ Time for questions & disagreement. Perhaps you want the complete SQL; it's shown here ]
You could try something like this SQLFiddle: http://sqlfiddle.com/#!2/219dae/3/0
Here is the code:
The SCHEMA:
-- This is the users table:
CREATE TABLE users
(
u_id int auto_increment,
username varchar(20),
PRIMARY KEY (u_id)
);
INSERT INTO users (username)
VALUES ('user1'),
('user2'),
('user3'),
('user4'),
('user5');
-- This is the friends table:
CREATE TABLE friends
(
f_id int auto_increment,
r_name varchar(20), -- the name of the user that requests for friendship
a_name varchar(20), -- the name of the user that answers the friendship request
status varchar(20), -- the status of the request
PRIMARY KEY (f_id)
);
-- below, user1 sends frind requests to user2, user3, user4 and user5; and receives one from user2:
INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user2', 'pending');
INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user3', 'pending');
INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user4', 'pending');
INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user5', 'pending');
INSERT INTO friends (r_name, a_name, status)
VALUES ('user2','user1', 'pending');
-- user1 accepts user2 request to be his friend:
UPDATE friends
SET status='accepted'
WHERE a_name='user1' AND r_name='user2';
-- user3 accepts user1 request to be his friend:
UPDATE friends
SET status='accepted'
WHERE a_name='user3' AND r_name='user1';
and the SELECT:
-- here we select all friend requests that the user1 received and all friend requests that he made
SELECT r_name, a_name, status FROM users
INNER JOIN friends ON users.username=friends.a_name
WHERE username='user1'
UNION
SELECT r_name, a_name, status FROM users
INNER JOIN friends ON users.username=friends.r_name
WHERE username='user1'