sql >> データベース >  >> RDS >> Sqlserver

最短時間で1,000万件のレコードを挿入するにはどうすればよいですか?

    しない 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>を介して行をSQL​​Serverに送信します。 および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;
    }
    

    追記:

    1. いくつかの変更を加えると、上記のC#コードをデータのバッチ処理に適合させることができます。
    2. わずかな変更を加えるだけで、上記のC#コードを複数のフィールドで送信するように適合させることができます(上記のリンク先の「SteamingData ...」の記事に示されている例は、2つのフィールドを通過します)。
    3. SELECTの各レコードの値を操作することもできます 手続きのステートメント。
    4. プロシージャでWHERE条件を使用して、行を除外することもできます。
    5. TVPテーブル変数には複数回アクセスできます。読み取り専用ですが、「転送専用」ではありません。
    6. SqlBulkCopyよりも優れている点 :
      1. SqlBulkCopy はINSERTのみですが、TVPを使用すると、データを任意の方法で使用できます。MERGEを呼び出すことができます。; DELETEできます ある条件に基づく;データを複数のテーブルに分割できます。など。
      2. TVPはINSERTのみではないため、データをダンプするための個別のステージングテーブルは必要ありません。
      3. ExecuteReaderを呼び出すと、データベースからデータを取得できます。 ExecuteNonQueryの代わりに 。たとえば、IDENTITYがあった場合 DATAsのフィールド テーブルをインポートすると、OUTPUTを追加できます INSERTの句 INSERTED.[ID]を返します (IDを想定 IDENTITYの名前です 分野)。または、Reader.NextResult()を介して複数の結果セットを送信およびアクセスできるため、完全に異なるクエリの結果を返すか、またはその両方を返すことができます。 。 SqlBulkCopyを使用している場合、データベースから情報を取得することはできません。 しかし、ここS.Oにはいくつかの質問があります。 (少なくとも新しく作成されたIDENTITYに関しては)それを正確に実行したい人の 値)。
      4. ディスクからSQLServerへのデータの取得が少し遅い場合でも、プロセス全体で高速になることがある理由の詳細については、SQL Serverカスタマーアドバイザリーチームのこのホワイトペーパー:TVPによるスループットの最大化>


    1. PostgreSQLは2つのSQLServerストアドプロシージャ間の結合を実行できますか?

    2. 列のデフォルト値としてのシーケンス

    3. MySQLWorkbenchでデータベース図を作成する

    4. 条件に基づいて、別のテーブルの列で列の値を更新します