powered by simpleCommunicator - 2.0.60     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / возможности функций в Postgres - поясните
13 сообщений из 13, страница 1 из 1
возможности функций в Postgres - поясните
    #35676350
Ega
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Долгое время писал под MSSQL, соответственно сложно переключиться между подходами в организации бизнес-логики.

Задача.
Есть сущность "Событие", есть сущность "Абонент". Каждое событие принадлежит какому-то абоненту, связь многие-к-одному (FK).

структура:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
create table abonents (
  abid integer,
  abname character variyng ( 32 ),
  constraint pk_abonents primary key (abid)
)

create table events (
  evid integer, 
  abid integer, 
  evname character variyng ( 32 ),
  constraint pk_events primary key (evid),
  constraint fk_events_ref_abonents foreign key (abid) references abonents (abid)
)

В идеале evid и abid ещё и "счётчиками" должны быть (ну либо из последовательности браться).


По бизнес-логике, нужна хранимка с параметрами @evname и @abname, которая делает следующее:

1. Проверяет таблицу abonents на предмет нахождения там записи с abname=@abname.
2. Если таковая запись нашлась - берёт её abid, если не нашлась - добавляет и берёт abid добавленной.
3. Добавляет запись в events с полученным abid
4. Возвращает в качестве результата evid добавленной записи.

Собственно, чтобы было понятней, привожу вариант хп для mssql:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
if exists (select * from sysobjects where id = object_id(N'[s_eveproc]') and OBJECTPROPERTY(id, N'IsProcedure') =  1 )
drop procedure [s_eveproc]
GO
CREATE PROCEDURE s_eveproc

  @evname varchar( 32 ) ='',
  @abname varchar( 32 ) =''

As 
declare  
		@result int,
    @abid int;

Set @result= 0 ;
Set @abid= 0 ;

Set NoCount ON;

select @abid=abid from abonents where abname=@abname;
if @abid= 0 
begin
  insert into abonents (abname) values (@abname);
  select @abid = scope_identity();
end;

insert into events (abid, evname) values (@abid, @evname);
select @result = scope_identity();


select result=@result;

return(@result);
GO


-- exec s_eveproc @evname='ev1', @abname='ab2';

...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35676370
Ega
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Собственно, вопросы:
1. Вообще, насколько такой подход организации бизнес-логики применим в Postgres? Может "принято" как-то по-другому поступать, например через триггеры или что-то ещё?
2. Как переменной внутри функции приравнять результат запроса, причём желательно обойтись без дополнительного if exists (MSSQL, к примеру, не меняет значения переменной, если нужных записей не нашлось)
3. Как можно организовать функцию и каким образом будет выглядеть её вызов через ADOCommand так, чтобы не получать result-set, а просто получить то, что возвращается через return (ADOCommand.Execute)?
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35676402
Dan Black
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
EgaСобственно, вопросы:
1. Вообще, насколько такой подход организации бизнес-логики применим в Postgres? Может "принято" как-то по-другому поступать, например через триггеры или что-то ещё?
2. Как переменной внутри функции приравнять результат запроса, причём желательно обойтись без дополнительного if exists (MSSQL, к примеру, не меняет значения переменной, если нужных записей не нашлось)
3. Как можно организовать функцию и каким образом будет выглядеть её вызов через ADOCommand так, чтобы не получать result-set, а просто получить то, что возвращается через return (ADOCommand.Execute)?
1. Поступать принято как позволяет совесть
2. Один из вариантов SELECT ... INTO _var1, _var2;
3. Я всегда делал DataSet.Open для подобного запроса SELECT s_eveproc('abname', 'evname'). Всё работало и не жужжало :)
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35676499
Ega
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну вот что получилось. Насколько "эстетично"? Покритикуйте, если что...

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
CREATE OR REPLACE FUNCTION tmp_get_dbver(
  _evname character varying ( 32 ),
  _abname character varying ( 32 )
)
  RETURNS integer AS
$BODY$
declare 
  _result integer;
  _abid integer;
begin

  select abid from abonents where abname=_abname into _abid;

  if _abid is null then
    _abid := nextval('abonents_abid_seq'::regclass);
    insert into abonents (abid, abname) values (_abid, _abname);
  end if;


 _result := nextval('events_evid_seq'::regclass);

  insert into events (evid, abid, evname) values (_result, _abid, _evname);

  
 return _result;
end
$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST  100 ;

...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35676522
Dan Black
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ega,
по мне так нормально
едиственное, я бы сам написал вместо if _abid is null then -> if not found then
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35676790
Ega
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dan Blackедиственное, я бы сам написал вместо if _abid is null then -> if not found then

О! Вот за это - офигенное спасибо. Именно это и хотел узнать. Только я почему-то эту штуку в документации найти не могу. Раздел не подскажешь?
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35676932
Dan Black
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
EgaРаздел не подскажешь?38.5.5
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35677138
Ega
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dan BlackEgaРаздел не подскажешь?38.5.5

Спасибо! Полезная штука.
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35677823
Andrey Daeron
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ega
Код: plaintext
1.
2.
3.
4.
5.
6.
  select abid from abonents where abname=_abname into _abid;

  if _abid is null then
    _abid := nextval('abonents_abid_seq'::regclass);
    insert into abonents (abid, abname) values (_abid, _abname);
  end if;

Если между этими двумя коммандами будет вставка в другой транзакции и она закомитится, то Вы получите UNIQUE VIOLATION. Следствие - этот INSERT нужно обрамить ексепшен-уловителем и повторным запросом к базе на предмет ID.

пример из мурзилки :
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
Example  38 - 1 . Exceptions with UPDATE/INSERT

This example uses exception handling to perform either UPDATE or INSERT, as appropriate: 
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db( 1 , 'david');
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35677883
Фотография Ёш
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Andrey DaeronEga
Код: plaintext
1.
2.
3.
4.
5.
6.
  select abid from abonents where abname=_abname into _abid;

  if _abid is null then
    _abid := nextval('abonents_abid_seq'::regclass);
    insert into abonents (abid, abname) values (_abid, _abname);
  end if;

Если между этими двумя коммандами будет вставка в другой транзакции и она закомитится, то Вы получите UNIQUE VIOLATION. Следствие - этот INSERT нужно обрамить ексепшен-уловителем и повторным запросом к базе на предмет ID.
откуда ? на abname нет ограничения уникальности, а nextval всегда разный будет - это гарантируется. он совершенно успешно вставит два одинаковых имени в двух параллельных транзакциях :)

в этом куске может быть другая проблема - если после
Код: plaintext
select abid from abonents where abname=_abname into _abid;
найденная строка будет удалена в соседней транзакции - тогда да, будет исключение по внешнему ключу на строке вставки в events
Код: plaintext
insert into events (evid, abid, evname) values (_result, _abid, _evname);

что бы защититься от этого предлагаю делать:
Код: plaintext
select abid from abonents where abname=_abname for share into _abid;
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35678728
Andrey Daeron
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ёш
откуда ? на abname нет ограничения уникальности, а nextval всегда разный будет - это гарантируется. он совершенно успешно вставит два одинаковых имени в двух параллельных транзакциях :)
[/quot]
Точно! ) Но я че-то думаю, что она там появится )))

Ёш
в этом куске может быть другая проблема - если после
Код: plaintext
select abid from abonents where abname=_abname into _abid;
найденная строка будет удалена в соседней транзакции - тогда да, будет исключение по внешнему ключу на строке вставки в events
Код: plaintext
insert into events (evid, abid, evname) values (_result, _abid, _evname);
что бы защититься от этого предлагаю делать:
Код: plaintext
select abid from abonents where abname=_abname for share into _abid;

+1
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35680449
Funny_Falcon
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Давно я не видил такого полезного поста (особенно для новичков ) ( да и для себя тоже )
Попробую подытожить:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
drop table if exists abonents CASCADE;
create table abonents (
  abid serial primary key,
  abname varchar( 32 ) unique
);

drop table if exists events CASCADE;
create table events (
  evid serial primary key, 
  abid integer references abonents(abid), 
  evname varchar( 32 )
);

CREATE OR REPLACE FUNCTION tmp_get_dbver(
  _evname character varying ( 32 ),
  _abname character varying ( 32 )
)
  RETURNS integer AS
$BODY$
declare 
  _result integer;
  _abid integer;
begin
  <<INSERTING>>
  LOOP
    BEGIN
      insert into abonents (abname) values (_abname) returning abid into _abid;
      EXIT INSERTING;
    EXCEPTION WHEN unique_violation THEN
      select abid from abonents where abname=_abname FOR SHARE into _abid;
      EXIT INSERTING when FOUND;
    END;
  END LOOP;
  insert into events (abid, evname) values (_abid, _evname) returning evid into _result;
  return _result;
end;
$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST  100 ;

select tmp_get_dbver('event1','user1');
select tmp_get_dbver('event2','user1');
select tmp_get_dbver('event1','user1');
Версия PostgreSQL 8.3
...
Рейтинг: 0 / 0
возможности функций в Postgres - поясните
    #35680810
Dan Black
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Funny_Falcon...Если продолжать тему идеальной хранимой процедуры, то я бы как минимум не стал делать теоретически бесконечный цикл ;)
+ чисто теоретически, в большинстве случаев в таблице abonents будут существовать записи с именем abname, поэтому insert будет в большистве случаев вызввать эксепшн, накладные расходы на который относительно велеки. Поэтому разумнее будет в начале проверять существование записи в таблице простым селектом.
...
Рейтинг: 0 / 0
13 сообщений из 13, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / возможности функций в Postgres - поясните
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]