5 ms·
It's worth noting that this model works quite well for the vast majority of client facing applications out there. I.E. Things categorized by tags or groups whe
by eksith 12y ago
It's worth noting that this model works quite well for the vast majority of client facing applications out there.
I.E. Things categorized by tags or groups where 1-to-n relationships are necessary. Of course, the extreme of this is EAV (Entity Attribute Value) which I would limit to meta/taxonomy data for performance reasons.
But for information where the labels (or quantity) aren't known before input are easier to deal with in normalized databases.
E.G. Getting metadata on a specific entry with no prior knowlege of said data except parent id:
SELECT id, label, content FROM meta WHERE id IN (
SELECT meta_id FROM posts_meta WHERE post_id = :id
);
Or if you're using Postgres, you can return the metadata as an associative array. E.G. http://stackoverflow.com/a/11942726 http://stackoverflow.com/a/11942726
The following is an excerpt from the actual schema I used on a very simple forum that ran on SQLite for years before switching to Postgres fairly recently. The normalization (varying forms) afforded a lot of flexibility. Maybe someone will find it useful.
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT NULL,
title VARCHAR NULL,
summary TEXT NOT NULL,
body TEXT NOT NULL,
plain TEXT NOT NULL,
quality FLOAT NOT NULL DEFAULT 0,
status INTEGER NOT NULL DEFAULT 0,
reply_count INTEGER NOT NULL DEFAULT 0,
auth_key VARCHAR NOT NULL
);
CREATE INDEX idx_posts_on_status ON posts ( status );
CREATE INDEX idx_posts_on_created_at ON posts ( created_at );
CREATE VIRTUAL TABLE posts_search USING fts4 ( search_data );
CREATE TABLE posts_family (
parent_id INTEGER NOT NULL,
child_id INTEGER NOT NULL,
PRIMARY KEY ( child_id, parent_id )
);
CREATE TABLE taxonomy (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
label VARCHAR NOT NULL,
term VARCHAR NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT NULL,
status INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE posts_taxonomy (
post_id INTEGER NOT NULL,
taxonomy_id INTEGER NOT NULL,
PRIMARY KEY ( post_id, taxonomy_id )
);
CREATE TABLE taxonomy_family (
parent_id INTEGER NOT NULL,
child_id INTEGER NOT NULL,
PRIMARY KEY ( child_id, parent_id )
);
CREATE TABLE meta (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
label VARCHAR NOT NULL,
parse_as VARCHAR NOT NULL DEFAULT "text",
content TEXT NOT NULL
);
CREATE TABLE posts_meta (
post_id INTEGER NOT NULL,
meta_id INTEGER NOT NULL,
PRIMARY KEY ( post_id, meta_id )
);
CREATE UNIQUE INDEX idx_taxonomy_on_terms ON taxonomy ( label ASC, term ASC );
CREATE INDEX idx_taxonomy_on_status ON taxonomy ( status );
-- Triggers
-- Post create procedures
CREATE TRIGGER post_after_insert AFTER INSERT ON posts FOR EACH ROW
BEGIN
INSERT INTO posts_search ( docid, search_data )
VALUES ( NEW.rowid, NEW.plain );
UPDATE posts SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.rowid;
END;
-- Post update procedures
CREATE TRIGGER post_before_update BEFORE UPDATE ON posts FOR EACH ROW
BEGIN
DELETE FROM posts_search WHERE docid = OLD.rowid;
END;
CREATE TRIGGER post_after_update AFTER UPDATE ON posts FOR EACH ROW
BEGIN
INSERT INTO posts_search ( docid, search_data )
VALUES ( NEW.rowid, NEW.plain );
UPDATE posts SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.rowid;
END;
-- Post deletion procedure
CREATE TRIGGER post_before_delete BEFORE DELETE ON posts FOR EACH ROW
BEGIN
UPDATE posts SET reply_count = ( reply_count - 1 )
WHERE id != OLD.rowid AND id IN (
SELECT parent_id FROM posts_family WHERE child_id = OLD.rowid
);
DELETE FROM posts_family WHERE parent_id = OLD.rowid OR child_id = OLD.rowid;
DELETE FROM posts_search WHERE docid = OLD.rowid;
DELETE FROM posts_taxonomy WHERE post_id = OLD.rowid;
DELETE FROM meta WHERE id IN (
SELECT meta_id FROM posts_meta WHERE post_id = OLD.rowid
);
DELETE FROM posts_meta WHERE post_id = OLD.rowid;
END;
-- Post parent insert procedures
CREATE TRIGGER posts_family_after_insert AFTER INSERT ON posts_family FOR EACH ROW
BEGIN
UPDATE posts SET reply_count = ( reply_count + 1 ) WHERE id IN (
SELECT parent_id FROM posts_family WHERE child_id = NEW.rowid
);
END;
-- Taxonomy procedures
CREATE TRIGGER taxonomy_after_insert AFTER INSERT ON taxonomy FOR EACH ROW
BEGIN
UPDATE taxonomy SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.rowid;
END;
CREATE TRIGGER taxonomy_after_update AFTER UPDATE ON taxonomy FOR EACH ROW
BEGIN
UPDATE taxonomy SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.rowid;
END;
CREATE TRIGGER taxonomy_before_delete BEFORE DELETE ON taxonomy FOR EACH ROW
BEGIN
DELETE FROM posts_taxonomy WHERE taxonomy_id = OLD.rowid;
DELETE FROM taxonomy_family WHERE parent_id = OLD.rowid OR child_id = OLD.rowid;
END;
- yangyang 12y ago> Or if you're using Postgres, you can return the metadata as an associative array. E.G. http://stackoverflow.com/a/11942726 http://stackoverflow.com/a/11942726 That's not an associative array, it's just an array of a composite type. The hstore type is probably the closest you'd get to an associate array in PostgreSQL, but the keys and values are always strings.
- jimktrains2 12y agoIt's returning a record, which is more of a struct than an array.
- yangyang 12y agoCREATE OR REPLACE FUNCTION get_people() RETURNS person[] LANGUAGE sql AS person is a composite type, defined further up. The square brackets indicate an array of that type.
- jimktrains2 12y agoI misread you at first.
- dragonwriter 12y ago> The hstore type is probably the closest you'd get to an associate array in PostgreSQL No, a table (possibly a temp table with the results of a particular query) with an appropriately defined primary key (or other unique index) would be the closest you can get to an associative array -- since a relation with n candidate keys is exactly identical to n different associative arrays with the same data but different key/value splits. But an array of a composite type drawn in such a way that some set of columns is guaranteed unique is pretty much the same thing from a data perspective, even though you may need to do work on the client side to load it into a structure that supports associative array operations (e.g., efficient key/value lookups).
- 12y ago