Thread: Re: [pgsql-ru-general] Хранимая процедура: возврат строки разного формата



7 марта 2011 г. 21:13 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:
есть таблица

parent:
 | id | somecol |

есть десяток унаследованных таблиц:

childX:
 | id | somecol | .... |


есть некий скрипт, который обрабатывает все данные в унаследованных по
каким-то критериям.

работает примерно так:

1. делает
   SELECT
       parent.id,
       pg_class.relname AS tbl
   FROM
       parent, pg_class
   WHERE
       pg_class.oid = r.tableoid
   LIMIT 1;

2. если результаты есть делает
   SELECT
       %1 AS tbl,
       *
   FROM
       %1
   WHERE
       id = %2

   где вместо %1 подставляется поле  tbl выбранное из первого
   запроса, а вместо %2 - id.

После того как результаты обработаны они удаляются из унаследованной
таблицы.

вот хочется запхать 1 и 2 в хранимую процедуру, пишем:

CREATE OR REPLACE FUNCTION foo() RETURNS RECORD AS $$
   DECLARE
       retval RECORD;
       recid  RECORD;
   BEGIN
       SELECT
           p.id,
           pg_class.relname AS tbl
       INTO
           recid
       FROM
           parent, pg_class
       WHERE
           parent.tableoid = pg_class.oid
       LIMIT 1;

       EXECUTE
           'SELECT '''
               || recid.tbl
               || ''' AS tbl, * FROM "'
               || recid.tbl
               || '" WHERE id = '
               || recid.id
           INTO retval;
       RETURN retval;
$$ LANGUAGE 'plpgsql';

получаем примерно то что надо но в виде одного значения:

foo: '(tblname,1234,abc,...)'

а хочется получить в виде такого результата как вернул бы SELECT, то
есть в виде хеша:

tbl:        tblname
id:         1234
somecol:    'abc'
...
Используйте SELECT * FROM foo();
 


И еще, квоттинг: в документации написано что можно использовать
функции quote_ident и quote_value для создания динамических запросов,
но у меня почему-то постгрис на них ругается, говорит: нет такой
функции
Если функции quote_ident(), quote_literal() и quote_nullable(). Первая
принимает в кач-ве аргумента только текст, а вторая и третья перегружены.
Проверьте аргумент какого типа Вы указываете при вызове.
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.


DI> Используйте SELECT * FROM foo();

в приведенном примере SELECT foo() работает,
а вот про
SELECT * FROM foo()

говорит ошибка:

ERROR:  a column definition list is required for functions returning "record"
SELECT * FROM foo()
              ^

понятное дело что определить column definition list заранее я не могу,
поскольку дочерние таблицы - разные.

и как быть?


DI> Если функции quote_ident(), quote_literal() и quote_nullable(). Первая
DI> принимает в кач-ве аргумента только текст, а вторая и третья перегружены.
DI> Проверьте аргумент какого типа Вы указываете при вызове.

ага поставил ::text и заработало, спасибо!
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
  `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537

Attachment


7 марта 2011 г. 22:56 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:

DI> Используйте SELECT * FROM foo();

в приведенном примере SELECT foo() работает,
а вот про
SELECT * FROM foo()

говорит ошибка:

ERROR:  a column definition list is required for functions returning "record"
SELECT * FROM foo()
             ^

понятное дело что определить column definition list заранее я не могу,
поскольку дочерние таблицы - разные.

и как быть?
Так псевдотип RECORD по своей сути не имеет предопределённой структуры,
то необходимо явно её указывать (включая точный тип данных каждого столбца)
в момент выборки.
Можно использовать hstore, который, начиная с 9.0,
может конструироваться из RECORD.



DI> Если функции quote_ident(), quote_literal() и quote_nullable(). Первая
DI> принимает в кач-ве аргумента только текст, а вторая и третья перегружены.
DI> Проверьте аргумент какого типа Вы указываете при вызове.

ага поставил ::text и заработало, спасибо!
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.


DI> Так псевдотип RECORD по своей сути не имеет предопределённой структуры,
DI> то необходимо явно её указывать (включая точный тип данных каждого столбца)
DI> в момент выборки.

то есть моя задача на хранимой процедуре решена быть не может :(
придется видимо оставлять два запроса

--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
  `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537

Attachment


7 марта 2011 г. 23:43 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:

DI> Так псевдотип RECORD по своей сути не имеет предопределённой структуры,
DI> то необходимо явно её указывать (включая точный тип данных каждого столбца)
DI> в момент выборки.

то есть моя задача на хранимой процедуре решена быть не может :(
Смотря какая задача. Вашу, если честно, я до конца не понял.
придется видимо оставлять два запроса

--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.


DI> Смотря какая задача. Вашу, если честно, я до конца не понял.

ну попробую подробнее:

имеется родительская таблица:
 | id | somecol |

и имеется множество таблиц от нее произведенных.

Фишка родительской таблицы в том что она неявно адресует всех дочек.

несмотря на то что все дочерние таблицы - разные, алогоритмика их
обработки примерно одинакова:

выбрали из них данное, обработали (запись ушла в другую БД), стерли

таким образом запрос вида

SELECT * FROM parent;

дает ответ на вопрос "есть ли данные для обработки в хотя бы одной из
дочерних таблиц?" а если сджойнить его с pg_class то и попутно ответ
на вопрос "в какой таблице есть данные для обработки?"

далее зная что в какой-то таблице есть данные мы имея имя этой таблицы
и уже заранее предвыбранный id записи можем забрать запись из этой
таблицы на выборку. а потом удалить.

вот и получалось две стадии:

1. селект по родительской
2. селект по одной из дочерних

я хотел это все впихнуть в хранимую процедуру, но полный аналог
двум селектам сделать не получается: либо запись с кучей безымянных
полей на выходе либо эти имена надо вручную расставлять, а это более
накладно нежели два общих SQL запроса в клиенте.

PS: дочерние таблицы - данные поступающие от различных устройств
измерения. Они актуальны только пока не обработаны, после обработки
становятся не нужны и удаляются.
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
  `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537

Attachment


7 марта 2011 г. 23:58 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:

DI> Смотря какая задача. Вашу, если честно, я до конца не понял.

ну попробую подробнее:

имеется родительская таблица:
 | id | somecol |

и имеется множество таблиц от нее произведенных.

Фишка родительской таблицы в том что она неявно адресует всех дочек.

несмотря на то что все дочерние таблицы - разные, алогоритмика их
обработки примерно одинакова:

выбрали из них данное, обработали (запись ушла в другую БД), стерли

таким образом запрос вида

SELECT * FROM parent;

дает ответ на вопрос "есть ли данные для обработки в хотя бы одной из
дочерних таблиц?" а если сджойнить его с pg_class то и попутно ответ
на вопрос "в какой таблице есть данные для обработки?"

далее зная что в какой-то таблице есть данные мы имея имя этой таблицы
и уже заранее предвыбранный id записи можем забрать запись из этой
таблицы на выборку. а потом удалить.

вот и получалось две стадии:

1. селект по родительской
2. селект по одной из дочерних

я хотел это все впихнуть в хранимую процедуру, но полный аналог
двум селектам сделать не получается: либо запись с кучей безымянных
полей на выходе либо эти имена надо вручную расставлять, а это более
накладно нежели два общих SQL запроса в клиенте.

PS: дочерние таблицы - данные поступающие от различных устройств
измерения. Они актуальны только пока не обработаны, после обработки
становятся не нужны и удаляются.
Хороший пример задачи, где можно применить hstore.
Решение.
CREATE TABLE device(id serial not null primary key, name text not null); -- усройство
CREATE TABLE measurement(id serial not null primary key,
  device integer not null references device(id),
  mtime timestamp not null default now(),
  data hstore not null) ; -- измерение

Итого: 2 сущности - "устройство" и "измерение". Никаких объединений с pg_class.

Проверка того, что есть данные для обработки:
select exists(select 1 from measurement);

Извлечение данных:
select dat from measurement;

Всё.
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.


DI> Хороший пример задачи, где можно применить hstore.
DI> Решение.
DI> CREATE TABLE device(id serial not null primary key, name text not null); --
DI> усройство
DI> CREATE TABLE measurement(id serial not null primary key,
DI> device integer not null references device(id),
DI> mtime timestamp not null default now(),
DI> data hstore not null) ; -- измерение

DI> Итого: 2 сущности - "устройство" и "измерение". Никаких объединений с pg_class.

кабы было все так просто то и не стоило бы огород городить :) я тут
просто задачу сильно упрощаю по отношению к реальной когда формулирую.

на самом деле данные не удаляются, а помечаются как обработанные это
раз.

а во вторых по каждой из дочерних таблиц построены свои индексы/отчеты
итп. то есть их не получается просто взять и в кучу соединить: как
минимум эффективные индексы потеряем (в т. ч. и уникальные)

то есть если мы перейдем к hstore, то получим способ записи/хранения,
но потеряем способы выборки. для хешей одного типа нужен индекс по
key1, для хешей другого типа по key2, для третьего - уникальный ключ
по key3 и key4, плюс еще CHECK'и а так же некоторые измерения имеют
друг на друга FOREIGN'ы.

можно было бы по добавлении записи в любую таблицу пересчет общей
статистики делать, но это накладно, поэтому вот такая схема как я выше
нарисовал используется
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
  `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537

Attachment


8 марта 2011 г. 0:39 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:

DI> Хороший пример задачи, где можно применить hstore.
DI> Решение.
DI> CREATE TABLE device(id serial not null primary key, name text not null); --
DI> усройство
DI> CREATE TABLE measurement(id serial not null primary key,
DI> device integer not null references device(id),
DI> mtime timestamp not null default now(),
DI> data hstore not null) ; -- измерение

DI> Итого: 2 сущности - "устройство" и "измерение". Никаких объединений с pg_class.

кабы было все так просто то и не стоило бы огород городить :) я тут
просто задачу сильно упрощаю по отношению к реальной когда формулирую.

на самом деле данные не удаляются, а помечаются как обработанные это
раз.

а во вторых по каждой из дочерних таблиц построены свои индексы/отчеты
итп. то есть их не получается просто взять и в кучу соединить: как
минимум эффективные индексы потеряем (в т. ч. и уникальные)
Данные hstore индексируются с помощью GIST или GIN.
 

то есть если мы перейдем к hstore, то получим способ записи/хранения,
но потеряем способы выборки. для хешей одного типа нужен индекс по
key1, для хешей другого типа по key2, для третьего - уникальный ключ
по key3 и key4, плюс еще CHECK'и а так же некоторые измерения имеют
друг на друга FOREIGN'ы.
Уникальные ключи в измерениях? Можно пример, очень интересно.
А ещё интереснее было бы взглянуть на пример с вн. ключами.
 

можно было бы по добавлении записи в любую таблицу пересчет общей
статистики делать, но это накладно, поэтому вот такая схема как я выше
нарисовал используется
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.




8 марта 2011 г. 0:47 пользователь Dmitriy Igrishin <dmitigr@gmail.com> написал:


8 марта 2011 г. 0:39 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:


DI> Хороший пример задачи, где можно применить hstore.
DI> Решение.
DI> CREATE TABLE device(id serial not null primary key, name text not null); --
DI> усройство
DI> CREATE TABLE measurement(id serial not null primary key,
DI> device integer not null references device(id),
DI> mtime timestamp not null default now(),
DI> data hstore not null) ; -- измерение

DI> Итого: 2 сущности - "устройство" и "измерение". Никаких объединений с pg_class.

кабы было все так просто то и не стоило бы огород городить :) я тут
просто задачу сильно упрощаю по отношению к реальной когда формулирую.

на самом деле данные не удаляются, а помечаются как обработанные это
раз.

а во вторых по каждой из дочерних таблиц построены свои индексы/отчеты
итп. то есть их не получается просто взять и в кучу соединить: как
минимум эффективные индексы потеряем (в т. ч. и уникальные)
Данные hstore индексируются с помощью GIST или GIN.
В догонку, начиная с 9.0, hstore может индексироваться и с помощью
BTREE и HASH, что позволяет строить даже уникальные индексы.
А ещё можно строить индесы (в т.ч. и уникальные) на выражения.
Например, так
create unique index m_idx1 on measurement using btree((data->'a'));
Это позволит сделать данные ключа 'a' уникальными.
 

то есть если мы перейдем к hstore, то получим способ записи/хранения,
но потеряем способы выборки. для хешей одного типа нужен индекс по
key1, для хешей другого типа по key2, для третьего - уникальный ключ
по key3 и key4, плюс еще CHECK'и а так же некоторые измерения имеют
друг на друга FOREIGN'ы.
Уникальные ключи в измерениях? Можно пример, очень интересно.
А ещё интереснее было бы взглянуть на пример с вн. ключами.
 

можно было бы по добавлении записи в любую таблицу пересчет общей
статистики делать, но это накладно, поэтому вот такая схема как я выше
нарисовал используется
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.





--
// Dmitriy.


DI> Данные hstore индексируются с помощью GIST или GIN.

а я вот с этими GIST и GIN не понял пока. где почитать про их
внутреннее устройство? интересует вопрос: если мы индекс по полю
hstore сделаем то насколько эффективность поиска пострадает по
сравнению с BTREE по выделенному столбику?

ну и некоторые столбики у меня бывает хранят массив значений (2-3)
то есть получится как бы хеш, но который в одном из своих полей (не
ключей) хранит массив. hstore справится с таким заданием? а построение
индекса по полю?


DI> Уникальные ключи в измерениях? Можно пример, очень интересно.

есть и уникальные. фишка в том что некоторые измерители выдают
повторяющиеся результаты, а так же делают повторы передач если
посчитали что коннект с сервером не состоялся (канал плохой бывает
всякое)

соответственно есть уникальные индексы вида

 идентификатор-устройства -- время когда было сделано измерение

на таблицу написано RULE, которое при приходе измерения которое уже
приходило ранее делает игнор (вернее запись в таблицу регистрации
трабел)

DI> А ещё интереснее было бы взглянуть на пример с вн. ключами.

а это когда измеритель вылетает за установленный порог, то он
вызывает запуск другого измерителя, который в обычном режиме не
работает :) соответственно запись измерения еще ссылается на
идентификатор измерения по результатам которого инициировано сие
измерение. как-то так

ну и комбинации вышеприведенного
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
  `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537

Attachment


8 марта 2011 г. 1:01 пользователь Dmitry E. Oboukhov <unera@debian.org> написал:
DI> Данные hstore индексируются с помощью GIST или GIN.

а я вот с этими GIST и GIN не понял пока. где почитать про их
внутреннее устройство? интересует вопрос: если мы индекс по полю
hstore сделаем то насколько эффективность поиска пострадает по
сравнению с BTREE по выделенному столбику?

ну и некоторые столбики у меня бывает хранят массив значений (2-3)
то есть получится как бы хеш, но который в одном из своих полей (не
ключей) хранит массив. hstore справится с таким заданием? а построение
индекса по полю?
hstore хранит пары типа text - text. Любой тип данных или массив
можно привести к text.
 


DI> Уникальные ключи в измерениях? Можно пример, очень интересно.

есть и уникальные. фишка в том что некоторые измерители выдают
повторяющиеся результаты, а так же делают повторы передач если
посчитали что коннект с сервером не состоялся (канал плохой бывает
всякое)

соответственно есть уникальные индексы вида

 идентификатор-устройства -- время когда было сделано измерение
В приведённой мною таблице measurement есть столбец mtime. Это
и есть момент измерения. Можно строить индекс (только сомневаюсь,
что есть необходимость строить уникальный индекс, если mtime
типа timestamp).
Другими словами, идея в том, чтобы поместить в hstore только
_данные_ измерений, а не свойста типа "время измерения", которые
являются метаданными по сути.
Индексы по данными измерения кажутся мне сомнительными...

на таблицу написано RULE, которое при приходе измерения которое уже
приходило ранее делает игнор (вернее запись в таблицу регистрации
трабел)
Тогда смысл в уникальных индексах (и т.п. ограничениях), если проверку
осуществляет правило?

DI> А ещё интереснее было бы взглянуть на пример с вн. ключами.

а это когда измеритель вылетает за установленный порог, то он
вызывает запуск другого измерителя, который в обычном режиме не
работает :) соответственно запись измерения еще ссылается на
идентификатор измерения по результатам которого инициировано сие
измерение. как-то так
Хорошо. В таблицы device и measurement добавляются столбцы parent,
который смотрит на столбец id (ссылка на себя). При этом, нужен
триггер на таблицу measurement для обеспечения соответствия
связей measurement - measurement и device - device - т.е. соответствия
связи данных и метаданных.

ну и комбинации вышеприведенного
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
 `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537



--
// Dmitriy.


спасибо за подробные коментарии. есть над чем поразмыслить.
после mysql голову переключить сложно - сразу столько возможностей
обрушилось :)
--

. ''`.                               Dmitry E. Oboukhov
: :’  :   email: unera@debian.org jabber://UNera@uvw.ru
`. `~’              GPGKey: 1024D / F8E26537 2006-11-21
  `- 1B23 D4F8 8EC0 D902 0555  E438 AB8C 00CF F8E2 6537

Attachment