2

VALUES準備されたDBIステートメントを介して1つ以上のセットを挿入する動的SQLステートメントを作成しています。私の質問は次のとおりです。

セットの数は動的であり、プレースホルダーとバインド値を使用して1つのクエリのみを送信するには、ステートメントを拡張するために必要なVALUES数などを追加する必要があるため、これが推奨される方法です(最も効率的な方法など)。効率の背後にある推論は、可能であればあなたの答えに役立つでしょう)または私はこれをとでクエリ文字列として構築する必要がありますか?( ?, ?, ?),( ?, ?, ?)INSERT INTO `tblname` ( $columnsString ) VALUESsprintfdbh->quote()

(少し追加情報として:私は現在AnyEvent :: DBIを使用しています。これはプレースホルダーとバインド値のみを公開し、メソッドは公開しないため、別のストレートDBIを作成して別のデータベースを使用しquote()ないと簡単に実現できません。メソッドを使用するためだけに、またはAnyEvent :: DBIモジュールを自分で変更せず$dbhにサーバーに接続します。)quote()

通常は必要に応じてステートメントを実行しますが、この重いワークロードの場合は、DBの効率を上げるために、挿入をまとめてバッチ処理しようとしています。

また、可能であれば(そしてその方法で)誰かが答えることができれば、DEFAULTプレースホルダーを使用してSQL値を挿入し、すばらしい値をバインドします。通常、これを行う必要がある場合はDEFAULT、文字列にsを直接追加し、sprintfを$dbh->quote()DEFAULT値にのみ使用します。

アップデート:

簡単なチャットで誤解を解きました。ユーザーikegamiは、プレースホルダーなしでクエリ文字列を自分で作成するのではなく、VALUESと次のようなプレースホルダーを混在させることを提案しました。

$queryString .= '(DEFAULT,?,?),(DEFAULT,DEFAULT,DEFAULT)';

SOに関するこの質問の最初の質問の背後にある理由のいくつかは、SQL'DEFAULT'がプレースホルダーバインドに含まれないことが保証された後、コードが読みにくくなると考えたため、この混合にいくらか反対したためです。価値、これは私が実装し始めた方法でした。

可能な場合はプレースホルダーを使用する方がクエリを作成する方法として受け入れられているようです。SQLDEFAULTが必要な場合は、プレースホルダーと同じクエリ作成に含める必要があります。NULLプレースホルダーとバインド値を使用して値を挿入できるため、これは値には適用されませんundef

アップデート2:

パフォーマンス、quote()を使用した独自のクエリの作成とプレースホルダーを使用した作成の「受け入れ」、およびSQLにすべての列を使用するソリューションを採用したINSERT INTO tblname (cols)理由は、およそ2〜4個あるためです。 1日に100万行がひどいデータベースサーバーに入り、私のコードは同じようにひどいサーバーで実行されています。SQL値が必要であるという要件とDEFAULT、これらのひどいパフォーマンスの制約から、今のところソリューションを選択しました。

これに遭遇する将来の開発者のために-SQL::Abstractを使用する@emazepのソリューションを見てください。または、何らかの理由で独自のソリューションを構築する必要がある場合は、@ Schwernのサブルーチンソリューションを使用するか、@ikegamiのソリューションを組み込むことを検討してください。これらはすべて、DBIの使用法と動的クエリの構築に関する「現状」に関する優れた回答であるため、それに答えてください。

4

4 に答える 4

3

車輪の再発明を行う特別な理由がない限り(いくつかある可能性があります)、SQL::Abstract(とりわけ)私たち全員の動的SQL生成の問題はすでに解決されています。

my %data = (
    name    => 'Jimbo Bobson',
    phone   => '123-456-7890',
    address => '42 Sister Lane',
    city    => 'St. Louis',
    state   => 'Louisiana'
);

use SQL::Abstract;
my ($stmt, @bind)
    = SQL::Abstract->new->insert('people', \%data);

print $stmt, "\n";
print join ', ', @bind;

印刷するもの:

INSERT INTO people ( address, city, name, phone, state)
VALUES ( ?, ?, ?, ?, ? )
42 Sister Lane, St. Louis, Jimbo Bobson, 123-456-7890, Louisiana

SQL::Abstract次に、SQLを毎回再生成せずに挿入するために多くの行を反復処理するための優れたトリックを提供しますが、一括挿入の場合もありますSQL::Abstract::Plugin::InsertMulti

use SQL::Abstract;    
use SQL::Abstract::Plugin::InsertMulti;

my ($stmt, @bind)
    = SQL::Abstract->new->insert_multi( 'people', [
        { name => 'foo', age => 23 },
        { name => 'bar', age => 40 },
    ]);

# INSERT INTO people ( age, name ) VALUES ( ?, ? ), ( ?, ? )
# 23, foo, 40, bar
于 2012-10-05T00:39:29.450 に答える
2

私は時々、次のような構成を使用しました:

#!/usr/bin/env perl

use strict; use warnings;

# ...

my @columns = ('a' .. 'z');

my $sql = sprintf(q{INSERT INTO sometable (%s) VALUES (%s)},
    join(',', map $dbh->quote($_), @columns),
    join(',', ('?') x @columns),
);

処理に関してDEFAULTは、その列を除外すると、DBがそれをデフォルト値に設定することを保証しませんか?

于 2012-10-04T16:57:36.067 に答える
1

「静的」クエリにプレースホルダーを使用する場合は、「動的」クエリにもプレースホルダーを使用する必要があります。クエリはクエリです。

my $stmt = 'UPDATE Widget SET foo=?'
my @params = $foo;

if ($set_far) {
   $stmt .= ', far=?';
   push @params, $far;
}

{
   my @where;

   if ($check_boo) {
      push @where, 'boo=?';
      push @params, $boo;
   }

   if ($check_bar) {
      push @where, 'bar=?';
      push @params, $bar;
   }

   $stmt .= ' WHERE ' . join ' AND ', map "($_)", @where
      if @where;
}

$dbh->do($stmt, undef, @params);

より多くのデモンストレーションを行うことができたのでUPDATEを使用しましたが、すべてがINSERTにも適用されます。

my @fields = ('foo');
my @params = ($foo);

if ($set_far) {
   push @fields, 'bar';
   push @params, $far;
}

$stmt = 'INSERT INTO Widget ('
      . join(',', @fields)
      . ') VALUES ('
      . join(',', ('?')x@fields)
      . ')';

$dbh->do($stmt, undef, @params);
于 2012-10-04T16:51:42.603 に答える
1

あなたは、コードの可読性とDEFAULTを渡すことができることについて懸念を表明しました。@ikegamiの答えをさらに一歩進めます...

sub insert {
    my($dbh, $table, $fields, $values) = @_;

    my $q_table      = $dbh->quote($table);
    my @q_fields     = map { $dbh->quote($_) } @$fields;
    my @placeholders = map { "?" } @q_fields;

    my $sql = qq{
        INSERT INTO $q_table
               ( @{[ join(', ', @q_fields)    ]} )
        VALUES ( @{[ join(', ', @placeholders ]} )
    };

    return $dbh->do($sql, undef, @$values);
}

これで、汎用の複数値挿入ルーチンができました。

# INSERT INTO foo ('bar', 'baz') VALUES ( 23, 42 )
insert( $dbh, "foo", ['bar', 'baz'], [23, 43] );

デフォルト値を示すために、その列を渡さないでください。

# INSERT INTO foo ('bar') VALUES ( 23 )
# 'baz' will use its default
insert( $dbh, "foo", ['bar'], [23] );

これを最適化して、1つのサブルーチン呼び出しと1つのプリペアドステートメントで複数の挿入を実行し、クライアント側(およびプリペアドハンドルをサポートしている場合はデータベース側)でCPUを節約できます。

sub insert {
    my($dbh, $table, $fields, @rows) = @_;

    my $q_table      = $dbh->quote($table);
    my @q_fields     = map { $dbh->quote($_) } @$fields;
    my @placeholders = map { "?" } @q_fields;

    my $sql = qq{
        INSERT INTO $q_table
               ( @{[ join(', ', @q_fields)    ]} )
        VALUES ( @{[ join(', ', @placeholders ]} )
    };

    my $sth = $dbh->prepare_cached($sql);
    for my $values (@rows) {
        $sth->execute(@$values);
    }
}

# INSERT INTO foo ('bar', 'baz') VALUES ( 23, 42 )
# INSERT INTO foo ('bar', 'baz') VALUES ( 99, 12 )
insert( $dbh, "foo", ['bar', 'baz'], [23, 43], [99, 12] );

最後に、1つのステートメントで複数の値を渡す一括挿入を記述できます。これは、挿入の大きなグループを実行するためのおそらく最も効率的な方法です。これは、列の固定セットを用意し、DEFAULTマーカーを渡すと便利な場合です。スカラー参照として渡された値が生のSQL値として扱われるというイディオムを採用しました。これで、好きなものを渡す柔軟性があります。

sub insert {
    my($dbh, $table, $fields, @rows) = @_;

    my $q_table      = $dbh->quote($table);
    my @q_fields     = map { $dbh->quote($_) } @$fields;

    my $sql = qq{
        INSERT INTO $q_table
               ( @{[ join(', ', @q_fields)    ]} )
        VALUES
    };

    # This would be more elegant building an array and then joining it together
    # on ",\n", but that would double the memory usage and there might be
    # a lot of values.
    for my $values (@rows) {
        $sql .= "( ";
        # Scalar refs are treated as bare SQL.
        $sql .= join ", ", map { ref $value ? $$_ : $dbh->quote($_) } @$values;
        $sql .= "),\n";
    }
    $sql =~ s{,\n$}{};

    return $dbh->do($sql);
}

# INSERT INTO foo ('bar', 'baz') VALUES ( 23, NOW ), ( DEFAULT, 12 )
insert( $dbh, "foo", ['bar', 'baz'], [23, \"NOW"], [\"DEFAULT", 12] );

欠点は、これがメモリ内に文字列を構築することです。おそらく非常に大きくなります。これを回避するには、ファイル構文からデータベース固有の一括挿入を行う必要があります。

このSQL生成のすべてを自分で作成するのではなく、@ emazepの回答を使用して、SQL::AbstractとSQL::Abstract :: Plugin::InsertMultiを使用してください。

プロファイルを作成してください。

于 2012-10-04T20:28:37.923 に答える