2. Table Partitioning
is a technique for physically dividing
the data by smaller physical pieces
for best query perfomance.
3. Suppose we have
And we have many bandits.
Gang Bandit Crime
has_many :bandits has_many :crimes
belongs_to :gang belongs_to :bandit
specialization
4. Implementing
1. Create master table. In our case this will be the ‘bandits’ table.
2. Create ‘child’ tables.
3. Create condition for data partitioning.
5. Step 1. Create procedure
CREATE OR REPLACE FUNCTION bandits_insert_master() RETURNS TRIGGER AS $$
DECLARE
colname text := ‘specialization’;
colval text := NEW.specialization;
tblname text := 'bandits_' || colval;
BEGIN
IF NOT EXISTS(SELECT relname FROM pg_class WHERE relname=tblname) THEN
EXECUTE 'CREATE TABLE ' || tblname || '(check (' || quote_ident(colname) || '=' || quote_literal(colval) || ')) INHERITS (' || TG_RELNAME || ');';
END IF;
EXECUTE 'INSERT INTO ' || tblname || ' SELECT ($1).*' USING NEW;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
6. Step 2. Create before insert trigger
CREATE TRIGGER bandits_insert_trigger
BEFORE INSERT ON bandits
FOR EACH ROW EXECUTE PROCEDURE bandits_insert_master();
7. It's worked!
INSERT INTO bandits (name, specifiaction) VALUES (‘Al Capone’, ‘bootlegger’);
SELECT COUNT(*) FROM ONLY bandints;
Before
> 1 row
After
> 0 rows
SELECT COUNT(*) FROM bandints; SELECT COUNT(*) FROM bandints_botlegger;
> 1 row
> 1 row
9. Why?
EXECUTE 'INSERT INTO ' || tblname || ' SELECT ($1).*' USING NEW;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Change to
EXECUTE 'INSERT INTO ' || tblname || ' SELECT ($1).*' USING NEW;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
10. D'oh!
Bandit.create!(name: ‘Al Capone’, specialization: ‘bootlegger’)
=> #<Bandit id: 1, name: "Al Capone", specialization: "bootlegger" …
But...
SELECT COUNT(*) FROM ONLY bandints;
> 1 row
we have duplication of records in the tables ‘bandits’ and ‘bandits_bootlegger’
11. Fix
CREATE OR REPLACE FUNCTION bandits_delete_master() RETURNS TRIGGER AS $$
DECLARE
row bandits%rowtype;
BEGIN
DELETE FROM ONLY bandits WHERE id = NEW.id RETURNING * INTO row;
RETURN row;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS bandits_after_insert_trigger ON bandits;
CREATE TRIGGER bandits_after_insert_trigger
AFTER INSERT ON bandits
FOR EACH ROW EXECUTE PROCEDURE bandits_delete_master();
12. It's worked again!
Bandit.create!(name: ‘Al Capone’, specialization: ‘bootlegger’)
=> #<Bandit id: 1, name: "Al Capone", specialization: "bootlegger" …
SELECT COUNT(*) FROM ONLY bandints;
> 0 rows
SELECT COUNT(*) FROM bandints;
> 1 rows
SELECT COUNT(*) FROM ONLY bandints_bootlegger;
> 1 rows
13. RubyOnRails associations
has_many :bandits
@gang.bandits.create!(name: ‘Al Capone’, specialization: ‘bootlegger’); @gang.bandits.last
#<ActiveRecord::Relation [#<Bandit id: 1, name: "Al Cap", specialization: "bootlegger", gang_id: 1…
belongs_to :bandit
@bandit.crimes.create(title: ‘Loot’)
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "crimes" violates foreign
key constraint...
DETAIL: Key (bandit_id)=(1) is not present in table "bandits".
14. ForeignKey are useless (in this case)
A partitioned table is really an empty table
with multiple child tables all inheriting
from the main partitioned table.
remove_foreign_key :crimes, column: :bandit_id