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

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

Он должен:
1. Выбрать перечень полей в таблице
2. Для каждого поля сравнить значения в NEW и OLD
3. выполнить остальные действия ...

Вот триггерная процедура:
Код: 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.
DECLARE
	field record;
BEGIN

-- Выбираем перечень полей из таблицы, где висит триггер
FOR field IN	
		SELECT
			a.attname AS name
		FROM
			pg_class c,
			pg_attribute a
		WHERE
			c.relfilenode = a.attrelid and
			a.attnum >  0  and
			c.relname = TG_RELNAME
LOOP

--Как тут подставить field.name в NEW?
-- Т.е надо получать:
-- NEW.id
-- NEW.name
-- NEW.street_id и т.д.

	RAISE NOTICE 'field: %', NEW.field.name;

END LOOP;

RETURN OLD;

END;

Вариант NEW.field.name ессно не работает. Как можно сделать что-то вроде 'NEW.' || field.name?
...
Рейтинг: 0 / 0
Составное имя переменной
    #34134677
wbear
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
может попробовать другой язык для тригера?
в plpgsqle не получица.
...
Рейтинг: 0 / 0
Составное имя переменной
    #34135227
DDT
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Не против :) А как это сделать, например, в С?
...
Рейтинг: 0 / 0
Составное имя переменной
    #34135354
ShadyAngel
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вопрос так часто возникает что разработчики бы напряглись да сделали бы обращение к полю в NEW ну хотя бы по номеру :(
...
Рейтинг: 0 / 0
Составное имя переменной
    #34135619
Shweik
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну так отпиши им. Только сначала советую внимательно вычитать TODO. ;)
...
Рейтинг: 0 / 0
Составное имя переменной
    #34135664
st_serg
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
либо вышли патч :)
...
Рейтинг: 0 / 0
Составное имя переменной
    #34135796
.gc
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
.gc
Гость
на pl/Perl:
Код: plaintext
\nempty_db=# CREATE LANGUAGE plperl;\nCREATE LANGUAGE\nempty_db=# \\! cat plp.sql\nbegin;\nCREATE OR REPLACE FUNCTION perl_watch_changes() RETURNS trigger AS $$\n  foreach $key (keys %{$_TD->{new}} ) {\n    if( $_TD->{new}{$key} ne $_TD->{old}{$key} ) {\n      $old_v = $_TD->{old}{$key};\n      $new_v = $_TD->{new}{$key};\n      elog(NOTICE, qq{$key : $old_v -> $new_v });\n    }\n  }\n  return;\n$$ LANGUAGE plperl;\nCREATE TABLE t001 (\n   id int primary key,\n   t_data text,\n   d_data date,\n   f_data float\n);\nCREATE TRIGGER t001_trg AFTER UPDATE ON t001 FOR EACH ROW EXECUTE PROCEDURE perl_watch_changes();\ninsert into t001 (id, t_data, d_data, f_data) VALUES ( 1 , \'test\', now(),  3 . 1415 );\nupdate t001 set d_data = d_data + \'1 day\'::interval where id =  1 ;\nupdate t001 set f_data =  2 . 78 , t_data = \'new text\';\nrollback;\nempty_db=# \\i plp.sql\nBEGIN\nCREATE FUNCTION\npsql:plp.sql: 17 : NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t001_pkey" for table "t001"\nCREATE TABLE\nCREATE TRIGGER\nINSERT  0   1 \npsql:plp.sql: 20 : NOTICE:  d_data :  2006 - 11 - 17  ->  2006 - 11 - 18 \nUPDATE  1 \npsql:plp.sql: 21 : NOTICE:  f_data :  3 . 1415  ->  2 . 78 \npsql:plp.sql: 21 : NOTICE:  t_data : test -> new text\nUPDATE  1 \nROLLBACK\nempty_db=#\n
на pl/pgsql был пример решения подобной задачи: /topic/278937#2527303
...
Рейтинг: 0 / 0
Составное имя переменной
    #34136418
DDT
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
.gc

Спасибо. Это уже что-то.

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

А как на С это будет выглядеть можешь сказать?
...
Рейтинг: 0 / 0
Составное имя переменной
    #34137166
.gc
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
.gc
Гость
DDT .gc

Спасибо. Это уже что-то.

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

А как на С это будет выглядеть можешь сказать?
АХЕЗ...
может быть примерно так:
Код: 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.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
/*
create table change_log (
   id        bigserial,
   tablename text,
   fieldname text,
   old_value text,
   new_value text,
   change_date timestamp default now()
);
*/
#include <string.h>
#include "postgres.h"
#include "executor/spi.h"       /* this is what you need to work with SPI */
#include "commands/trigger.h"   /* ... and triggers */
#define GET_TEXT(cstrp) (DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
extern Datum trig_log_changes(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(trig_log_changes);

Datum
trig_log_changes(PG_FUNCTION_ARGS)
{
    TriggerData *trigdata = (TriggerData *) fcinfo->context;
    TupleDesc   tupdesc;
    HeapTuple old_tuple;
    HeapTuple new_tuple;

    Datum argvalues[ 4 ];
    Oid argtypes[ 4 ] = {TEXTOID, TEXTOID, TEXTOID, TEXTOID};
    char nulls[ 4 ] = "    "; // fourspaces
    void * plan;

    int         ret, i;
    char * relname, * fname, * old_value, * new_value;
    int need_to_record;

    /* make sure it is called as a trigger at all */
    if (!CALLED_AS_TRIGGER(fcinfo))
        elog(ERROR, "trig_log_changes: not called by trigger manager");

    if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event) || !TRIGGER_FIRED_AFTER(trigdata->tg_event) )
        elog(ERROR, "trig_log_changes: not called after update");

    /* connect to SPI manager */
    if ((ret = SPI_connect()) <  0 )
        elog(ERROR, "trig_log_changes: SPI_connect returned %d", ret);

   tupdesc = trigdata->tg_relation->rd_att;
   old_tuple = trigdata->tg_trigtuple;
   new_tuple = trigdata->tg_newtuple;

   relname = SPI_getrelname( trigdata-> tg_relation );
   argvalues[ 0 ] = GET_TEXT(relname);
   plan = SPI_prepare("INSERT INTO change_log (tablename, fieldname, old_value, new_value) VALUES ( $1, $2, $3, $4 )",  4 , argtypes);
   SPI_saveplan(plan);
   for( i =  0 ; i < tupdesc -> natts; i ++ ) {
      old_value = SPI_getvalue(old_tuple, tupdesc, i);
      new_value = SPI_getvalue(new_tuple, tupdesc, i);
      need_to_record =  0 ;

      if( (NULL == new_value && NULL != old_value) ||
          (NULL != new_value && NULL == old_value) ||
          (NULL != old_value && NULL != new_value &&  0  != strcmp(old_value, new_value))  )
           need_to_record =  1 ;

      if( need_to_record ) {
         fname = SPI_fname( tupdesc, i);

         if( old_value == NULL) nulls[ 2 ] = 'n' ;  else nulls[ 2 ] = ' ';
         if( new_value == NULL) nulls[ 3 ] = 'n' ;  else nulls[ 3 ] = ' ';
         argvalues[ 1 ] = GET_TEXT(fname);
         argvalues[ 2 ] = GET_TEXT(old_value);
         argvalues[ 3 ] = GET_TEXT(new_value);

         elog(NOTICE, "trig_log_changes: %s changed field %s: %s -> %s", relname, fname, old_value, new_value);


         SPI_execp( plan, argvalues, nulls,   1 );
      }
   }
   SPI_freeplan(plan);
   SPI_finish();
   return PointerGetDatum( trigdata->tg_newtuple);
}
Код: 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.
begin;
create table change_log (
   id        bigserial,
   tablename text,
   fieldname text,
   old_value text,
   new_value text,
   change_date timestamp default now()
);
CREATE TABLE t001 (
   id int primary key,
   t_data text,
   d_data date,
   f_data float
);

CREATE FUNCTION trig_log_changes() RETURNS trigger  AS '/opt/pg/trigg/trig_log_changes.so'  LANGUAGE C;
CREATE TRIGGER tafter AFTER UPDATE  ON t001 FOR EACH ROW EXECUTE PROCEDURE trig_log_changes();

insert into t001 (id, t_data, d_data, f_data) VALUES ( 1 , 'test', now(),  3 . 1415 );
update t001 set d_data = d_data + '1 day'::interval where id =  1 ;
update t001 set f_data =  2 . 78 , t_data = 'new text';
select * from change_log;
rollback;
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
empty_db=# \i tst.sql
BEGIN
psql:tst.sql: 9 : NOTICE:  CREATE TABLE will create implicit sequence "change_log_id_seq" for serial column "change_log.id"
CREATE TABLE
psql:tst.sql: 15 : NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t001_pkey" for table "t001"
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
INSERT  0   1 
psql:tst.sql: 21 : NOTICE:  trig_log_changes: t001 changed field d_data:  2006 - 11 - 17  ->  2006 - 11 - 18 
UPDATE  1 
psql:tst.sql: 22 : NOTICE:  trig_log_changes: t001 changed field t_data: test -> new text
UPDATE  1 
 id | tablename | fieldname | old_value  | new_value  |        change_date
----+-----------+-----------+------------+------------+----------------------------
   1  | t001      | d_data    |  2006 - 11 - 17  |  2006 - 11 - 18  |  2006 - 11 - 17   14 : 49 : 41 . 487301 
   2  | t001      | t_data    | test       | new text   |  2006 - 11 - 17   14 : 49 : 41 . 487301 
(записей:  2 )

ROLLBACK
внимание! код тестовый и стопудово глючный!
...
Рейтинг: 0 / 0
Составное имя переменной
    #34139728
ShadyAngel
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
раз уж пошла такая пьянка, подскажите, плз, процедуры на Си нужно как то компилять/подключать или можно просто писать как на PL\PgSQL?
...
Рейтинг: 0 / 0
Составное имя переменной
    #34145618
glebofff
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
.gc внимание! код тестовый и стопудово глючный!

Да, есть кое-какие проблемы, с NULL-значениями, например, или с
Код: plaintext
#define GET_TEXT(cstrp) (DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
:-)

1. Мне кажется, что хранение oldvalue - совершенно ни к чему, достаточно и newvalue, но начальные значения должны добавляться при insert (соответственно триггер должен уметь работать и в режиме "after insert").

2. Кроме имени таблицы было бы неплохо хранить имя схемы ( SPI_getnspname( trigdata-> tg_relation) ).

3. Лог совершенно не имеет смысла, если не хранить (опционально) значение первичного ключа для изменяемых данных. Поле, содержащее значение ключа можно передать через аргументы триггера, но доставать их придётся не из PG_FUNCTION_ARGS , а из trigdata->tg_trigger->tgargs .

А так - всё клёво. Твой пример заставил меня написать это дело с нуля. :-)
...
Рейтинг: 0 / 0
11 сообщений из 11, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Составное имя переменной
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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