Übergeben von Array-Parametern an eine gespeicherte Prozedur

53

Ich habe einen Prozess, der eine Reihe von Datensätzen (1000) erfasst und verarbeitet. Wenn ich fertig bin, muss ich eine große Anzahl von Datensätzen als verarbeitet markieren. Ich kann dies mit einer großen Liste von IDs anzeigen. Ich versuche, das Muster "Aktualisierungen in einer Schleife" zu vermeiden, daher möchte ich einen effizienteren Weg finden, um diese IDs in einen gespeicherten MS SQL Server 2008-Prozess zu senden.

Vorschlag Nr. 1 - Tabellenwerte. Ich kann einen Tabellentyp mit nur einem ID-Feld definieren und eine Tabelle mit IDs zum Aktualisieren senden.

Angebot Nr. 2 - XML-Parameter (varchar) mit OPENXML () im Proc-Body.

Vorschlag Nr. 3 - Listenanalyse. Ich möchte dies nach Möglichkeit lieber vermeiden, da es unhandlich und fehleranfällig erscheint.

Irgendeine Präferenz unter diesen oder irgendwelche Ideen, die ich vermisst habe?

D. Lambert
quelle
Wie kommst du auf die große Liste der IDs?
Larry Coleman
Ich ziehe sie zusammen mit "Payload" -Daten über einen anderen gespeicherten Prozess herunter. Ich muss jedoch nicht alle diese Daten aktualisieren. Aktualisieren Sie einfach eine Markierung in bestimmten Datensätzen.
D. Lambert

Antworten:

42

Die besten Artikel zu diesem Thema stammen von Erland Sommarskog:

Er deckt alle Möglichkeiten ab und erklärt ziemlich gut.

Entschuldigen Sie die Kürze der Antwort, aber Erlands Artikel über Arrays ist wie Joe Celkos Bücher über Bäume und andere SQL-Leckerbissen :)

Marian
quelle
23

Auf StackOverflow gibt es eine große Diskussion darüber , die viele Ansätze abdeckt. Für SQL Server 2008+ bevorzuge ich die Verwendung tabellenwertiger Parameter . Dies ist im Wesentlichen die Lösung für Ihr Problem mit SQL Server - die Übergabe einer Werteliste an eine gespeicherte Prozedur.

Die Vorteile dieses Ansatzes sind:

  • Führen Sie einen Stored Procedure-Aufruf mit allen Daten aus, die als 1 Parameter übergeben wurden
  • Tabelleneingabe ist strukturiert und stark typisiert
  • Kein Erstellen / Parsen von Strings oder Umgang mit XML
  • Mit der Tabelleneingabe können Sie einfach filtern, verknüpfen oder was auch immer

Beachten Sie jedoch Folgendes : Wenn Sie eine gespeicherte Prozedur aufrufen, die TVPs über ADO.NET oder ODBC verwendet, und sich die Aktivität mit SQL Server Profiler ansehen, werden Sie feststellen, dass SQL Server mehrere INSERTAnweisungen zum Laden des TVP empfängt , eine für jede Zeile im TVP , gefolgt vom Aufruf der Prozedur. Dies ist beabsichtigt . Dieser Stapel von INSERTs muss jedes Mal kompiliert werden, wenn die Prozedur aufgerufen wird, und stellt einen kleinen Overhead dar. Doch selbst mit diesem Kopf, TVPs noch wegblasen andere Ansätze in Bezug auf Leistung und Benutzerfreundlichkeit für die meisten Anwendungsfälle.

Wenn Sie mehr erfahren möchten, hat Erland Sommarskog die volle dünn wie Tabellenwertparametern arbeiten und mehrere Beispiele.

Hier ist ein weiteres Beispiel, das ich zusammengestellt habe:

CREATE TYPE id_list AS TABLE (
    id int NOT NULL PRIMARY KEY
);
GO

CREATE PROCEDURE [dbo].[tvp_test] (
      @param1           INT
    , @customer_list    id_list READONLY
)
AS
BEGIN
    SELECT @param1 AS param1;

    -- join, filter, do whatever you want with this table 
    -- (other than modify it)
    SELECT *
    FROM @customer_list;
END;
GO

DECLARE @customer_list id_list;

INSERT INTO @customer_list (
    id
)
VALUES (1), (2), (3), (4), (5), (6), (7);

EXECUTE [dbo].[tvp_test]
      @param1 = 5
    , @customer_list = @customer_list
;
GO

DROP PROCEDURE dbo.tvp_test;
DROP TYPE id_list;
GO
Nick Chammas
quelle
Wenn ich dies ausführe, wird eine Fehlermeldung angezeigt: Meldung 2715, Ebene 16, Status 3, Prozedur tvp_test, Zeile 4 [Stapelstartzeile 4] Spalte, Parameter oder Variable Nr. 2: Der Datentyp id_list wurde nicht gefunden. Der Parameter oder die Variable '@customer_list' hat einen ungültigen Datentyp. Meldung 1087, Ebene 16, Status 1, Prozedur tvp_test, Zeile 13 [Stapelstartzeile 4] Die Tabellenvariable "@customer_list" muss deklariert werden.
Damian
@ Damian - Wurde die CREATE TYPEAnweisung am Anfang erfolgreich ausgeführt? Welche Version von SQL Server verwenden Sie?
Nick Chammas
Im SP-Code steht dieser Satz in der Zeile SELECT @ param1 AS param1; . Was ist der Zweck? Sie verwenden oder param1 nicht. Warum haben Sie dies als Parameter in den SP-Header eingefügt?
EAmez
@EAmez - Es war nur ein willkürliches Beispiel. Der Punkt ist @customer_listnicht @param1. Das Beispiel zeigt einfach, dass Sie verschiedene Parametertypen mischen können.
Nick Chammas
21

Das gesamte Thema wird in dem endgültigen Artikel von Erland Sommarskog behandelt: "Arrays und Liste in SQL Server" . Wählen Sie die gewünschte Version aus.

Zusammenfassung, für Pre- SQL Server 2008, wo TVPs den Rest übertrumpfen

  • CSV, teilen Sie, wie Sie möchten (ich verwende im Allgemeinen eine Zahlentabelle)
  • XML und Parsen (besser mit SQL Server 2005+)
  • Erstellen Sie eine temporäre Tabelle auf dem Client

Der Artikel ist auf jeden Fall eine Lektüre wert, um andere Techniken und Denkweisen kennenzulernen.

Bearbeiten: späte Antwort für große Listen an anderer Stelle: Übergeben von Array-Parametern an eine gespeicherte Prozedur

gbn
quelle
14

Ich weiß, dass ich zu spät zu dieser Party komme, aber ich hatte in der Vergangenheit ein derartiges Problem: Ich musste bis zu 100.000 Bigint-Nummern senden und habe einige Benchmarks durchgeführt. Am Ende haben wir sie im Binärformat als Bild gesendet - das war für bis zu 100.000 Zahlen schneller als alles andere.

Hier ist mein alter Code (SQL Server 2005):

SELECT  Number * 8 + 1 AS StartFrom ,
        Number * 8 + 8 AS MaxLen
INTO    dbo.ParsingNumbers
FROM    dbo.Numbers
GO

CREATE FUNCTION dbo.ParseImageIntoBIGINTs ( @BIGINTs IMAGE )
RETURNS TABLE
AS RETURN
    ( SELECT    CAST(SUBSTRING(@BIGINTs, StartFrom, 8) AS BIGINT) Num
      FROM      dbo.ParsingNumbers
      WHERE     MaxLen <= DATALENGTH(@BIGINTs)
    )
GO

Der folgende Code packt ganze Zahlen in einen binären Blob. Ich kehre die Reihenfolge der Bytes hier um:

static byte[] UlongsToBytes(ulong[] ulongs)
{
int ifrom = ulongs.GetLowerBound(0);
int ito   = ulongs.GetUpperBound(0);
int l = (ito - ifrom + 1)*8;
byte[] ret = new byte[l];
int retind = 0;
for(int i=ifrom; i<=ito; i++)
{
ulong v = ulongs[i];
ret[retind++] = (byte) (v >> 0x38);
ret[retind++] = (byte) (v >> 0x30);
ret[retind++] = (byte) (v >> 40);
ret[retind++] = (byte) (v >> 0x20);
ret[retind++] = (byte) (v >> 0x18);
ret[retind++] = (byte) (v >> 0x10);
ret[retind++] = (byte) (v >> 8);
ret[retind++] = (byte) v;
}
return ret;
}
AK
quelle
9

Ich bin hin- und hergerissen, ob ich Sie auf SO verweise oder hier beantworte, denn dies ist fast eine Programmierfrage. Aber da ich schon eine Lösung habe, die ich benutze ... werde ich das posten;)

Die Art und Weise, wie dies funktioniert, besteht darin, dass Sie eine durch Kommas getrennte Zeichenfolge (einfache Aufteilung, keine CSV-Aufteilungen) in die gespeicherte Prozedur als varchar (4000) einspeisen und diese Liste dann in diese Funktion einspeisen und eine handliche Tabelle zurückgeben. eine Tabelle nur varchars.

Auf diese Weise können Sie nur die Werte der zu verarbeitenden IDs senden und an dieser Stelle einen einfachen Join ausführen.

Alternativ können Sie mit einer CLR-Datentabelle etwas anfangen und dies einspeisen, aber das ist ein bisschen mehr Aufwand für die Unterstützung, und jeder versteht CSV-Listen.

USE [Database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER FUNCTION [dbo].[splitListToTable] (@list      nvarchar(MAX), @delimiter nchar(1) = N',')
      RETURNS @tbl TABLE (value     varchar(4000)      NOT NULL) AS
/*
http://www.sommarskog.se/arrays-in-sql.html
This guy is apparently THE guy in SQL arrays and lists 

Need an easy non-dynamic way to split a list of strings on input for comparisons

Usage like thus:

DECLARE @sqlParam VARCHAR(MAX)
SET @sqlParam = 'a,b,c'

SELECT * FROM (

select 'a' as col1, '1' as col2 UNION
select 'a' as col1, '2' as col2 UNION
select 'b' as col1, '3' as col2 UNION
select 'b' as col1, '4' as col2 UNION
select 'c' as col1, '5' as col2 UNION
select 'c' as col1, '6' as col2 ) x 
WHERE EXISTS( SELECT value FROM splitListToTable(@sqlParam,',') WHERE x.col1 = value )

*/
BEGIN
   DECLARE @endpos   int,
           @startpos int,
           @textpos  int,
           @chunklen smallint,
           @tmpstr   nvarchar(4000),
           @leftover nvarchar(4000),
           @tmpval   nvarchar(4000)

   SET @textpos = 1
   SET @leftover = ''
   WHILE @textpos <= datalength(@list) / 2
   BEGIN
      SET @chunklen = 4000 - datalength(@leftover) / 2
      SET @tmpstr = @leftover + substring(@list, @textpos, @chunklen)
      SET @textpos = @textpos + @chunklen

      SET @startpos = 0
      SET @endpos = charindex(@delimiter, @tmpstr)

      WHILE @endpos > 0
      BEGIN
         SET @tmpval = ltrim(rtrim(substring(@tmpstr, @startpos + 1,
                                             @endpos - @startpos - 1)))
         INSERT @tbl (value) VALUES(@tmpval)
         SET @startpos = @endpos
         SET @endpos = charindex(@delimiter, @tmpstr, @startpos + 1)
      END

      SET @leftover = right(@tmpstr, datalength(@tmpstr) / 2 - @startpos)
   END

   INSERT @tbl(value) VALUES (ltrim(rtrim(@leftover)))
   RETURN
END
jcolebrand
quelle
Nun, ich habe speziell versucht, die durch Kommas getrennte Liste zu vermeiden, damit ich so etwas nicht schreiben muss, aber da es bereits geschrieben ist, würde ich diese Lösung wohl wieder in die Mischung werfen müssen. ;-)
D. Lambert
1
Ich sage versucht und wahr ist am einfachsten. Sie können eine durch Kommas getrennte Liste in C # in Sekunden ausspucken und Sie können sie schnell genug in diese Funktion werfen (nachdem Sie sie in Ihren Sproc aufgenommen haben), und Sie müssen kaum darüber nachdenken. ~ Und ich weiß, dass Sie gesagt haben, Sie wollten keine Funktion verwenden, aber ich denke, es ist der einfachste Weg (vielleicht nicht der effektivste)
jcolebrand
5

Ich erhalte regelmäßig Sätze von 1000 Zeilen und 10000 Zeilen, die von unserer Anwendung gesendet wurden, um von verschiedenen gespeicherten SQL Server-Prozeduren verarbeitet zu werden.

Um die Leistungsanforderungen zu erfüllen, verwenden wir TVPs. Sie müssen jedoch eine eigene Zusammenfassung des dbDataReader implementieren, um einige Leistungsprobleme im Standardverarbeitungsmodus zu beheben. Ich werde nicht auf die Fragen eingehen, da sie für diese Anfrage nicht in Frage kommen.

Ich habe die XML-Verarbeitung nicht in Betracht gezogen, da ich keine XML-Implementierung gefunden habe, die mit mehr als 10.000 "Zeilen" performant bleibt.

Die Listenverarbeitung kann durch die Verarbeitung von eindimensionalen und zweidimensionalen Tally-Tabellen (Zahlen) erfolgen. Wir haben diese erfolgreich in verschiedenen Bereichen eingesetzt, aber gut verwaltete TVPs sind leistungsfähiger, wenn es mehr als ein paar hundert "Zeilen" gibt.

Wie bei allen Entscheidungen in Bezug auf die SQL Server-Verarbeitung müssen Sie Ihre Auswahl basierend auf dem Verwendungsmodell treffen.

Robert Miller
quelle
5

Endlich hatte ich die Möglichkeit, einige TableValuedParameters zu erstellen, und sie funktionieren hervorragend. Daher füge ich einen ganzen Haufen Code ein, der zeigt, wie ich sie verwende. Ein Beispiel aus einem Teil meines aktuellen Codes ist: (Anmerkung: Wir verwenden ADO .NETZ)

Beachten Sie auch: Ich schreibe Code für einen Dienst und habe viele vordefinierte Codebits in der anderen Klasse, aber ich schreibe dies als Konsolen-App, damit ich es debuggen kann die Konsolen-App. Entschuldigen Sie meinen Codierungsstil (wie fest codierte Verbindungszeichenfolgen), da er sozusagen "eine zum Wegwerfen" war. Ich wollte zeigen, wie ich a verwende List<customObject>und es einfach als Tabelle in die Datenbank schiebe, die ich in der gespeicherten Prozedur verwenden kann. C # - und TSQL-Code unten:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using a;

namespace a.EventAMI {
    class Db {
        private static SqlCommand SqlCommandFactory(string sprocName, SqlConnection con) { return new SqlCommand { CommandType = CommandType.StoredProcedure, CommandText = sprocName, CommandTimeout = 0, Connection = con }; }

        public static void Update(List<Current> currents) {
            const string CONSTR = @"just a hardwired connection string while I'm debugging";
            SqlConnection con = new SqlConnection( CONSTR );

            SqlCommand cmd = SqlCommandFactory( "sprocname", con );
            cmd.Parameters.Add( "@CurrentTVP", SqlDbType.Structured ).Value = Converter.GetDataTableFromIEnumerable( currents, typeof( Current ) ); //my custom converter class

            try {
                using ( con ) {
                    con.Open();
                    cmd.ExecuteNonQuery();
                }
            } catch ( Exception ex ) {
                ErrHandler.WriteXML( ex );
                throw;
            }
        }
    }
    class Current {
        public string Identifier { get; set; }
        public string OffTime { get; set; }
        public DateTime Off() {
            return Convert.ToDateTime( OffTime );
        }

        private static SqlCommand SqlCommandFactory(string sprocName, SqlConnection con) { return new SqlCommand { CommandType = CommandType.StoredProcedure, CommandText = sprocName, CommandTimeout = 0, Connection = con }; }

        public static List<Current> GetAll() {
            List<Current> l = new List<Current>();

            const string CONSTR = @"just a hardcoded connection string while I'm debugging";
            SqlConnection con = new SqlConnection( CONSTR );

            SqlCommand cmd = SqlCommandFactory( "sprocname", con );

            try {
                using ( con ) {
                    con.Open();
                    using ( SqlDataReader reader = cmd.ExecuteReader() ) {
                        while ( reader.Read() ) {
                            l.Add(
                                new Current {
                                    Identifier = reader[0].ToString(),
                                    OffTime = reader[1].ToString()
                                } );
                        }
                    }

                }
            } catch ( Exception ex ) {
                ErrHandler.WriteXML( ex );
                throw;
            }

            return l;
        }
    }
}

-------------------
the converter class
-------------------
using System;
using System.Collections;
using System.Data;
using System.Reflection;

namespace a {
    public static class Converter {
        public static DataTable GetDataTableFromIEnumerable(IEnumerable aIEnumerable) {
            return GetDataTableFromIEnumerable( aIEnumerable, null );
        }

        public static DataTable GetDataTableFromIEnumerable(IEnumerable aIEnumerable, Type baseType) {
            DataTable returnTable = new DataTable();

            if ( aIEnumerable != null ) {
                //Creates the table structure looping in the in the first element of the list
                object baseObj = null;

                Type objectType;

                if ( baseType == null ) {
                    foreach ( object obj in aIEnumerable ) {
                        baseObj = obj;
                        break;
                    }

                    objectType = baseObj.GetType();
                } else {
                    objectType = baseType;
                }

                PropertyInfo[] properties = objectType.GetProperties();

                DataColumn col;

                foreach ( PropertyInfo property in properties ) {
                    col = new DataColumn { ColumnName = property.Name };
                    if ( property.PropertyType == typeof( DateTime? ) ) {
                        col.DataType = typeof( DateTime );
                    } else if ( property.PropertyType == typeof( Int32? ) ) {
                        col.DataType = typeof( Int32 );
                    } else {
                        col.DataType = property.PropertyType;
                    }
                    returnTable.Columns.Add( col );
                }

                //Adds the rows to the table

                foreach ( object objItem in aIEnumerable ) {
                    DataRow row = returnTable.NewRow();

                    foreach ( PropertyInfo property in properties ) {
                        Object value = property.GetValue( objItem, null );
                        if ( value != null )
                            row[property.Name] = value;
                        else
                            row[property.Name] = "";
                    }

                    returnTable.Rows.Add( row );
                }
            }
            return returnTable;
        }

    }
}

USE [Database]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROC [dbo].[Event_Update]
    @EventCurrentTVP    Event_CurrentTVP    READONLY
AS

/****************************************************************
    author  cbrand
    date    
    descrip I'll ask you to forgive me the anonymization I've made here, but hope this helps
    caller  such and thus application
****************************************************************/

BEGIN TRAN Event_Update

DECLARE @DEBUG INT

SET @DEBUG = 0 /* test using @DEBUG <> 0 */

/*
    Replace the list of outstanding entries that are still currently disconnected with the list from the file
    This means remove all existing entries (faster to truncate and insert than to delete on a join and insert, yes?)
*/
TRUNCATE TABLE [database].[dbo].[Event_Current]

INSERT INTO [database].[dbo].[Event_Current]
           ([Identifier]
            ,[OffTime])
SELECT [Identifier]
      ,[OffTime]
  FROM @EventCurrentTVP

IF (@@ERROR <> 0 OR @DEBUG <> 0) 
BEGIN
ROLLBACK TRAN Event_Update
END
ELSE
BEGIN
COMMIT TRAN Event_Update
END

USE [Database]
GO

CREATE TYPE [dbo].[Event_CurrentTVP] AS TABLE(
    [Identifier] [varchar](20) NULL,
    [OffTime] [datetime] NULL
)
GO

Außerdem werde ich meinen Codierungsstil konstruktiv kritisieren, wenn Sie das zu bieten haben (für alle Leser, die auf diese Frage stoßen), aber bitte bleiben Sie konstruktiv;) ... Wenn Sie mich wirklich wollen, finden Sie mich hier im Chatroom . Hoffentlich kann man mit diesem Codeblock sehen, wie sie das verwenden können, List<Current>wie ich es als Tabelle in der Datenbank und List<T>in ihrer App definiert habe.

jcolebrand
quelle
3

Ich würde entweder mit Vorschlag Nr. 1 gehen oder alternativ eine Arbeitstabelle erstellen, die nur verarbeitete IDs enthält. Fügen Sie während der Verarbeitung etwas in diese Tabelle ein und rufen Sie dann, wenn Sie fertig sind, ein Verfahren wie das folgende auf:

BEGIN TRAN

UPDATE dt
SET processed = 1
FROM dataTable dt
JOIN processedIds pi ON pi.id = dt.id;

TRUNCATE TABLE processedIds

COMMIT TRAN

Sie werden viele Einfügungen machen, aber sie werden an einem kleinen Tisch sein, also sollte es schnell gehen. Sie können Ihre Beilagen auch mit ADO.net oder einem anderen von Ihnen verwendeten Datenadapter stapeln.

Eric Humphrey - Lotsahelp
quelle
2

Der Fragentitel enthält die Aufgabe, Daten aus einer Anwendung in die gespeicherte Prozedur zu übertragen. Dieser Teil wird vom Fragenkörper ausgeschlossen, aber lassen Sie mich auch versuchen, dies zu beantworten.

Im Kontext von sql-server-2008, wie in den Tags angegeben, gibt es einen weiteren großartigen Artikel von E. Sommarskog über Arrays und Listen in SQL Server 2008 . Übrigens habe ich es in dem Artikel gefunden, auf den Marian in seiner Antwort verwiesen hat.

Anstatt nur den Link anzugeben, zitiere ich die Inhaltsliste:

  • Einführung
  • Hintergrund
  • Tabellenwertige Parameter in T-SQL
  • Übergeben von tabellenwertigen Parametern aus ADO .NET
    • Verwenden einer Liste
    • Verwenden einer Datentabelle
    • Verwenden eines DataReader
    • Schlussbemerkungen
  • Verwenden von tabellenwertigen Parametern aus anderen APIs
    • ODBC
    • OLE DB
    • ADO
    • LINQ und Entity Framework
    • JDBC
    • PHP
    • Perl
    • Was ist, wenn Ihre API TVPs nicht unterstützt?
  • Leistungsüberlegungen
    • Serverseitig
    • Client-Seite
    • Primärschlüssel oder nicht?
  • Danksagung und Feedback
  • Änderungshistorie

Über die dort genannten Techniken hinaus habe ich das Gefühl, dass Bulkcopy und Bulk Insert in einigen Fällen eine Erwähnung verdienen, um den allgemeinen Fall zu erfassen.

bernd_k
quelle
1

Übergeben von Array-Parametern an eine gespeicherte Prozedur

Für MS SQL 2016 neueste Version

Mit MS SQL 2016 führen sie eine neue Funktion ein: SPLIT_STRING () , um mehrere Werte zu analysieren.

Dies kann Ihr Problem leicht lösen.

Für MS SQL ältere Version

Wenn Sie eine ältere Version verwenden, gehen Sie wie folgt vor:

Machen Sie zuerst eine Funktion:

 ALTER FUNCTION [dbo].[UDF_IDListToTable]
 (
    @list          [varchar](MAX),
    @Seperator     CHAR(1)
  )
 RETURNS @tbl TABLE (ID INT)
 WITH 

 EXECUTE AS CALLER
 AS
  BEGIN
    DECLARE @position INT
    DECLARE @NewLine CHAR(2) 
    DECLARE @no INT
    SET @NewLine = CHAR(13) + CHAR(10)

    IF CHARINDEX(@Seperator, @list) = 0
    BEGIN
    INSERT INTO @tbl
    VALUES
      (
        @list
      )
END
ELSE
BEGIN
    SET @position = 1
    SET @list = @list + @Seperator
    WHILE CHARINDEX(@Seperator, @list, @position) <> 0
    BEGIN
        SELECT @no = SUBSTRING(
                   @list,
                   @position,
                   CHARINDEX(@Seperator, @list, @position) - @position
               )

        IF @no <> ''
            INSERT INTO @tbl
            VALUES
              (
                @no
              )

        SET @position = CHARINDEX(@Seperator, @list, @position) + 1
    END
END
RETURN
END

Übergeben Sie anschließend einfach Ihre Zeichenfolge mit Trennzeichen an diese Funktion.

Ich hoffe das ist hilfreich für dich. :-)

Ankit Bhalala
quelle
-1

Verwenden Sie dies, um "Typentabelle erstellen" zu erstellen. einfaches Beispiel für den Benutzer

CREATE TYPE unit_list AS TABLE (
    ItemUnitId int,
    Amount float,
    IsPrimaryUnit bit
);

GO
 CREATE TYPE specification_list AS TABLE (
     ItemSpecificationMasterId int,
    ItemSpecificationMasterValue varchar(255)
);

GO
 declare @units unit_list;
 insert into @units (ItemUnitId, Amount, IsPrimaryUnit) 
  values(12,10.50, false), 120,100.50, false), (1200,500.50, true);

 declare @spec specification_list;
  insert into @spec (ItemSpecificationMasterId,temSpecificationMasterValue) 
   values (12,'test'), (124,'testing value');

 exec sp_add_item "mytests", false, @units, @spec


//Procedure definition
CREATE PROCEDURE sp_add_item
(   
    @Name nvarchar(50),
    @IsProduct bit=false,
    @UnitsArray unit_list READONLY,
    @SpecificationsArray specification_list READONLY
)
AS


BEGIN
    SET NOCOUNT OFF     

    print @Name;
    print @IsProduct;       
    select * from @UnitsArray;
    select * from @SpecificationsArray;
END
Dinesh Vaitage
quelle