2011-06-18 42 views
3

我想爲數據庫中的許多表生成代碼,並在我準備編寫第三個「獲取表X的代碼」的實現時停下來重構我的解決方案。PostgreSQL觸發器動態生成多個表的代碼

我的代碼是這樣的:

-- Tenants receive a code that's composed of a portion of their subdomain and a unique number. 
-- This number comes from this sequence. 
CREATE SEQUENCE tenant_codes_seq MAXVALUE 9999 NO CYCLE; 

CREATE TABLE tenants (
    subdomain varchar(36) NOT NULL UNIQUE 
    , tenant_code char(8)  NOT NULL UNIQUE 
    , PRIMARY KEY (tenant_code) 
); 

-- This function expects four parameters: 
-- 1. The column that's receiving the generated code (RECEIVING_COLUMN_NAME) 
-- 2. The column that's used to salt the code (SALT_COLUMN_NAME) 
-- 3. The number of characters to use from the salt column (SALT_LENGTH) 
-- 4. The sequence name, but defaults to RECEIVING_COLUMN_NAME || 's' 
CREATE OR REPLACE FUNCTION generate_table_code() RETURNS trigger AS $$ 
DECLARE 
    receiving_column_name text; 
    salt_column_name  text; 
    salt_length   text; 
    sequence_name   text; 
BEGIN 
    receiving_column_name := TG_ARGV[0]; 
    salt_column_name  := TG_ARGV[1]; 
    salt_length   := TG_ARGV[2]; 

    CASE 
    WHEN TG_NARGS = 3 THEN 
    sequence_name := receiving_column_name || 's'; 
    WHEN TG_NARGS = 4 THEN 
    sequence_name := TG_ARGV[3]; 
    ELSE 
    RAISE EXCEPTION '3 or 4 arguments expected, received %', TG_NARGS; 
    END CASE; 

    -- The intent is to return ABC-0001 when salt_column contains 'ABC' 
    EXECUTE 'rpad(substr('    || 
      quote_ident(salt_column_name) || 
      ', 1, 4), 4, '    || 
      quote_literal('-')   || 
      ') || lpad(nextval('   || 
      quote_literal(sequence_name) || 
      ')::text, '     || 
      quote_literal(salt_length) || 
      ', '       || 
      quote_literal('0')   || 
      ')' 
    INTO STRICT NEW; 
    RETURN NEW; 
END 
$$ LANGUAGE plpgsql; 

CREATE TRIGGER generate_tenant_code_trig 
    BEFORE INSERT ON tenants FOR EACH ROW 
    EXECUTE PROCEDURE generate_table_code('tenant_code', 'subdomain', 4); 

如何分配給NEW.tenant_code,NEW.user_code或NEW.table_whatever_code?

運行一些測試,得出正確的「說法」,但我似乎無法正確分配:

INSERT INTO tenants(subdomain) VALUES ('abc') 


CREATE TABLE 
ERROR: syntax error at or near "NEW" 
LINE 1: NEW.tenant_code := rpad(substr(subdomain, 1, 4), 4, '-') || ... 
     ^
QUERY: NEW.tenant_code := rpad(substr(subdomain, 1, 4), 4, '-') || lpad(nextval('tenant_codes')::text, '4', '0'::text) 
CONTEXT: PL/pgSQL function "generate_table_code" line 20 at EXECUTE statement 

回答

4

我會很熱情地顯示錯誤(我偶爾需要這個自己太)但最好的我知道,使用變量引用列名稱是您實際需要使用PL/C觸發器而不是PL/PgSQL觸發器的情況之一。你可以在contrib/spi和PGXN上找到這種觸發器的例子。

或者,爲了能夠直接引用它們,請始終命名您的列,例如, NEW.tenant_code

就個人而言,我一般寫出來,創建觸發器的功能:

create function create_tg_stuff(_table regclass, _args[] text[]) 
    returns void as $$ 
begin 
    -- explore pg_catalog a bit 
    execute $x$ 
    create function $x$ || quote_ident(_table || '_tg_stuff') || $x$() 
    returns trigger as $t$ 
    begin 
    -- more stuff 
    return new; 
    end; 
    $t$ language plpgsql; 
    $x$; 
end; 
$$ language plpgsql; 
+0

謝謝,這就是我要做的,除了Ruby/ERB而不是PostgreSQL本身。 –

0

NEW是類型RECORD,所以你不能分配給AFAIK。

要設置列的值,分配給NEW.column,例如:

NEW.tenant_code := (SELECT some_calculation); 

也許你的設計過於複雜; PL/SQL是一種非常有限的語言 - 儘量讓代碼儘可能簡單