I am developing a website using PHP and MySQL where users have options such as:
receive notifications via email
hide/display Facebook/Twitter links
It will also allow users to control privacy of their profile from information viewable to friends/non-friends and what friends are able to view certain photos/albums.
I was wondering how I would design this table to be robust and what things should I put into consideration. How does Facebook manage each users privacy settings and options just out of curiosity?
My solution so far is along the lines of this:
id - Primary Key
member_id - Primary Key and Foreign Key (Member tables 'id')
facebook_viewable - int(1) - 0 for No and 1 for Yes
email_notifications - int(1) - 0 for No and 1 for Yes
First off, you probably don't need to have both id and member_id if they are both going to be primary. Really you need member_id so you can just drop the other id.
To be robust, what you want is to drive settings into rows rather than columns. It is much easier from the database perspective to append rows than to alter the table to add columns.
What you need is a table that contains a list of your privacy rule types and then an intersection table between your member table and your privacy rule type table. The schema would be something like this:
MEMBER
id int not null PRIMARY KEY
, name nvarchar(50)...
, (and so forth)
PRIVACY_RULE
id int not null PRIMARY KEY
, rule_description nvarchar(50)
MEMBER_PRIVACY
member_id int not null
, privacy_rule_id int not null
, rule_value tinyint
, PRIMARY KEY (member_id, privacy_rule_id)
This way, the privacy rule ID becomes a constant in your application code that is used to enforce the actual rule programmatically and the values can be added easily to the intersection table. When you invent a new privacy rule, all you need to do is insert one record into PRIVACY_RULE and then do your application code changes. No database schema changes required.
Note that this same basic structure can be elaborated on to make it much more flexible. For example you could have many different types of values beyond a binary flag and you could control the interpretation of these values with an additional "rule type" attribute on the PRIVACY_RULE table. I don't want to go too far off topic with this so let me know if you want further detail of this aspect.
Related
I have a system which has (for the sake of a simple example) tables consisting of Customers, Vendors, Products, Sales, etc. Total 24 tables at present.
I want to add the ability to have multiple notes for each record in these tables. E.g., Each Customers record could have 0 to many notes, each Vendor record could have 0 to many notes, etc.
My thinking is that I would like to have one "Notes" table, indexed by a Noteid and Date/time stamp. The actual note data would be a varchar(255).
I am looking for a creative way to bi-directionally tie the source tables to the Notes table. The idea of having 24 foreign key type cross reference tables or 24 Notes tables doesn't really grab me.
Programming is being done in PHP with Apache server. Database is mysql/InnoDB.
Open to creative ideas.
Thanks
Ralph
I would sugges a table like this
note_id : int autoincrement primary
type_id : int, foreign key from f Customers, Vendors, Products etc
type : varchar, code indicating the type, like Vendors, VENDORS or just V
note : varchar, the actual node
CREATE TABLE IF NOT EXISTS `notes` (
`note_id` int(11) NOT NULL AUTO_INCREMENT,
`type_id` int(11) NOT NULL,
`type` varchar(20) CHARACTER SET utf8 NOT NULL,
`note` varchar(255) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`note_id`)
)
With a setup like that you can have multiple notes for each type, like Vendors, and also hold notes for multiple types.
data sample
note_id type_id type note
--------------------------------------------------------------------
1 45 Vendors a note
2 45 Vendors another note
3 3 Customers a note for customer #3
4 67 Products a note for product #67
SQL sample
select note from notes where type="Vendors" and type_id=45
To reduce table size, I would prefer aliases for the types, like V, P, C and so on.
Don't do a "universal" table, e.g.
id, source_table, source_record_id, note_text
might sound good in practice, but you can NOT join this table against your others without writing dynamic SQL.
It's far better to simply add a dedicated notes field to every table. This eliminates any need for dynamic sql, and the extra space usage will be minimal if you use varchar/text fields, since those aren't stored in-table anyways.
I've done a structure like this before where I used a format like this:
id (int)
target_type (enum/varchar)
target_id (int)
note (text)
Each data element just has to query for it's own type then, so for your customer object you would query for notes attached to it like this
SELECT * FROM notes where target_type='customer' AND target_id=$this->id
You can also link target_type to the actual class, so that you write to the database using get_class($this) to fill out target type, in which case a single function inside of the Note class could take in any other object type you have.
In my opinion, there isn't a clean solution for this.
option 1: Master entity table
Every (relevant) row of every (relevant) table has a master entry inside a table (let's call it entities_tbl. The ids of each derived table isn't an autoincrement but it's a foreign key referencing the master table.
Now you can easily link the notes table with the master entity id.
PRO: It's an object oriented idea. Like a base "Object" class which is the father of every other class. Also, each entity has an unique id across the database.
CON: It's a mess. Every entity ID is scattered among (at least) two tables. You'd need JOINs every single time, and the master entity table will be HUGE (it will contain the same number of rows as the sum of every other child table, combined)
option 2: meta-attributes
inside the notes table, the primary key would contain an autoincrement, the entity_id and item_table_name. This way you can easily extract the notes of any entity from any table.
PRO: Easy to write, easy to populate
CON: It needs meta-values to extract real values. No foreign keys to grant referential integrity, messy and sloppy joins, table names as where conditions.
option 3: database denormalization
(sigh, I've never considered to ever give this suggestion)
Add a column inside each table where you need notes. Store the notes as json encoded strings. (this means to denormalize a database because you will introduce non-atomic values)
PRO: easy and fast to write, uses some form of standard even for future database users, the notes are centralized and easily accessible from each entity
CON: the database isn't normalized, poor search and comparison between notes
If you use RedBean ORM, do you need to add a primary key named "id" to every table in your database?
In my db I have a few tables that have primary keys pairs of 2 or 3 fields, or primary keys with other names than "id" (yes, I could change the name to "id", but it wouldn't really reflect the reality, because they are not IDs)
Example:
table1 - stores posts:
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
title TEXT,
content TEXT,
table2 - stores meta for posts:
post INTEGER DEFAULT 0, # <- references "id" from "posts"
name TEXT,
value TEXT,
PRIMARY KEY(name, post),
CONSTRAINT posts_meta FOREIGN KEY(post)
REFERENCES posts(id) ON DELETE CASCADE ON UPDATE RESTRICT
Would RedBean work with this kind of db structure?
Unfortunately, with how your current table structure is, you couldn't use RedBean. Every table needs to have an auto-increment primary key. A slight downfall, as it makes integration into an already existing product more difficult.
A couple of threads that failed to use RedBean due to this constraint, with responses from the author, Gabor De Mooij:
http://groups.google.com/group/redbeanorm/browse_thread/thread/6d5582275326744f?pli=1
http://groups.google.com/group/redbeanorm/browse_thread/thread/4fa7b29b453dcdb8
RedBean does NOT require the primary key field to be named simply "id", however. You can format the name of the primary key column to your liking by using the formatBeanID() method, as seen in the example below, which prefixes the table name to the "id" conditionally. eg) table users would have the primary key be users_id. Using that formatting, you can get as detailed with the id name as needed.
http://redbeanphp.com/community/wiki/index.php/Prefixes
Hopefully this restraint will be lifted in the future, since it really does hamper the integration into existing products.
EDIT: As an alternative ORM, I've heard well of Doctrine: http://www.doctrine-project.org/. I haven't personally used it, but it seems to be the standard for many working with PHP.
EDIT 2: Thanks and credit to Jason for bringing to attention a new method for integrating RedBean into an existing project where your database might not be set up for it. I wanted to update my answer as well in case people still reference it with this problem. Gabor suggested making views that map to the tables, where you can set up the view to have the proper structure required for RedBean. I have not personally tested this, but it has gotten positive feedback from some users. It adds some extra overhead and maintenance when altering tables, but seems to be the best and most complete answer to this issue to date.
http://www.redbeanphp.com/faq#beanformatter
The accepted answer is not strictly true... You can use the existing table structure - but you would need to implement a VIEW on top of each of the tables that allow you to rename the PKEY column to be 'id'... See this email from Gabor - the creator of RedBeanPHP:
https://groups.google.com/forum/#!searchin/redbeanorm/view/redbeanorm/wXUeT4Tj2uU/3AngnmVwZdYJ
UPDATE: I've come across this question I did after some years: now I know this is a very bad approach. Please don't use this. You can always use additional tables for i18n (for example products and products_lang), with separate entries for every locale: better for indexes, better for search, etc.
I'm trying to implement i18n in a MySQL/PHP site.
I've read answers stating that "i18n is not part of database normally", which I think is a somewhat narrow-minded approach.
What about product namesd, or, like in my instance, a menu structure and contents stored in the db?
I would like to know what do you think of my approach, taking into account that the languages should be extensible, so I'm trying to avoid the "one column for each language solution".
One solution would be to use a reference (id) for the string to translate and for every translatable column have a table with primary key, string id, language id and translation.
Another solution I thought was to use JSON. So a menu entry in my db would look like:
idmenu label
------ -------------------------------------------
5 {"en":"Homepage", "it":"pagina principale"}
What do you think of this approach?
"One solution would be to use a reference (id) for the string to translate and for every translatable column have a table with primary key, string id, language id and translation."
I implemented it once, what i did was I took the existing database schema, looked for all tables with translatable text columns, and for each such table I created a separate table containing only those text columns, and an additional language id and id to tie it to the "data" row in the original table. So if I had:
create table product (
id int not null primary key
, sku varchar(12) not null
, price decimal(8,2) not null
, name varchar(64) not null
, description text
)
I would create:
create table product_text (
product_id int not null
, language_id int not null
, name varchar(64) not null
, description text
, primary key (product_id, language_id)
, foreign key (product_id) references product(id)
, foreign key (language_id) references language(id)
)
And I would query like so:
SELECT product.id
, COALESCE(product_text.name, product.name) name
, COALESCE(product_text.description, product.description) description
FROM product
LEFT JOIN product_text
ON product.id = product_text.product_id
AND 10 = product_text.language_id
(10 would happen to be the language id which you're interested in right now.)
As you can see the original table retains the text columns - these serve as default in case no translation is available for the current language.
So no need to create a separate table for each text column, just one table for all text columns (per original table)
Like others pointed out, the JSON idea has the problem that it will be pretty impossible to query it, which in turn means being unable to extract only the translation you need at a particular time.
This is not an extension. You loose all advantages of using a relational database. By way like yours you may use serialize() for much better performance of decoding and store data even in files. There is no especial meen to use SQL with such structures.
I think no problem to use columns for all languages. That's even easier in programming of CMS. A relational database is not only for storing data. It is for rational working with data (e.g. using powerful built-in mechanisms) and controlling the structure and integrity of data.
first thought: this would obviously brake exact searching in sql WHERE label='Homepage'
second: user while search would be able to see not needed results (when e.g. his query was find in other languge string)
I would recommend keeping a single primary language in the database and using an extra sub-system to maintain the translations. This is the standard approach for web applications like Drupal. Most likely in the domain of your software/application there will be a single translation for each primary language string, so you don't hav to worry about context or ambiguity. (In fact for best user experience you should strive to have unique labels for unique functionality anyway).
If you want to roll your own table, you could have something like:
create table translations (
id int not null primary key
, source varchar(255) not null // the text in the primary language
, lang varchar(5) not null // the language of the translation
, translation varchar(255) not null // the text of the translation
)
You probably want more than 2 characters for language since you'll likely want en_US, en_UK etc.
I'm in a fix here. I'm building a home reservation site and the client requires a filter search facility to allow visitors to search and filter through properties based upon criteria. The thing is that his list of criteria is exceedingly long, largely boolean values and calculable values stuff like:
Attached bathroom, balcony, smoking, alcoholic, maximum number of occupants, cable TV, Internet, carpeted, airconditioning, central heating, room service, etc., etc., etc...
I'm thinking of having to create a field for each of these, but there's a very strong chance that the number of preferences might even go up. I dished out the idea of storing everying in a serialised object as a string as then it would be impossible to search using an SQL query. Do I have any options apart from setting up individual fields for each preference here?
Thanks. I'm using PHP MySQL.
Could you have a many-to-many table that ties the ID of the property to a reference table that has Attributes? The lookup table would contain a row for every attribute that the property has?
Main Table
ID PropertyName OtherCols
10 CottageA Stuff
20 CottageB OtherStuff
Attributes
100 Smoking
200 AirConditioning
300 Room Service
400 TV
500 Internet
Lookup Table
ID AttributeID
10 100
10 200
10 300
20 100
20 400
20 500
I did exactly the same search a couple years back for a hotel catalogue. We used a BitMask for that, e.g. we stored a single number representing all the possible values, e.g.
HotelTable
ID Name … Features
1 SuperHotel … 5
Features
ID Name
1 Balcony
2 Shower
4 Barrier-Free
8 Whatever
… …
In the example above SuperHotel would have a Balcony and be Barrier-Free (4+1).
While this worked well, I am not sure I'd handle it this way again. Basically, all these features are the same as tags, so you could just as well use the known approaches to create a tagging table.
I would suggest creating a field for each criteria. This is going to allow you to have a the fastest search capability. With that being said you could always create a TEXT or a MEDIUMTEXT and store JSON in that field. So you could use json_encode on an array such as:
$amenities['bathroom'] = 1;
$amenities['balcony'] = 1;
$amenities['smoking'] = 0;
Then if you were looking for a home that had a balcony you could do:
SELECT * FROM `homes` WHERE `json_field` LIKE '%balcony: 1%'
And instead of a LIKE you could always use FULLTEXT searches if you have that capability on your server.
You could do a mapping like suggested before but include a value in the mapping
Hotel Table
create table hotel_table (
id int(4) unsigned not null auto_increment primary key,
hotel_name varchar(40) not null,
...other row info
);
Hotel Criteria
create table hotel_criteria (
id int(4) unsigned not null auto_increment primary key,
criteria_name varchar(40) not null
);
Hotel Criteria Map
create table hotel_criteria_map (
id int(4) unsigned not null auto_increment primary key,
hotel_id int(4) unsigned not null,
criteria_id int(4) unsigned not null,
string_data varchar(20) null, #use this to add in extra string information for criteria
decimal_data decimal(6,2) null, #use this to add in extra decimal information for criteria
#... either of the above or other extra info, just giving examples ...
unique key (hotel_id,criteria_id),
foreign key (hotel_id) references hotel_table(id),
foreign key (criteria_id) references hotel_criteria(id)
);
You could then select those values:
select * from hotel_table where id={your hotel id}; #hotel info
select m.*,c.criteria_name from hotel_criteria_map m, hotel_criteria c where m.criteria_id=c.id and hotel_id={your hotel id}; #criteria info
There may be a better way to do this but just a suggestion. You would only enter the criteria for a certain hotel into the map if it pertained to that certain hotel (essentially not having any criteria map rows that would be bool 0).
You should read up on database normalization. IMHO the goal is to structure your tables in such a way that allow these queries to be run efficiently and with less logic on the php side. For example, if each of the above mentioned search criteria have a value associated with them, the values should be stored in a separate table, and if there are many options to many values, you will need to setup a different type of relationship. This initial time looking at the database from all angles will save you a lot of make-shift php code and possible prevent an entire rebuild of the database once you realized you've bottlenecked. Hope this was at all helpful. Good luck!
I'm thinking of this, if I make a web site for a specific university's students should I make the ID as standard IDs on MySQL (integer, auto increment) or should I give the IDs as how is their email address is, like if the student's email address is e12234#university.edu then his/her id is e12234 (on my web site). Is it ok, what about performance?
Edit: Also, there are such email addresses:
n13345#university.edu (exchange student)
jennajameson#university.edu (this is a professor)
I would strongly recommend a separate, independent value for the id (integer, auto increment). Id values should never change, never be updated. People change their emails all the time, corporations reissue the same email address to new users sometimes.
If an emailaddress is unique and static in your population (and make very sure it is), you may make it a primary key, and actually a full normalization would favor that option. There are however some pitfalls to consider:
People change emailaddresses once in while. What if a student becomes a professor, or is harassed on his/hers emailaddress so he/she applied for a new address and got one? The primary key shold not change, ever, so there goes your schema.
Sanitizing emailaddresses takes a little bit more effort then integers.
Depending on how many foreign keys point to this ID, needed storage space could be increased, and joining on CHARs rather then INTs could suffer in performance (you should test that though)
Generally you'd want to map strings to ids and reference the ID eveywhere
CREATE TABLE `student` (
`id` int unsigned NOT NULL auto_increment,
`email` varchar(150) NOT NULL
PRIMARY KEY (`id`)
)
This will reduce the size of any table reference the email table as it will be using an INT instead of a VARCHAR.
Also if you used part of their email and the user ever changed their email you'd have to go back through every table and update their ID.