MySQL Structure for a social network - php

I'm experimenting by making a social network from scratch in PHP/MySQL, but I'm having trouble thinking of the optimal MySQL structure for it, currently I have:
This is a table which stores all user info:
fname varchar (300),
sname varchar (300),
pass varchar (400),
email varchar (300),
gender varchar (300),
dob varchar (200),
uid varchar (300),
PRIMARY KEY (id)
This is created when a user signs up, their own personal table:
id int(20) NOT NULL auto_increment,
uid varchar (300),
photo_url varchar (400),
pfid varchar (300),
phototime datetime,
video_url varchar (400),
vfid varchar (300),
videotime datetime,
status longtext,
sid varchar (300),
statustime datetime,
blog longtext,
bid varchar (300),
blogtime datetime,
about_bio longtext,
about_current_job longtext,
about_secondary_school longtext,
about_primary_school longtext,
about_college longtext,
about_university longtext,
about_workemail longtext,
about_homeemail longtext,
about_phonenumber longtext,
about_relationshipstatus longtext,
about_relationshipwith longtext,
PRIMARY KEY (id)
)";
The sessions table to track whether someone is logged in or not:
id int(20) NOT NULL auto_increment,
sid varchar(300),
uid varchar(300),
PRIMARY KEY (id)
Haven't gotten onto relationships yet but I was thinking:
id int(20) NOT NULL auto_increment,
requestby varchar(200),
requestto varchar(200),
status varchar(200)
(Before anyone asks, this is purely just for the learning experience, nothing more)

Well, you definitely shouldn't have one table per user. I think a database structure more like this would work really well:
CREATE TABLE users (
userID INT NOT NULL AUTO_INCREMENT,
firstName VARCHAR(30),
lastName VARCHAR(30),
password CHAR(32), -- should be encrypted, CHAR is better if the field is always the same length
email VARCHAR(64) NOT NULL, -- not null if this is what you will use as a "username"
PRIMARY KEY (userID)
);
CREATE TABLE personalInfo (
userID INT NOT NULL,
gender ENUM ('MALE', 'FEMALE'),
dateOfBirth DATE,
phoneNumber VARCHAR(15),
personalEmail VARCHAR(64), -- may or may not be the same as the email field in the "users" table
workEmail VARCHAR(64),
bio TEXT,
FOREIGN KEY (userID) REFERENCES users (userID)
);
/* this table is not specific to any single user. It is just a list of jobs that have been created */
CREATE TABLE jobs (
jobID INT NOT NULL AUTO_INCREMENT,
company VARCHAR(100),
title VARCHAR(100),
description TEXT,
PRIMARY KEY (jobID)
);
/* the workInfo table will hold one entry per user per job. So if a user has held five jobs,
there will be five rows with that userID in this table, each with a different jobID, which
refers to an entry in the "jobs" table above. */
CREATE TABLE workInfo (
userID INT NOT NULL,
jobID INT NOT NULL,
startDate DATE,
endDate DATE, -- can set this to null if it's the user's current job
FOREIGN KEY (userID) REFERENCES users (userID),
FOREIGN KEY (jobID) REFERENCES jobs (jobID)
);
CREATE TABLE schools (
schoolID INT NOT NULL AUTO_INCREMENT,
schoolName VARCHAR(100),
-- any other information you want to provide about the school (city, address, phone, etc)
PRIMARY KEY (schoolID)
);
CREATE TABLE schoolPrograms (
programID INT NOT NULL AUTO_INCREMENT,
programName VARCHAR(100),
-- any other information you want to provide about the program (department, teachers, etc)
PRIMARY KEY (programID)
);
CREATE TABLE educationInfo (
userID INT NOT NULL,
schoolID INT,
programID INT,
startDate DATE,
endDate DATE,
FOREIGN KEY (userID) REFERENCES users (userID),
FOREIGN KEY (schoolID) REFERENCES schools (schoolID),
FOREIGN KEY (programID) REFERENCES schoolPrograms (programID)
);
CREATE TABLE relationships (
userID INT NOT NULL,
userID2 INT, -- allowed to be null if the user is single or does not specify who they are in a relationship with
status ENUM ('SINGLE', 'IN A RELATIONSHIP', 'MARRIED', 'IT''S COMPLICATED' /* etc */),
FOREIGN KEY (userID) REFERENCES users (userID)
);
/* each photo is created here. This way, when a user wants to share a photo,
we don't have to duplicate each column. We just create another row in
the "userPhotos" table below that) REFERENCES the same photoID. */
CREATE TABLE photos (
photoID INT NOT NULL AUTO_INCREMENT,
url VARCHAR(200),
caption VARCHAR(200),
dateOfUpload TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (photoID)
);
CREATE TABLE userPhotos (
userID INT NOT NULL,
photoID INT NOT NULL,
FOREIGN KEY (userID) REFERENCES users (userID),
FOREIGN KEY (photoID) REFERENCES photos (photoID)
);
/* vidoes, handled exactly the same as photos */
CREATE TABLE videos (
videoID INT NOT NULL AUTO_INCREMENT,
url VARCHAR(200),
caption VARCHAR(200),
dateOfUpload TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (videoID)
);
CREATE TABLE userVideos (
userID INT NOT NULL,
videoID INT NOT NULL,
FOREIGN KEY (userID) REFERENCES users (userID),
FOREIGN KEY (videoID) REFERENCES videos (videoID)
);
CREATE TABLE status (
userID INT NOT NULL,
status TEXT,
FOREIGN KEY (userID) REFERENCES users (userID)
);

Don't use large varchars for all those fields. Friendship status can be just an int if you keep a lookup table (or a list in your code) that explains each value.
If the user table has an auto incrementing ID, you could use that ID for foreign key relationships. Even if you don't want UID to be an integer, you could still make it a GUID or something else that is much, much smaller than a varchar.
These tables only specify a profile and maybe a relationship, but there is so much more. Even something as simple as Twitter has a table of tweets, lists, accounts to put in a list, users that follow a list, direct messages (although those could theoretically be in the same table as Tweets), linked apps, blocked users and much, much more.
So I think first of all, you should think about what your social network should be, what it should look like, what features should it have. Then, strip that down to only the most essential features. Then, strip it down a little more, you're still thinking too big. ;)
When you got clear what your minimum desirement are, it will probably be much clearer to you what table you would need.
Don't forget to add constraints and indexes!
Note that in practice, Twitter, Facebook and the other large networks don't use MySQL at all, but to practice, MySQL is fine.

Related

Referencing multiple tables in mysql

How do I reference multiple tables for one table ?
In the assignment, I have to create 5 tables with each table having INT id:
Owners, homes, home_owners, installation, house_type.
Owners can have multiple homes and homes can have multiple owners.
Home needs to have installations and house_type(both need to be VARCHAR type).
I wrote this to sql but it returns an
error: errno: 150 "Foreign key constraint is incorrectly formed"
CREATE TABLE owners (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE homes (
id INT AUTO_INCREMENT PRIMARY KEY,
created DATETIME,
modified DATETIME,
home_types_group VARCHAR(255) NOT NULL
);
CREATE TABLE home_owners (
id INT AUTO_INCREMENT PRIMARY KEY,
created DATETIME,
modified DATETIME,
owners_num INT NOT NULL,
FOREIGN KEY owners_num_key (owners_num) REFERENCES owners(id)
);
CREATE TABLE installation (
id INT AUTO_INCREMENT PRIMARY KEY,
brands VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME,
FOREIGN KEY brands_key (brands) REFERENCES homes(home_types_group)
);
CREATE TABLE types (
id INT AUTO_INCREMENT PRIMARY KEY,
types VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME,
FOREIGN KEY brands_key (types) REFERENCES homes(home_types_group)
);
The problem is here:
CREATE TABLE installation ( id INT AUTO_INCREMENT PRIMARY KEY, brands VARCHAR(255) NOT NULL, created DATETIME, modified DATETIME, FOREIGN KEY brands_key (brands) REFERENCES homes(home_types_group) )
REFERENCES homes(home_types_group) : this is wrong, you can only reference a primary or unique column of some table for making foreign key. But home_types_group column is neither primary nor unique. Change it to id clumn of homes table
Try this
CREATE TABLE installation ( id INT AUTO_INCREMENT PRIMARY KEY, brands
int, created DATETIME, modified DATETIME, FOREIGN KEY fk_id(brands)
REFERENCES homes(id) );
CREATE TABLE types ( id INT AUTO_INCREMENT PRIMARY KEY, types int,
created DATETIME, modified DATETIME, FOREIGN KEY brands_key (types)
REFERENCES homes(id) );
You can create a foreign key by defining a FOREIGN KEY constraint when you create or modify a table. In a foreign key reference, a link is created between two tables when the column or columns that hold the primary key value for one table are referenced by the column or columns in another table
Here REFERENCES homes(home_types_group) is wrong so you should change it to homes(id), you can only reference a primary or unique column of some table for making foreign key.and both keys data type should be same that is
brands VARCHAR(255) NOT NULL
types VARCHAR(255) NOT NULL
are wrong it should be int

Sql Insert into with duplicate key

Ok so, im still a beginner in databases. i have this code
$sql="INSERT INTO complaints_members(password, complaint) VALUES ('$mypassword','$submit') ON DUPLICATE KEY UPDATE complaint='$submit' ; ";
This simply updates my complaint in the existing entry. How can i insert a new entry with the same key, instead of updating the old one?
Im thinking of it like this.
1st entry is like
Password : 123
Complaint : abc
2nd would be like
Password : 123
Complaint : def
Im not very familiar with the terms of SQL, so i'm sorry in advance, and thanks for your time.
EDIT: This is how the tables are
You can't have duplicate primary keys in a database. This is intentional.
Instead, consider re-designing your database so that each complaint has a unique ID (AKA a Primary Key). You should set it as something like complaint_id or simply id, and make it a PK (Primary Key) and AI (Auto-Increment). That way, when you do inserts you won't have to worry about updating the same row.
One option is to make password and complaint a composite primary key
For future reference when someone asks for your table structure its better to post the text from SHOW CREATE TABLE table_name instead of an image from a visual editor.
That said the problem is that your primary key is the password field. You need to add primary keys to both tabled than can be uniquely identified and then you need to link them.
Your table structure should be more like this:
CREATE TABLE `register` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`number` INTEGER(255),
`address` varchar(255),
PRIMARY KEY (`id`),
-- I assume that email and username should always be unique here
UNIQUE KEY (`email`)
UNIQUE KEY (`username`)
);
CREATE TABLE `complaints_members`
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`complaint` VARCHAR(255),
`password` varchar(255),
`member_id` INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY (`member_id`),
CONSTRAINT `complaints_members_register` FOREIGN KEY (`member_id`) REFERENCES `register` (`id`) ON DELETE CASCADE
);
So now to create a new member complaint your SQL would look like
INSERT INTO complaints_members(member_id, password, complaint) VALUES (?, ?, ?)
And to get all complaints for a member:
SELECT c.*
FROM compalaints_members c
WHERE c.member_id = ?

Should mailing addresses be normalized in SQL?

Should mailing addresses with city, state, and zip code be normalized? I am currently concerned with US addresses only.I have shown a normalized tables along with an ERD, and a non-normalized table at the bottom of this post. Please provide rational for your answer.
Note that To Normalize or Not To Normalize is related to this topic, but is different.
Thank you
CREATE TABLE IF NOT EXISTS states (
id CHAR(2) NOT NULL ,
name VARCHAR(45) NULL DEFAULT NULL ,
PRIMARY KEY (id) ,
INDEX states_name (name ASC) )
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS cities (
id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
name VARCHAR(45) NOT NULL ,
states_id CHAR(2) NOT NULL ,
PRIMARY KEY (id) ,
INDEX fk_zipcodes_states1_idx (states_id ASC) ,
UNIQUE INDEX makeUnique (states_id ASC, name ASC) ,
INDEX cities_name (name ASC) ,
CONSTRAINT fk_zipcodes_states1
FOREIGN KEY (states_id )
REFERENCES states (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
PACK_KEYS = 0
ROW_FORMAT = DEFAULT;
CREATE TABLE IF NOT EXISTS zipcode_types (
id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
name VARCHAR(45) NULL DEFAULT NULL ,
PRIMARY KEY (id) )
ENGINE = InnoDB
PACK_KEYS = 0
ROW_FORMAT = DEFAULT;
CREATE TABLE IF NOT EXISTS counties (
id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
name VARCHAR(45) NOT NULL ,
PRIMARY KEY (id) ,
INDEX counties_name (name ASC) )
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS timezones (
id CHAR(4) NOT NULL ,
name VARCHAR(45) NOT NULL ,
PRIMARY KEY (id) )
ENGINE = InnoDB
PACK_KEYS = 0
ROW_FORMAT = DEFAULT;
CREATE TABLE IF NOT EXISTS zipcodes (
id CHAR(5) NOT NULL ,
longitude DECIMAL(9,6) NOT NULL ,
latitude DECIMAL(9,6) NOT NULL ,
zipcode_types_id INT UNSIGNED NOT NULL ,
counties_id INT UNSIGNED NOT NULL ,
timezones_id CHAR(4) NOT NULL ,
PRIMARY KEY (id) ,
INDEX fk_zipcodes_zipcode_types1_idx (zipcode_types_id ASC) ,
INDEX fk_zipcodes_counties1_idx (counties_id ASC) ,
INDEX fk_zipcodes_timezones1_idx (timezones_id ASC) ,
CONSTRAINT fk_zipcodes_zipcode_types1
FOREIGN KEY (zipcode_types_id )
REFERENCES zipcode_types (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_zipcodes_counties1
FOREIGN KEY (counties_id )
REFERENCES counties (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_zipcodes_timezones1
FOREIGN KEY (timezones_id )
REFERENCES timezones (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS cities_has_zipcodes (
cities_id INT UNSIGNED NOT NULL ,
zipcodes_id CHAR(5) NOT NULL ,
PRIMARY KEY (cities_id, zipcodes_id) ,
INDEX fk_cities_has_zipcodes_zipcodes1_idx (zipcodes_id ASC) ,
INDEX fk_cities_has_zipcodes_cities1_idx (cities_id ASC) ,
CONSTRAINT fk_cities_has_zipcodes_cities1
FOREIGN KEY (cities_id )
REFERENCES cities (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_cities_has_zipcodes_zipcodes1
FOREIGN KEY (zipcodes_id )
REFERENCES zipcodes (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS someRecord (
id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
data VARCHAR(45) NULL ,
address VARCHAR(45) NULL ,
cities_id INT UNSIGNED NOT NULL ,
zipcodes_id CHAR(5) NOT NULL ,
PRIMARY KEY (id) ,
INDEX fk_someRecord_cities1_idx (cities_id ASC) ,
INDEX fk_someRecord_zipcodes1_idx (zipcodes_id ASC) ,
CONSTRAINT fk_someRecord_cities1
FOREIGN KEY (cities_id )
REFERENCES cities (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_someRecord_zipcodes1
FOREIGN KEY (zipcodes_id )
REFERENCES zipcodes (id )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Example of data in a single table
CREATE TABLE IF NOT EXISTS otherRecord (
id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
data VARCHAR(45) NULL ,
address VARCHAR(45) NULL ,
city VARCHAR(45) NULL ,
state VARCHAR(45) NULL ,
zipcode VARCHAR(45) NULL ,
county VARCHAR(45) NULL ,
longitude DECIMAL(9,6) NULL ,
latitude DECIMAL(9,6) NULL ,
timezone VARCHAR(45) NULL ,
PRIMARY KEY (id) )
ENGINE = InnoDB;
Yes, if:
You will be analyzing your data with respect to their addresses, and by that I mean sorting, filtering, grouping, counting based on the various fields of an address.
If you allow free text, then you might have country names like US, USA, U.S.A., United States. This will be a pain if you want to view/count/group all American customers. Your internal users might want to drill down from continent to country to state to county to city, in which case your data needs to be normalized.
You will be doing matching against external sources. For example, you have data from a 3rd party vendor, and you need to match their Company A and your Company A. Oftentimes companies have similar names, and you need to match by (parts of) the address. For example, you need to match "Acme, Inc | California" with "Acme Incorporated | CA".
You want to truly avoid duplication. If you allow free text, then you would have a duplicate with "123-456 Main Street, Vancouver" and "Apt 123, 456 Main Street, Vancouver"
You want truly valid data. If you allow free text, then anyone can type in anything. This one is tough, as you'll need lots of reference data with available country names, state names, county names, even street names. You could start with getting some data from geonames.org .
Please note, Ireland does not use Postal Codes, so your schema needs to account for that if going global. Read Hay's Enterprise Model Patterns for some good Address models.
Addresses are not a cleanly relational entity. You should not normalize them in the traditional sense. What you may want to do is additionally store a normalized version of parts of the address (e.g. country, state, city) for your own analysis purposes, which is derived from the address provided by the user.
There are a tremendous number of exceptions in just US addresses, which are pretty well normalized compared to the rest of the world. Zip codes, by the way, correspond primarily to delivery routes by the USPS, not to specific physical locations.
As a personal example, I live in an unincorporated area which is served by a post office in a different (nearby) city, which is in a different county. My official address should, according to USPS, be written as "VC Highlands, NV 89521" and is in Storey County, NV. However the Zip code 89521 is primarily in "Reno, NV 89521" and is in Washoe County, NV. You can imagine that this causes is much trouble with just about everyone. Even the Nevada DMV refuses to accept "VC Highlands" because their database thinks 89521 is "Reno".
So even just with something "simple" in your above schema, you've got it wrong. A zip code can not only span multiple cities, but multiple counties. There are thousands more exceptions which will certainly frustrate some percentage of your users.

Polymorphic Associations or something easier

This is my first post and I can't seem to find the answer anywhere....
I have a database that has multiple companies,each company has multiple locations.
I'm running into problems trying to define the contacts. Some contacts need to be global and available
at any location....some contacts only need to exist for one location. In the contact_info table below
we specify the visibility of the contact (company or location). However the location needs to choose its primary contact.
That leaves a FK from contact -> location and from location -> contact.
I know there is another table involved but I can't seem to conceptualize it.
CREATE TABLE `company_info` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`));
CREATE TABLE `location_info` (
`id` INT NOT NULL AUTO_INCREMENT,
`company_info` INT NOT NULL DEFAULT -1,
`name` VARCHAR(100) NOT NULL DEFAULT '',
`primary_contact_id` INT NOT NULL DEFAULT -1,
PRIMARY KEY(`id`),
UNIQUE KEY(`company_id`,`name`),
FOREIGN KEY (company_id) REFERENCES company_info(id)
FOREIGN KEY (primary_contact_id) REFERENCES contact_info(id));
CREATE TABLE `contact_info` (
`id` INT NOT NULL AUTO_INCREMENT,
`company_id` INT
`location_id` INT,
`type` ENUM('Company','Location') NOT NULL DEFAULT 'Company',
`first_name` VARCHAR(50) NOT NULL DEFAULT '',
`last_name` VARCHAR(50) NOT NULL DEFAULT '',
PRIMARY KEY(`id`),
UNIQUE KEY(`id`,`company_id`,`location_id`),
FOREIGN KEY (location_id) REFERENCES location_info(id),
FOREIGN KEY (company_id) REFERENCES company_info(id)
The most effective way would be splitting it up so that there's a table for your companies, a table with your users, and a table solely for the purpose of storing all connections (i.e. EntryID, UserID, CompanyID). This way you'll be able to easily load them afterwards.

problem with mysql query

i have a table called users
this what the table look like
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(60) NOT NULL,
`email` varchar(60) NOT NULL,
PRIMARY KEY (`id`)
)
and finally i have a table called friends,
this what the table look like
CREATE TABLE `friends` (
`friendship_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id1` bigint(20) unsigned NOT NULL,
`user_id2` bigint(20) unsigned NOT NULL,
`time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`friendship_id`),
KEY `user_id1` (`user_id1`),
KEY `user_id2` (`user_id2`),
CONSTRAINT `friends_ibfk_1` FOREIGN KEY (`user_id1`) REFERENCES `users` (`id`),
CONSTRAINT `friends_ibfk_2` FOREIGN KEY (`user_id2`) REFERENCES `users` (`id`)
)
so basically if userA is following userB , then a row will be added to the friends table, with the attribute user_id1 is userA and user_id2 is userB.
im trying to write a mysql query for a searchbox. the user will enter a string and the query will crawl a list of users that include that string but the people that the user is following need to be displayed first.
so if we have 3 users
jack
jason
john
if the user Chris (who's following jason) enters in the searchbox the string 'ja', the query will crawl the list of users with the following order jason,jack. since jason is followed by chris.
from my understanding , i think it might a group by problem, i tried different queries but i couldnt get the needed results
do you guys have any idea ?
thanks a lot
You have to do a trick for sorting, so friendships get a 0 and non-friendships get a 1 in a temporary field and then we sort ascending for this field and as second we sort by username
SELECT x.username
FROM users x LEFT JOIN friends y ON x.id=y.user_id2 AND y.user_id1=$LOGGED_IN_USER
WHERE LOWER(x.username) LIKE 'ja%'
ORDER BY CASE WHEN y.user_id2 IS NULL THEN 1 ELSE 0 END,x.username
#thanks to scwagner for pointing me to extend JOIN-clause

Categories