sql >> Databasteknik >  >> RDS >> Sqlserver

Skicka Dictionary till lagrad procedur T-SQL

Det accepterade svaret att använda en TVP är i allmänhet korrekt, men behöver förtydligas baserat på mängden data som skickas in. Att använda en DataTable är bra (för att inte tala snabbt och enkelt) för mindre uppsättningar data, men för större uppsättningar gör det det. inte skalas med tanke på att den duplicerar datamängden genom att placera den i datatabellen helt enkelt för att skicka den till SQL Server. Så för större uppsättningar data finns det ett alternativ att streama innehållet i valfri anpassad samling. Det enda verkliga kravet är att du måste definiera strukturen i termer av SqlDb-typer och iterera genom samlingen, som båda är ganska triviala steg.

En förenklad översikt över den minimala strukturen visas nedan, som är en anpassning av svaret jag postade på Hur kan jag infoga 10 miljoner poster på kortast möjliga tid?, som handlar om att importera data från en fil och därför är något annorlunda då data finns för närvarande inte i minnet. Som du kan se av koden nedan är den här installationen inte alltför komplicerad men ändå mycket flexibel samt effektiv och skalbar.

SQL-objekt # 1:Definiera strukturen

-- First: You need a User-Defined Table Type
CREATE TYPE dbo.IDsAndOrderNumbers AS TABLE
(
   ID NVARCHAR(4000) NOT NULL,
   SortOrderNumber INT NOT NULL
);
GO

SQL-objekt # 2:Använd strukturen

-- Second: Use the UDTT as an input param to an import proc.
--         Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
   @ImportTable    dbo.IDsAndOrderNumbers READONLY
)
AS
SET NOCOUNT ON;

-- maybe clear out the table first?
TRUNCATE TABLE SchemaName.TableName;

INSERT INTO SchemaName.TableName (ID, SortOrderNumber)
    SELECT  tmp.ID,
            tmp.SortOrderNumber
    FROM    @ImportTable tmp;

-- OR --

some other T-SQL

-- optional return data
SELECT @NumUpdates AS [RowsUpdated],
       @NumInserts AS [RowsInserted];
GO

C#-kod, del 1:Definiera iteratorn/avsändaren

using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;

private static IEnumerable<SqlDataRecord> SendRows(Dictionary<string,int> RowData)
{
   SqlMetaData[] _TvpSchema = new SqlMetaData[] {
      new SqlMetaData("ID", SqlDbType.NVarChar, 4000),
      new SqlMetaData("SortOrderNumber", SqlDbType.Int)
   };
   SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
   StreamReader _FileReader = null;

      // read a row, send a row
      foreach (KeyValuePair<string,int> _CurrentRow in RowData)
      {
         // 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 an
         // object, do manipulation(s) / validation(s) on the object, then pass
         // the object to the DB or discard via "continue" if invalid.
         _DataRecord.SetString(0, _CurrentRow.ID);
         _DataRecord.SetInt32(1, _CurrentRow.sortOrderNumber);

         yield return _DataRecord;
      }
}

C#-kod, del 2:Använd iteratorn/avsändaren

public static void LoadData(Dictionary<string,int> MyCollection)
{
   SqlConnection _Connection = new SqlConnection("{connection string}");
   SqlCommand _Command = new SqlCommand("ImportData", _Connection);
   SqlDataReader _Reader = null; // only needed if getting data back from proc call

   SqlParameter _TVParam = new SqlParameter();
   _TVParam.ParameterName = "@ImportTable";
// _TVParam.TypeName = "IDsAndOrderNumbers"; //optional for CommandType.StoredProcedure
   _TVParam.SqlDbType = SqlDbType.Structured;
   _TVParam.Value = SendRows(MyCollection); // method return value is streamed data
   _Command.Parameters.Add(_TVParam);
   _Command.CommandType = CommandType.StoredProcedure;

   try
   {
      _Connection.Open();

      // Either send the data and move on with life:
      _Command.ExecuteNonQuery();
      // OR, to get data back from a SELECT or OUTPUT clause:
      SqlDataReader _Reader = _Command.ExecuteReader();
      {
       Do something with _Reader: If using INSERT or MERGE in the Stored Proc, use an
       OUTPUT clause to return INSERTED.[RowNum], INSERTED.[ID] (where [RowNum] is an
       IDENTITY), then fill a new Dictionary<string, int>(ID, RowNumber) from
       _Reader.GetString(0) and _Reader.GetInt32(1). Return that instead of void.
      }
   }
   finally
   {
      _Reader.Dispose(); // optional; needed if getting data back from proc call
      _Command.Dispose();
      _Connection.Dispose();
   }
}


  1. SQL Server 2005 ROW_NUMBER() utan ORDER BY

  2. PostgreSQL sammansatt primärnyckel

  3. Kan SQL Server skicka en webbförfrågan?

  4. Avancerad SQL:Variationer och olika användningsfall av T-SQL Insert Statement