しない DataTable
を作成します BulkCopyを介してロードします。これは、データセットが小さい場合は問題ないソリューションですが、データベースを呼び出す前に1,000万行すべてをメモリにロードする理由はまったくありません。
あなたの最善の策(BCP
以外) / BULK INSERT
/ OPENROWSET(BULK...)
)は、テーブル値パラメーター(TVP)を介してファイルからデータベースにコンテンツをストリーミングすることです。 TVPを使用すると、ファイルを開き、行を読み取り、完了するまで行を送信してから、ファイルを閉じることができます。このメソッドのメモリフットプリントは1行だけです。このシナリオの例が記載された記事「アプリケーションからSQLServer2008へのデータのストリーミング」を作成しました。
構造の簡単な概要は次のとおりです。上記の質問に示されているのと同じインポートテーブルとフィールド名を想定しています。
必要なデータベースオブジェクト:
-- First: You need a User-Defined Table Type
CREATE TYPE ImportStructure AS TABLE (Field VARCHAR(MAX));
GO
-- Second: Use the UDTT as an input param to an import proc.
-- Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
@ImportTable dbo.ImportStructure READONLY
)
AS
SET NOCOUNT ON;
-- maybe clear out the table first?
TRUNCATE TABLE dbo.DATAs;
INSERT INTO dbo.DATAs (DatasField)
SELECT Field
FROM @ImportTable;
GO
上記のSQLオブジェクトを利用するためのC#アプリコードは以下のとおりです。オブジェクト(DataTableなど)を埋めてからストアドプロシージャを実行するのではなく、このメソッドでファイルの内容の読み取りを開始するのはストアドプロシージャの実行であることに注意してください。ストアドプロシージャの入力パラメータは変数ではありません。これは、メソッドGetFileContents
の戻り値です。 。このメソッドは、SqlCommand
のときに呼び出されます。 ExecuteNonQuery
を呼び出します 、ファイルを開き、行を読み取り、IEnumerable<SqlDataRecord>
を介して行をSQLServerに送信します。 およびyield return
構築し、ファイルを閉じます。ストアドプロシージャは、データの受信が開始されるとすぐにアクセスできるテーブル変数@ImportTableを確認するだけです(注:データは、完全なコンテンツでなくても、tempdbに短時間保持されます em> 。
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;
private static IEnumerable<SqlDataRecord> GetFileContents()
{
SqlMetaData[] _TvpSchema = new SqlMetaData[] {
new SqlMetaData("Field", SqlDbType.VarChar, SqlMetaData.Max)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
StreamReader _FileReader = null;
try
{
_FileReader = new StreamReader("{filePath}");
// read a row, send a row
while (!_FileReader.EndOfStream)
{
// You shouldn't need to call "_DataRecord = new SqlDataRecord" as
// SQL Server already received the row when "yield return" was called.
// Unlike BCP and BULK INSERT, you have the option here to create a string
// call ReadLine() into the string, do manipulation(s) / validation(s) on
// the string, then pass that string into SetString() or discard if invalid.
_DataRecord.SetString(0, _FileReader.ReadLine());
yield return _DataRecord;
}
}
finally
{
_FileReader.Close();
}
}
GetFileContents
上記のメソッドは、以下に示すように、ストアドプロシージャの入力パラメータ値として使用されます。
public static void test()
{
SqlConnection _Connection = new SqlConnection("{connection string}");
SqlCommand _Command = new SqlCommand("ImportData", _Connection);
_Command.CommandType = CommandType.StoredProcedure;
SqlParameter _TVParam = new SqlParameter();
_TVParam.ParameterName = "@ImportTable";
_TVParam.TypeName = "dbo.ImportStructure";
_TVParam.SqlDbType = SqlDbType.Structured;
_TVParam.Value = GetFileContents(); // return value of the method is streamed data
_Command.Parameters.Add(_TVParam);
try
{
_Connection.Open();
_Command.ExecuteNonQuery();
}
finally
{
_Connection.Close();
}
return;
}
追記:
- いくつかの変更を加えると、上記のC#コードをデータのバッチ処理に適合させることができます。
- わずかな変更を加えるだけで、上記のC#コードを複数のフィールドで送信するように適合させることができます(上記のリンク先の「SteamingData ...」の記事に示されている例は、2つのフィールドを通過します)。
-
SELECT
の各レコードの値を操作することもできます 手続きのステートメント。 - プロシージャでWHERE条件を使用して、行を除外することもできます。
- TVPテーブル変数には複数回アクセスできます。読み取り専用ですが、「転送専用」ではありません。
-
SqlBulkCopy
よりも優れている点 :-
SqlBulkCopy
はINSERTのみですが、TVPを使用すると、データを任意の方法で使用できます。MERGE
を呼び出すことができます。;DELETE
できます ある条件に基づく;データを複数のテーブルに分割できます。など。 - TVPはINSERTのみではないため、データをダンプするための個別のステージングテーブルは必要ありません。
-
ExecuteReader
を呼び出すと、データベースからデータを取得できます。ExecuteNonQuery
の代わりに 。たとえば、IDENTITY
があった場合DATAs
のフィールド テーブルをインポートすると、OUTPUT
を追加できますINSERT
の句INSERTED.[ID]
を返します (ID
を想定IDENTITY
の名前です 分野)。または、Reader.NextResult()
を介して複数の結果セットを送信およびアクセスできるため、完全に異なるクエリの結果を返すか、またはその両方を返すことができます。 。SqlBulkCopy
を使用している場合、データベースから情報を取得することはできません。 しかし、ここS.Oにはいくつかの質問があります。 (少なくとも新しく作成されたIDENTITY
に関しては)それを正確に実行したい人の 値)。 - ディスクからSQLServerへのデータの取得が少し遅い場合でも、プロセス全体で高速になることがある理由の詳細については、SQL Serverカスタマーアドバイザリーチームのこのホワイトペーパー:TVPによるスループットの最大化 >
-