2011-04-09 2 views
-1

의 테이블을 보자 : DBD :: SQLite, 자리 표시자를 통해 쿼리에서 배열을 전달하는 방법?

sqlite> create table foo (foo int, bar int); 
sqlite> insert into foo (foo, bar) values (1,1); 
sqlite> insert into foo (foo, bar) values (1,2); 
sqlite> insert into foo (foo, bar) values (1,3); 

그런 다음 몇 가지 데이터를 선택 :

sqlite> select * from foo where foo = 1 and bar in (1,2,3); 
1|1 
1|2 
1|3 

괜찮아 작동합니다. 이제 DBD :: SQLite 1.29 사용하려고합니다 :

my $sth = $dbh->prepare('select * from foo where foo = $1 and bar in ($2)'); 
$sth->execute(1,[1,2,3]); 

그리고이 날 결과가 제공됩니다. DBI 추적은 제 2 자리 표시자가 배열에 올바르게 바인딩되지만 점수는 없음을 보여줍니다. 문자열에서 배열 값을 join으로 지정하고 전달하면 아무런 결과가 없습니다. 배열을 평평하게하면 "2 대신 N 자리 표시 자로 불린다"라는 예측 오류가 발생합니다.

나는 약간의 상실감이있다. 시도 할 다른 무엇이 있습니까?

업데이트 : 좋아, 여기 실제 적용에서 가져온 한 가지 좋은 예가 있습니다.

첫 번째 설정 : 통계 데이터가 채워진 여러 테이블이 있으며 열 수는 10 개에서 700 개까지입니다. 내가 말하는 쿼리는보고 용으로 해당 데이터의 하위 집합을 선택합니다. 서로 다른 보고서는 서로 다른 측면을 고려하므로 하나의 요청마다 다른 쿼리를 실행합니다. 200 개 이상의 보고서, 즉 200-300 개의 검색어가 있습니다. 이 접근법은 Postgres를 위해 개발되었으며 이제는 그것을 축소하여 SQLite와 함께 사용할 필요가 있습니다. 이 모든 것이 Postgres와 잘 작동한다고 생각하면 모든 쿼리를 검토하고 다시 작성하는 것을 정당화 할 수 없습니다. 유지 관리에 좋지 않습니다. ANY()를 IN()으로 바꾸는 것과 같은 내부 쿼리 조정을 할 수 있고 할 수 있습니다. 이것은 사소한 측면입니다.

그래서, 여기 내 예입니다 거기에 사용되는 입력 매개 변수의 숫자와 다른 장소에서 다시 사용할 수

SELECT SPLIT, syn(SPLIT), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 40), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 30), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 50), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 220), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 20), 
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND 
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 80) 
FROM csplit WHERE ACD = $1 AND SPLIT = $2 

SELECT syn(LOGID), syn(LOC_ID), LOGID, EXTENSION, syn(ROLE), PERCENT, 
syn(AUXREASON), syn(AWORKMODE), syn(DIRECTION), WORKSKILL, syn(WORKSKLEVEL), 
AGTIME FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND LOC_ID = ANY ($3) AND 
LOGID IS NOT NULL 

이 가장 복잡한 예제되지 않습니다 :이 쿼리를 하나 개의 보고서에 대해 연속적으로 실행 질문; 일반 ? 자리 표시 자로 대체하는 것은 간단한 작업이 아닙니다. 포스트 그레스에 대해 쿼리를 실행하는 코드처럼 보이는 (입력 클렌징 등은 후) :

sub run_select { 
    my ($class, $dbh, $sql, @bind_values) = @_; 

    my $sth; 
    eval { 
    $sth = $dbh->prepare_cached($sql); 
    $sth->execute(@bind_values); 
    }; 
    [email protected] and die "Error executing query: [email protected]"; 

    my %types; 
    { 
    my $dbt = $dbh->type_info_all; 
    @types{ map { $_->[1] } @$dbt[1..$#$dbt] } = 
     map { $_->[0] } @$dbt[1..$#$dbt]; 
    }; 

    my @result; 

    while (my $row = $sth->fetchrow_arrayref) { 
    my $i = 0; 
    push @result, [ map { [ $types{${$sth->{TYPE}}[$i++]}, $_ ] } @$row ]; 
    }; 

    return \@result; 
}; 

내가 쿼리를 다시 작성하고 값을 직접 삽입 할 수; SQL 인젝션은 SQL 엔진을 치기 훨씬 전에 모든 입력이 정규식 패턴을 통해 유지되기 때문에 별다른 위협이 아닙니다. 필자는 두 가지 이유로 동적으로 쿼리를 다시 작성하고 싶지 않습니다. a) 잠재적으로 값 인용과 관련된 문제를 야기 할 수 있고 b) prepare_cached의 모든 원인을 제거합니다. SQL 엔진은 매번 변경 될 경우 미리 준비된 명령문을 캐시 할 수 없습니다.

이제 내가 말했듯이 위의 코드는 Postgres와 잘 작동합니다. SQLite 엔진 자체가 분명히 데이터 세트로 작업 할 가능성이 있기 때문에 DBite :: SQLite 구현의 결함이라고 생각했습니다. 따라서 실제 질문은 다음과 같습니다. DBD :: SQLite를 사용하여 자리 표시 자에 데이터 세트를 전달하는 방법이 있습니까? 가장 논리적 인 배열 일 필요는 없습니다.

+0

작성한 코드를 게시하는 대신 실제 사례를 게시해야합니다. 응용 프로그램/라이브러리에서 가져 오는 데이터. – MkV

+0

-1이 질문은 품질이 낮습니다 –

답변

3

이 시도 :

my $sth = $dbh->prepare("select * from foo where foo = ? and bar in (?,?,?)"; 
$sth->execute(1,1,2,3); 

당신은 ?의 필요한 수의 생성 x 반복 연산자를 사용할 수 있습니다

my $sql = sprintf "select ... and bar in (%s)", join ",", ('?')[email protected]; 
+0

수 없습니다. 첫째, 질의는 매개 변수와 함께 호출 응용 프로그램 (광산은 라이브러리)에서 전달되고 나는 그것을 모두 분석하고 싶지 않습니다. 위의 단순화 된 예제보다 훨씬 더 복잡 할 수 있습니다. 둘째,이 접근법은 처음부터 전체 자리 표시 자 아이디어를 손상시킵니다. 구문 분석을 수행해야한다면 두통을 피하기 위해 쿼리 텍스트에 값을 직접 삽입하는 것이 좋습니다. –

+2

값을 주입하려면 값을 올바르게 이스케이프해야하고 여러 개를 전달하면 문제가 심해지므로 실제로 아이디어를 망칠 수는 없습니다. 쉼표로 구분 된 값. 여러 개의 '?'를 삽입 할 때 구문 분석을 수행하지 않습니다. – MkV

+0

@Alexander : SQL 인용을 망칠 때 두통이 생기는 것은 사실입니다. * Do not yourself yourself! * (그렇게한다면 PHP 애플리케이션을 완전히 괴롭히는 종류의 SQL injection 취약점이 생길 수 있습니다.) –

1

사용 SQL::Abstract을 다음과 같이 : 아마도

use strict; 
use warnings; 
use SQL::Abstract; 

my $sqla = SQL::Abstract->new; 
my %where = (
    foo => 1, 
    bar => { -in => [1,2,3] } 
); 

my ($sql, @params) = 
    $sqla->select('foo', '*', \%where); 

my $sth = $dbh->prepare($sql); 
$sth->execute(@params); 
+0

내 요구에 충분히 추상적으로 보이지 않습니다. 위의 예를 참조하십시오. 제안 주셔서 감사하지만, 나는 미래를 위해 그 모듈을 염두에 두겠습니다. –

관련 문제