web-dev-qa-db-ja.com

DataTableバルクの値全体をpostgreSQLテーブルに挿入します

SQLでは、データテーブルへの一括挿入に対して次のようなことを行います

SqlBulkCopy copy = new SqlBulkCopy(sqlCon);
copy.DestinationTableName = strDestinationTable;            
copy.WriteToServer(dtFrom);

Blockquote

しかしPostgreSQLではこの操作を行う方法

20
Karan Singh

パラメータを使用した簡単な挿入

プロジェクトは次のアセンブリを参照する必要があります:Npgsql。この参照がVisual Studio内に表示されない場合、次のようになります。

  1. コネクタのインストールフォルダを参照します
  2. 実行:_GACInstall.exe_
  3. Visual Studioを再起動します。

サンプルテーブル

_CREATE TABLE "OrderHistory"
(
  "OrderId" bigint NOT NULL,
  "TotalAmount" bigint,
  CONSTRAINT "OrderIdPk" PRIMARY KEY ("OrderId")
)
WITH (
  OIDS=FALSE
);
ALTER TABLE "OrderHistory"
  OWNER TO postgres;
GRANT ALL ON TABLE "OrderHistory" TO postgres;
GRANT ALL ON TABLE "OrderHistory" TO public;
ALTER TABLE "OrderHistory" ALTER COLUMN "OrderId" SET (n_distinct=1);

GRANT SELECT("OrderId"), UPDATE("OrderId"), INSERT("OrderId"), REFERENCES("OrderId") ON "OrderHistory" TO public;
GRANT SELECT("TotalAmount"), UPDATE("TotalAmount"), INSERT("TotalAmount"), REFERENCES("TotalAmount") ON "OrderHistory" TO public;
_

サンプルコード

必ず次のディレクティブを使用してください。

_using Npgsql;
using NpgsqlTypes;
_

メソッドに次のソースコードを入力します。

_// Make sure that the user has the INSERT privilege for the OrderHistory table.
NpgsqlConnection connection = new NpgsqlConnection("PORT=5432;TIMEOUT=15;POOLING=True;MINPOOLSIZE=1;MAXPOOLSIZE=20;COMMANDTIMEOUT=20;COMPATIBLE=2.2.4.3;DATABASE=test;Host=127.0.0.1;PASSWORD=test;USER ID=test");

connection.Open();

DataSet dataSet = new DataSet();

NpgsqlDataAdapter dataAdapter = new NpgsqlDataAdapter("select * from OrderHistory where OrderId=-1", connection);
dataAdapter.InsertCommand = new NpgsqlCommand("insert into OrderHistory(OrderId, TotalAmount) " +
                        " values (:a, :b)", connection);
dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("a", NpgsqlDbType.Bigint));
dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("b", NpgsqlDbType.Bigint));
dataAdapter.InsertCommand.Parameters[0].Direction = ParameterDirection.Input;
dataAdapter.InsertCommand.Parameters[1].Direction = ParameterDirection.Input;
dataAdapter.InsertCommand.Parameters[0].SourceColumn = "OrderId";
dataAdapter.InsertCommand.Parameters[1].SourceColumn = "TotalAmount";

dataAdapter.Fill(dataSet);

DataTable newOrders = dataSet.Tables[0];
DataRow newOrder = newOrders.NewRow();
newOrder["OrderId"] = 20;
newOrder["TotalAmount"] = 20.0;

newOrders.Rows.Add(newOrder);
DataSet ds2 = dataSet.GetChanges();
dataAdapter.Update(ds2);
dataSet.Merge(ds2);
dataSet.AcceptChanges();

connection.Close();
_

パフォーマンスについての考え

最初の投稿では、パフォーマンス要件についての言及はありませんでした。ソリューションは次の条件を満たす必要があります。

  1. DataTableを使用して挿入
  2. ループを使用せずにデータを挿入する

大量のデータを挿入する場合は、パフォーマンスオプションを確認することをお勧めします。 Postgresのドキュメントでは、次のことを推奨しています。

  • 自動コミットを無効にする
  • COPYコマンドを使用する
  • インデックスを削除する
  • 外部キー制約を削除する
  • 等.

Postgresインサートの最適化の詳細については、以下をご覧ください。

また、システムのパフォーマンスに影響を与える可能性のある他の多くの要因があります。高レベルの概要については、以下をご覧ください。

その他のオプション

  • .NETコネクタはPostgresCopyコマンドをサポートしていますか?
    • そうでない場合は、Npgsqlコネクタの ソースコード をダウンロードして、独自のBulkCopy()メソッドを追加できます。最初にソースコードのライセンス契約を必ず確認してください。
  • PostgresTable Value Parametersをサポートしているかどうかを確認します。
    • このアプローチでは、テーブルをPostgres関数に渡すことができます。この関数は、データを宛先に直接挿入できます。
  • 必要な機能を含むベンダーからPostgres.NETコネクタを購入します。

その他の参考資料

7
Pressacco

同じ問題が少し前にあります。まだ「すぐに使える」ソリューションはないようです。

私は this を読んでその時点で同様のソリューションを構築し、今日まで生産的に使用しています。 STDINからファイルを読み取るテキストクエリに基づいています。 ADO.NET Postgreデータプロバイダー Npgsql を使用します。 DataTableに基づいて大きな文字列(または一時ファイル、メモリ使用の原因)を作成し、それをCOPYコマンドでテキストクエリとして使用できます。私たちの場合、インサーティーチングローよりもはるかに高速でした。

多分これは完全な解決策ではないかもしれませんが、開始するための良いポイントとそれについて私が知っていることなら何でもかもしれません。 :)

3
komaflash

また、「すぐに使える」ソリューションはまだないこともわかりました。おそらく、あなたが私がこの問題のために作成した小さなヘルパーを説明する私の別の回答をチェックして、別のヘルパーを本当に簡単に利用することができます: https://stackoverflow.com/a/46063313/6654362 Iそれが現在最良の解決策だと思います。投稿が死亡した場合に備えて、リンクから解決策を投稿しました。

編集:最近同様の問題が発生しましたが、Postgresqlを使用していました。効果的な一括挿入を使用したかったのですが、かなり難しいことがわかりました。このDBで適切な無料のライブラリを見つけることができません。私はこのヘルパーだけを見つけました: https://bytefish.de/blog/postgresql_bulk_insert/ これもNugetにあります。 Entity Frameworkの方法でプロパティを自動マッピングする小さなマッパーを作成しました。

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
        {
            var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
            var properties = typeof(T).GetProperties();
            foreach(var prop in properties)
            {
                var type = prop.PropertyType;
                if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
                    continue;
                switch (type)
                {
                    case Type intType when intType == typeof(int) || intType == typeof(int?):
                        {
                            helper = helper.MapInteger("\"" + prop.Name + "\"",  x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type stringType when stringType == typeof(string):
                        {
                            helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
                        {
                            helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
                        {
                            helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
                        {
                            helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
                        {
                            helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type guidType when guidType == typeof(Guid):
                        {
                            helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                }
            }
            return helper;
        }

私はそれを次のように使用しています(Undertakeという名前のエンティティがありました)。

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));

トランザクションの例を示しましたが、コンテキストから取得した通常の接続でも実行できます。 undertakeingsToAddは通常のエンティティレコードを列挙できます。これをDBにbulkInsertします。

このソリューションは、数時間の研究と試行の結果得られたものであり、はるかに速く、最終的に使いやすく、無料であることが期待できるものです。上記の理由だけでなく、Postgresql自体で問題がなかった唯一のソリューションであるため、このソリューションを使用することをお勧めします。他の多くのソリューションは、SqlServerなどで問題なく動作します。

2
Michał Pilarek