PostgreSQL 함수 매개 변수로서의 테이블 이름


85

Postgres 함수의 매개 변수로 테이블 이름을 전달하고 싶습니다. 이 코드를 시도했습니다.

CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer 
AS $$
    BEGIN
    IF EXISTS (select * from quote_ident($1) where quote_ident($1).id=1) THEN
     return 1;
    END IF;
    return 0;
    END;
$$ LANGUAGE plpgsql;

select some_f('table_name');

그리고 나는 이것을 얻었다 :

ERROR:  syntax error at or near "."
LINE 4: ...elect * from quote_ident($1) where quote_ident($1).id=1)...
                                                             ^

********** Error **********

ERROR: syntax error at or near "."

그리고 이것으로 변경했을 때 얻은 오류는 다음과 같습니다 select * from quote_ident($1) tab where tab.id=1.

ERROR:  column tab.id does not exist
LINE 1: ...T EXISTS (select * from quote_ident($1) tab where tab.id...

아마도 내가 얻는 부분이 quote_ident($1)없으면 무언가가 선택되었음을 의미 하기 때문에 작동합니다 . 첫 번째 작업과 두 번째 작업이 동시에 작동하지 않는 이유는 무엇 입니까? 그리고 이것은 어떻게 해결할 수 있습니까?where quote_ident($1).id=11quote_ident($1)


이 질문이 오래되었다는 것을 알고 있지만 다른 문제에 대한 답변을 검색하는 동안 발견했습니다. 함수가 informational_schema를 쿼리 할 수 ​​없습니까? 내 말은, 데이터베이스에 어떤 개체가 있는지 쿼리하고 볼 수 있도록하는 것입니다. 그냥 아이디어.
David S

@DavidS 의견을 보내 주셔서 감사합니다.
John Doe

답변:


125

이것은 더욱 단순화되고 개선 될 수 있습니다.

CREATE OR REPLACE FUNCTION some_f(_tbl regclass, OUT result integer)
    LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE format('SELECT (EXISTS (SELECT FROM %s WHERE id = 1))::int', _tbl)
   INTO result;
END
$func$;

스키마 규정 이름으로 호출 (아래 참조) :

SELECT some_f('myschema.mytable');  -- would fail with quote_ident()

또는:

SELECT some_f('"my very uncommon table name"');

주요 포인트

  • 사용하여 OUT매개 변수 기능을 단순화 할 수 있습니다. 동적 SQL의 결과를 직접 선택하여 수행 할 수 있습니다. 추가 변수 및 코드가 필요하지 않습니다.

  • EXISTS원하는 것을 정확히 수행합니다. true행이 존재하는지 여부를 얻습니다 false. 이를 수행하는 다양한 방법이 EXISTS있으며 일반적으로 가장 효율적입니다.

  • 정수를 되돌리고 싶은 것 같으므로 boolean결과를에서 EXISTS로 캐스팅하여 integer정확히 얻은 결과 를 얻습니다. 대신 부울 을 반환 합니다.

  • 개체 식별자 유형 regclass을에 대한 입력 유형으로 사용합니다 _tbl. 그것은 모든 것을 수행 quote_ident(_tbl)하거나 format('%I', _tbl)할 것입니다.

  • .. SQL 주입도 방지 합니다.

  • .. 테이블 이름이 유효하지 않거나 존재하지 않거나 현재 사용자에게 표시되지 않는 경우 즉시 실패합니다. ( regclass매개 변수는 기존 테이블 에만 적용됩니다 .)

  • .. 스키마 규정 테이블 이름과 함께 작동합니다. 여기서 일반 quote_ident(_tbl)또는 format(%I)모호성을 해결할 수 없기 때문에 실패합니다. 스키마와 테이블 이름을 별도로 전달하고 이스케이프해야합니다.

  • format()구문을 단순화하고 사용 방법을 보여주기 위해 여전히를 사용 하지만 %s대신 %I. 일반적으로 쿼리는 더 복잡하므로 format()더 많은 도움이됩니다. 간단한 예에서는 다음과 같이 연결할 수 있습니다.

      EXECUTE 'SELECT (EXISTS (SELECT FROM ' || _tbl || ' WHERE id = 1))::int'
    
  • 목록에 id단일 테이블 만있는 동안 열 을 테이블 한정 할 필요가 없습니다 FROM. 이 예에서는 모호성이 없습니다. (동적) 내부 SQL 명령 EXECUTE에는 별도의 범위가 있으며, 함수 변수 또는 매개 변수는 함수 본문의 일반 SQL 명령과 달리 여기에서 표시되지 않습니다.

다음 은 동적 SQL에 대한 사용자 입력을 항상 적절하게 이스케이프하는 이유입니다 .

DB <> 바이올린 여기 시연 SQL 주입
올드 sqlfiddle


2
@suhprano : 물론입니다. 시도해보십시오 :DO $$BEGIN EXECUTE 'ANALYZE mytbl'; END$$;
Erwin Brandstetter 2015

왜 % s가 아니라 % L입니까?
Lotus

3
@Lotus : 설명은 답에 있습니다. regclass값은 텍스트로 출력 될 때 자동으로 이스케이프됩니다. 이 경우에는 잘못된%L 것 입니다.
Erwin Brandstetter 2017-06-02

CREATE OR REPLACE FUNCTION table_rows(_tbl regclass, OUT result integer) AS $func$ BEGIN EXECUTE 'SELECT (SELECT count(1) FROM ' || _tbl || ' )::int' INTO result; END $func$ LANGUAGE plpgsql; 테이블 행 개수 함수 만들기select table_rows('nf_part1');
l mingzhi

모든 열을 어떻게 얻을 수 있습니까?
Ashish

12

가능하다면 이렇게하지 마십시오.

그것이 답입니다. 그것은 반 패턴입니다. 클라이언트가 데이터를 원하는 테이블을 알고 있으면 SELECT FROM ThatTable. 데이터베이스가 이것이 필요한 방식으로 설계 되었다면 차선책으로 설계된 것 같습니다. 데이터 액세스 계층이 테이블에 값이 있는지 여부를 알아야하는 경우 해당 코드에서 SQL을 작성하기 쉽고이 코드를 데이터베이스로 푸시하는 것은 좋지 않습니다.

나에게 이것은 원하는 층의 번호를 입력 할 수있는 엘리베이터 내부에 장치를 설치하는 것처럼 보입니다. Go 버튼을 누른 후 원하는 층의 올바른 버튼으로 기계식 손을 이동하고 누릅니다. 이로 인해 많은 잠재적 인 문제가 발생합니다.

참고 : 여기에는 조롱 할 의도가 없습니다. 저의 어리석은 엘리베이터의 예는이 기술의 문제를 간결하게 지적 할 수있는 * 내가 상상할 수있는 최고의 장치 *였습니다. 쓸모없는 간접 계층을 추가하여 호출자 공간 (튼튼하고 잘 이해 된 DSL, SQL 사용)에서 테이블 이름 선택을 모호하고 기괴한 서버 측 SQL 코드를 사용하여 하이브리드로 이동합니다.

쿼리 구성 논리를 동적 SQL로 이동하여 책임을 분할하면 코드를 이해하기가 더 어려워집니다. 오류 가능성이있는 사용자 지정 코드의 이름에서 표준적이고 신뢰할 수있는 규칙 (SQL 쿼리가 선택할 항목을 선택하는 방법)을 위반합니다.

다음은이 접근 방식의 몇 가지 잠재적 인 문제에 대한 자세한 내용입니다.

  • 동적 SQL은 프런트 엔드 코드 또는 백 엔드 코드만으로 인식하기 어려운 SQL 삽입 가능성을 제공합니다 (이를 확인하려면 함께 검사해야합니다).

  • 저장 프로 시저 및 함수는 SP / 함수 소유자가 권한을 가지고 있지만 호출자는 권한이없는 리소스에 액세스 할 수 있습니다. 내가 이해하는 한, 특별한주의없이 기본적으로 동적 SQL을 생성하고 실행하는 코드를 사용할 때 데이터베이스는 호출자의 권한에 따라 동적 SQL을 실행합니다. 즉, 권한있는 개체를 전혀 사용할 수 없거나 모든 클라이언트에 공개해야하므로 권한있는 데이터에 대한 잠재적 공격의 표면 영역이 증가합니다. 생성시 SP / 함수를 항상 특정 사용자 (SQL Server에서 EXECUTE AS) 로 실행하도록 설정하면 이 문제를 해결할 수 있지만 상황이 더 복잡해집니다. 이는 동적 SQL을 매우 매력적인 공격 벡터로 만들어 이전 지점에서 언급 한 SQL 주입의 위험을 악화시킵니다.

  • 개발자가 애플리케이션 코드를 수정하거나 버그를 수정하기 위해 수행하는 작업을 이해해야 할 때 실행되는 정확한 SQL 쿼리를 얻는 것이 매우 어렵습니다. SQL 프로파일 러를 사용할 수 있지만 이는 특별한 권한을 필요로하며 프로덕션 시스템에 부정적인 성능 영향을 미칠 수 있습니다. 실행 된 쿼리는 SP에 의해 기록 될 수 있지만 이로 인해 의심스러운 이점 (새 테이블 수용, 오래된 데이터 제거 등)에 대한 복잡성이 증가하고 명확하지 않습니다. 실제로 일부 응용 프로그램은 개발자가 데이터베이스 자격 증명을 갖지 않도록 설계되었으므로 실제로 제출되는 쿼리를 보는 것이 거의 불가능합니다.

  • 존재하지 않는 테이블을 선택하려고 할 때와 같이 오류가 발생하면 데이터베이스에서 "잘못된 개체 이름"줄을 따라 메시지가 표시됩니다. 백엔드에서 SQL을 작성하든 데이터베이스에서 SQL을 작성하든 똑같은 일이 발생하지만, 시스템 문제를 해결하려는 일부 가난한 개발자는 한 단계 더 깊은 곳에서 또 다른 동굴에 들어가야합니다. 문제가 무엇인지 알아 내기 위해 모든 일을하는 경이로운 절차를 파헤 치기 위해 문제가 존재합니다. 로그에는 "GetWidget의 오류"가 표시되지 않고 "OneProcedureToRuleThemAllRunner의 오류"가 표시됩니다. 이 추상화는 일반적으로 시스템을 악화 시킵니다.

매개 변수를 기반으로 테이블 이름을 전환하는 의사 C #의 예 :

string sql = $"SELECT * FROM {EscapeSqlIdentifier(tableName)};"
results = connection.Execute(sql);

이것이 상상할 수있는 모든 가능한 문제를 제거하는 것은 아니지만, 다른 기술로 설명했던 결함은이 예제에 없습니다.


4
나는 그것에 완전히 동의하지 않습니다. 이 "이동"버튼을 누른 다음 바닥이 있는지 여부를 확인하는 메커니즘이 있습니다. 트리거에서 함수를 사용하여 일부 조건을 확인할 수 있습니다. 이 결정이 가장 아름답지는 않을 수도 있지만 시스템이 이미 충분히 크고 논리를 수정해야한다면이 선택은 그렇게 극적이지 않은 것 같습니다.
John Doe

1
그러나 존재하지 않는 버튼을 누르려고하는 동작은 어떻게 처리하든 예외를 생성 할 뿐이라는 점을 고려하십시오. 실제로 존재하지 않는 버튼을 누를 수는 없기 때문에, 존재하지 않는 번호를 확인하기 위해 버튼을 누르는 것 외에도 레이어를 추가해도 아무런 이점이 없습니다. 해당 레이어를 생성하기 전에는 그러한 번호 입력이 존재하지 않았기 때문입니다! 추상화는 프로그래밍에서 가장 강력한 도구라고 생각합니다. 그러나 기존 추상화를 잘못 복제하는 레이어를 추가하는 것은 잘못된 것 입니다. 데이터베이스 자체는 이미 이름을 데이터 세트에 매핑하는 추상화 계층입니다.
ErikE dec

3
에 딱 맞다. SQL의 요점은 추출하려는 데이터 집합을 표현하는 것입니다. 이 함수가 수행하는 유일한 작업은 "미리 준비된"SQL 문을 캡슐화하는 것입니다. 식별자도 하드 코딩되어 있다는 사실을 감안할 때 모든 것이 나쁜 냄새를 풍깁니다.
Nick Hristov

1
@three 누군가 스킬숙달 단계 ( Dreyfus 스킬 습득 모델 참조 )에 도달 할 때까지 그는 "동적 SQL에서 사용할 프로 시저에 테이블 이름을 전달하지 마십시오"와 같은 규칙을 절대적으로 준수해야합니다. 그것이 항상 나쁜 것은 아니라는 암시조차도 그 자체로 나쁜 조언 입니다. 이것을 알면 초보자는 그것을 사용하고 싶을 것입니다! 그 나쁜. 특정 경우에 그러한 규칙 위반이 실제로 의미가 있는지 알 수있는 경험이있는 유일한 사람이기 때문에 주제의 마스터 만이 규칙을 위반해야합니다.
ErikE

1
@ three-cups 나는 그것이 왜 나쁜 생각인지에 대해 훨씬 더 자세히 업데이트했습니다.
ErikE

10

plpgsql 코드 내에서 EXECUTE 문은 테이블 이름이나 열이 변수에서 오는 쿼리에 사용해야합니다. 또한 동적으로 생성 IF EXISTS (<query>)될 때 구성이 허용되지 않습니다 query.

두 가지 문제가 모두 수정 된 함수는 다음과 같습니다.

CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer 
AS $$
DECLARE
 v int;
BEGIN
      EXECUTE 'select 1 FROM ' || quote_ident(param) || ' WHERE '
            || quote_ident(param) || '.id = 1' INTO v;
      IF v THEN return 1; ELSE return 0; END IF;
END;
$$ LANGUAGE plpgsql;

감사합니다. 몇 분 전에 답변을 읽을 때 똑같이 만들었습니다. 유일한 차이점은 quote_ident()추가 따옴표를 추가했기 때문에 제거해야 했다는 것입니다. 대부분의 예에서 사용되기 때문에 약간 놀랐습니다.
John Doe

테이블 이름에 [az] 외부의 문자가 포함 된 경우 또는 예약 된 식별자와 충돌하는 경우 (예 : 테이블 이름으로 "group")
Daniel Vérité

그런데, IF EXISTS <query>구조가 존재하지 않는다는 것을 증명할 링크를 제공해 주 시겠습니까? 나는 그런 것을 작업 코드 샘플로 보았을 것이라고 확신합니다.
John Doe

1
@JohnDoe : plpgsql IF EXISTS (<query>) THEN ...에서 완벽하게 유효한 구조입니다. .NET 용 동적 SQL이 아닙니다 <query>. 많이 사용합니다. 또한이 기능은 상당히 개선 될 수 있습니다. 답변을 올렸습니다.
Erwin Brandstetter

1
죄송합니다.에 대해 맞습니다 if exists(<query>). 일반적인 경우에 유효합니다. 그에 따라 답을 확인하고 수정했습니다.
Daniel Vérité

4

첫 번째는 실제로 "작동"하지 않는다는 의미에서 오류를 생성하지 않는 한만 작동합니다.

을 시도 SELECT * FROM quote_ident('table_that_does_not_exist');하면 함수가 1을 반환하는 이유를 알 수 있습니다. select는 quote_ident하나의 행 (변수 $1또는이 특정 경우 table_that_does_not_exist) 이 있는 하나의 열 (이름 )이 있는 테이블을 반환합니다 .

수행하려는 작업에는 동적 SQL이 필요하며, 이는 실제로 quote_*함수가 사용되는 곳입니다.


고마워요, Matt, table_that_does_not_exist같은 결과를 줬어요. 당신 말이 맞아요.
John Doe

2

질문이 테이블이 비어 있는지 여부를 테스트하는 것이라면 (id = 1) 여기에 Erwin의 저장된 proc의 단순화 된 버전이 있습니다.

CREATE OR REPLACE FUNCTION isEmpty(tableName text, OUT zeroIfEmpty integer) AS
$func$
BEGIN
EXECUTE format('SELECT COALESCE ((SELECT 1 FROM %s LIMIT 1),0)', tableName)
INTO zeroIfEmpty;
END
$func$ LANGUAGE plpgsql;

1

나는 이것이 오래된 스레드라는 것을 알고 있지만 최근에 동일한 문제를 해결하려고 할 때-내 경우에는 상당히 복잡한 스크립트에 대해 그것을 발견했습니다.

전체 스크립트를 동적 SQL로 바꾸는 것은 이상적이지 않습니다. 지루하고 오류가 발생하기 쉬운 작업이며 매개 변수화 기능을 잃게됩니다. 매개 변수는 SQL에서 상수로 보간되어야하며 성능과 보안에 나쁜 결과를 가져야합니다.

다음은 테이블에서만 선택해야하는 경우 SQL을 그대로 유지할 수있는 간단한 트릭입니다. 동적 SQL을 사용하여 임시보기를 만듭니다.

CREATE OR REPLACE FUNCTION some_f(_tbl varchar) returns integer
AS $$
BEGIN
    drop view if exists myview;
    execute format('create temporary view myview as select * from %s', _tbl);
    -- now you can reference myview in the SQL
    IF EXISTS (select * from myview where myview.id=1) THEN
     return 1;
    END IF;
    return 0;
END;
$$ language plpgsql;

0

테이블 이름, 열 이름 및 값을 매개 변수로 함수에 동적으로 전달하려는 경우

이 코드 사용

create or replace function total_rows(tbl_name text, column_name text, value int)
returns integer as $total$
declare
total integer;
begin
    EXECUTE format('select count(*) from %s WHERE %s = %s', tbl_name, column_name, value) INTO total;
    return total;
end;
$total$ language plpgsql;


postgres=# select total_rows('tbl_name','column_name',2); --2 is the value

-2

9.4 버전의 PostgreSQL이 있으며 항상이 코드를 사용합니다.

CREATE FUNCTION add_new_table(text) RETURNS void AS
$BODY$
begin
    execute
        'CREATE TABLE ' || $1 || '(
        item_1      type,
        item_2      type
        )';
end;
$BODY$
LANGUAGE plpgsql

그리고:

SELECT add_new_table('my_table_name');

그것은 나를 위해 잘 작동합니다.

주의! 위의 예는 "데이터베이스를 쿼리하는 동안 안전을 유지하려면 어떻게하지 않겠습니까?"를 보여주는 예 중 하나입니다.


1
new테이블 생성 은 기존 테이블의 이름으로 작업하는 것과 다릅니다. 어느 쪽이든, 코드로 실행되는 텍스트 매개 변수를 이스케이프하거나 SQL 주입에 열려 있어야합니다.
Erwin Brandstetter 2015 년

오, 그래, 내 실수. 그 주제는 나를 오도했고 또한 끝까지 읽지 않았습니다. 일반적으로 제 경우에는. : P 텍스트 매개 변수가있는 코드가 주입에 노출되는 이유는 무엇입니까?
dm3

죄송합니다. 정말 위험합니다. 답변 감사합니다!
dm3
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.