Hstore — key-value расширение для postgresql
Наверное, не все знают, что для postgresql существует большое количество расширений, которые называются contrib модулями.
Рассмотрим один из таких модулей - hstore. Этот модуль нужен для того, чтобы в одном поле в БД хранить много значений key/value, фактически, просто какой-то хеш. При этом и ключи и значения могут быть только строками. О том, чем это лучше, нежели просто хранить в текстовом поле сериализованный хеш, я расскажу чуть-чуть попозже. Понадобится это может в том случае, если у вас есть модели с произвольным набором полей.
Вначале поставим это расширения в Ubuntu 11.10
sudo apt-get install postgresql-contrib
Потом поставим этот модуль. Для этого в 9.1 зайдем в консоль psql
.
CREATE EXTENSION hstore;
WARNING: => is deprecated as an operator name
DETAIL: This name may be disallowed altogether in future versions of PostgreSQL.
Для версии младше 9.1 процедура установки немного другая. Далее создадим тестовую таблицу и заполним ее данными.
CREATE table test(id serial, value hstore);
INSERT INTO test(value)
SELECT hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text)
FROM
generate_series(1, 1000);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value ||
hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
SELECT * FROM test LIMIT 1;
id | value
----+--------------------------------------------------------------------------------------------------------------
1 | "33"=>"923", "262"=>"181", "471"=>"658", "554"=>"916", "579"=>"472", "649"=>"287", "891"=>"620", "897"=>"24"
Ваш результат, понятно, будет отличаться, но это непринципиально.
Пока, конечно, это ничем не отличается от хранения сериализованного хеша в поле таблицы. Но тут мы добавим немного особой postgresql магии.
CREATE INDEX ON test USING GIN(value);
" ну, или вот так
CREATE INDEX ON test USING GIST(value);
Postgres позволяет создавать индексы на ключах нашего хеша. И теперь при поиске элемента по ключу это будет не полное сканирование таблицы, а поиск по индексу. Почитать про разницу между GIN и GIST индексами можно здесь.
Итак, попробуем.
" Найдем все упячечные ключи
explain SELECT value -> '111' FROM test WHERE value ? '111';
QUERY PLAN
------------------------------------------------------------------------------
Bitmap Heap Scan on test (cost=12.01..16.02 rows=1 width=114)
Recheck Cond: (value ? '111'::text)
-> Bitmap Index Scan on test_value_idx (cost=0.00..12.01 rows=1 width=0)
Index Cond: (value ? '111'::text)
Вах, как здорово — действительно вместо полного просмотра таблицы используется индекс.
Конечно, уже нашлись коллеги, которые написали плагин для activerecord. Судя по комитам от октября 2011 проект живой, но сам я им не пользовался, если у кого-то есть опыт — было бы интересно услышать.
Так что в следующий раз, когда мне понадобится метод serialize, я попробую вначале hstore, и обязательно поделюсь с вами.