Surrogate versus Natural Primary Keys
This is a constant source of argument, flame and general discomfort with any database design. On the PostgreSQL lists it comes up occasionally and it is always a long drawn out thread with people arguing on each side about which one is correct. Before I go on about the good and bad of both, let me define a couple of things. 1. Surrogate keys (also known as artificial keys) are not correct in a correctly normalized design. That doesn't mean they are not useful but if you want a "correctly" designed database, you will not have surrogate keys. 2. Natural keys are always better at representing your data. That doesn't mean they are "easier". Let's assume a very simple structure:
  Table "public.one_nf"
   Column   | Type | Modifiers 
------------+------+-----------
 first_name | text | 
 last_name  | text | 
normal_forms=# SELECT * FROM one_nf ;
 first_name | last_name 
------------+-----------
 Joshua     | Drake
 John       | Worsley
 Joshua     | Drake
(3 rows)
Here we have already violated 1NF by having duplicate values (yes I am fully aware that there are other components to take into account. This is just the simplest example). We have no Primary Key and we have duplicates. Now, let's add the "standard" component amongst all web frameworks to "fix" this problem.
normal_forms=# ALTER TABLE one_nf ADD COLUMN id serial;
NOTICE:  ALTER TABLE will create implicit sequence "one_nf_id_seq" for serial column "one_nf.id"
ALTER TABLE
normal_forms=# \d one_nf
                           Table "public.one_nf"
   Column   |  Type   |                      Modifiers                      
------------+---------+-----------------------------------------------------
 first_name | text    | 
 last_name  | text    | 
 id         | integer | not null default nextval('one_nf_id_seq'::regclass)

normal_forms=# UPDATE one_nf set id = nextval('one_nf_id_seq');
UPDATE 3
                                           ^
normal_forms=# ALTER TABLE one_nf ADD PRIMARY KEY (id);
NOTICE:  ALTER TABLE / ADD PRIMARY KEY will create implicit index "one_nf_pkey" for table "one_nf"
ALTER TABLE
normal_forms=# \d one_nf
                           Table "public.one_nf"
   Column   |  Type   |                      Modifiers                      
------------+---------+-----------------------------------------------------
 first_name | text    | 
 last_name  | text    | 
 id         | integer | not null default nextval('one_nf_id_seq'::regclass)
Indexes:
    "one_nf_pkey" PRIMARY KEY, btree (id)
Great now we have a Primary Key, albeit a Surrogate Key.
normal_forms=# SELECT * FROM one_nf;
 first_name | last_name | id 
------------+-----------+----
 Joshua     | Drake     |  4
 John       | Worsley   |  5
 Joshua     | Drake     |  6
(3 rows)
Now your table is able to work with any number of ORMs. Which of course makes web development a lot easier but, which Joshua Drake is the correct Joshua Drake? Exactly. You don't know and your data representation is broken. O.k. no sweat, we will just add a UNIQUE constraint.
normal_forms=# DELETE FROM one_nf WHERE id = 6;
DELETE 1
normal_forms=# ALTER TABLE one_nf ADD UNIQUE (first_name,last_name);
NOTICE:  ALTER TABLE / ADD UNIQUE will create implicit index "one_nf_first_name_key" for table "one_nf"
ALTER TABLE
normal_forms=# \d one_nf
                           Table "public.one_nf"
   Column   |  Type   |                      Modifiers                      
------------+---------+-----------------------------------------------------
 first_name | text    | 
 last_name  | text    | 
 id         | integer | not null default nextval('one_nf_id_seq'::regclass)
Indexes:
    "one_nf_pkey" PRIMARY KEY, btree (id)
    "one_nf_first_name_key" UNIQUE, btree (first_name, last_name)

normal_forms=# 
Of course I had to delete Joshua Drake, and I am not sure if the Joshua Drake I deleted is the correct one, but that isn't really the point is it? You would think that this enough right? O.k. fair enough, the Surrogate Key allows for very easy queries:
UPDATE one_nf SET first_name = 'Bruce' WHERE id = 1;
Versus:
UPDATE one_nf SET first_name = 'Bruce' WHERE last_name = 'Momjian' AND first_name = 'Burce';
Is the laziness of a few extra characters worth the performance penalty of the extra INDEX maintenance? Random ramblings I guess. A great page on 1NF