it-swarm.asia

تمرير معلمات الصفيف إلى إجراء مخزن

لدي عملية تستحوذ على مجموعة من السجلات (1000) وتعمل عليها ، وعندما أنتهي ، أحتاج إلى وضع علامة على عدد كبير منها على أنه تمت معالجته. يمكنني الإشارة إلى ذلك بقائمة كبيرة من المعرّفات. أحاول تجنب نمط "التحديثات في حلقة" ، لذا أود أن أجد طريقة أكثر فعالية لإرسال هذه الحقيبة من المعرّفات إلى عملية تخزين MS SQL Server 2008 المخزنة.

الاقتراح رقم 1 - معلمات قيم الجدول. يمكنني تحديد نوع جدول مع حقل معرف فقط وإرسال جدول مليء بالمعرفات للتحديث.

الاقتراح رقم 2 - معلمة XML (varchar) مع OPENXML () في نص proc.

الاقتراح رقم 3 - تحليل القائمة. أفضل تجنب ذلك ، إذا أمكن ، لأنه يبدو غير عملي وعرضة للخطأ.

أي تفضيل بين هذه ، أو أي أفكار فاتني؟

54
D. Lambert

أفضل المقالات على الإطلاق حول هذه المسألة هي من إعداد إيرلاند سومارسكوج:

إنه يغطي جميع الخيارات ويشرح جيدًا.

آسف لضيق الإجابة ، لكن مقال إيرلاند عن المصفوفات يشبه كتب جو سيلكو عن الأشجار ومعالجات SQL الأخرى :)

44
Marian

هناك مناقشة رائعة لهذا على StackOverflow التي تغطي العديد من الأساليب. ما أفضل لـ SQL Server 2008+ هو استخدام معلمات قيمة الجدول. هذا هو في الأساس حل SQL Server لمشكلتك - تمرير قائمة القيم إلى إجراء مخزن.

مزايا هذا النهج هي:

  • إجراء استدعاء إجراء مخزن واحد مع كافة البيانات الخاصة بك تمريرها كمعلمة 1
  • إدخال الجدول منظم ومكتوب بقوة
  • لا بناء سلسلة/تحليل أو معالجة XML
  • يمكن بسهولة استخدام إدخال الجدول للتصفية أو الانضمام أو أي شيء

ومع ذلك ، خذ ملاحظة: إذا قمت باستدعاء إجراء مخزن يستخدم TVPs عبر ADO.NET أو ODBC وأخذ نظرة على النشاط باستخدام SQL Server Profiler ، ستلاحظ أن SQL Server يتلقى العديد من عبارات INSERT لتحميل TVP ، واحد لكل صف في TVP ، متبوعًا بالمكالمة إلى هذا هو حسب التصميم . هذه الدفعة من INSERTs تحتاج إلى تجميعها في كل مرة يتم فيها استدعاء الإجراء ، وتشكل نفقات صغيرة. ومع ذلك ، حتى مع هذه النفقات العامة ، لا يزال TVPs - تفجير طرق أخرى من حيث الأداء وسهولة الاستخدام لغالبية حالات الاستخدام.

إذا كنت تريد معرفة المزيد ، فإن Erland Sommarskog لديه النحيف الكامل حول كيفية عمل المعلمات ذات قيم الجدول ويقدم العديد من الأمثلة.

هنا مثال آخر أعدته:

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
23
Nick Chammas

تمت مناقشة الموضوع بالكامل في the مقالة محددة بقلم Erland Sommarskog: "المصفوفات والقائمة في SQL Server" . اختر أي إصدار تختار.

ملخص ، لـ pre SQL Server 2008 حيث يتفوق TVPs على البقية

  • CSV ، قسّم الطريقة التي تفضلها (عادةً ما أستخدم جدول أرقام)
  • XML وتحليل (أفضل مع SQL Server 2005+)
  • إنشاء جدول مؤقت على العميل

المقالة تستحق القراءة على أي حال لرؤية تقنيات وتفكير أخرى.

تحرير: إجابة متأخرة = ضخم القوائم في مكان آخر: تمرير معلمات الصفيف إلى إجراء مخزن

21
gbn

أعلم أنني تأخرت عن هذا الحزب ، لكني واجهت مثل هذه المشكلة في الماضي ، واضطررت إلى إرسال ما يصل إلى 100 ألف من أرقام bigint ، وقمت ببعض المعايير. انتهى بنا الأمر بإرسالها بتنسيق ثنائي ، كصورة - كان ذلك أسرع من كل شيء آخر لما يصل إلى 100 ألف رقم.

هذا هو رمز (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

التعليمة البرمجية التالية هي تعبئة الأعداد الصحيحة في فقاعة ثنائية. أنا أعكس ترتيب البايت هنا:

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;
}
14
A-K

أنا ممزق بين إحالتك إلى SO أو الإجابة عليه هنا ، 'لأن هذا يكاد يكون سؤال برمجة. ولكن نظرًا لأنني حصلت بالفعل على حل أستخدمه ... سأقوم بنشر هذا ؛)

الطريقة التي يعمل بها هذا هو أن تغذي سلسلة محددة بفواصل (انقسام بسيط ، لا ينقسم نمط CSV) إلى الإجراء المخزن على شكل varchar (4000) ثم تغذي تلك القائمة في هذه الوظيفة واستعادة جدول مفيد ، جدول varchars فقط.

يتيح لك هذا إرسال قيم المعرّفات التي تريد معالجتها فقط ، ويمكنك إجراء عملية ربط بسيطة في هذه المرحلة.

وبدلاً من ذلك ، يمكنك القيام بشيء باستخدام CLR DataTable وإطعامه ، ولكن هذا أكثر قليلاً لدعم ودعم الجميع يفهم قوائم CSV.

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
9
jcolebrand

أتلقى بانتظام مجموعات من 1000s من الصفوف و 10000s من الصفوف المرسلة من تطبيقنا ليتم معالجتها بواسطة إجراءات SQL Server المخزنة المختلفة.

لتلبية متطلبات الأداء ، نستخدم TVPs ، ولكن يجب عليك تنفيذ الملخص الخاص بك من dbDataReader للتغلب على بعض مشكلات الأداء في الوضع الافتراضي للمعالجة. لن أخوض في الأمور وكيف هم خارج نطاق هذا الطلب.

لم أفكر في معالجة XML لأنني لم أجد تطبيق XML الذي لا يزال يعمل بأكثر من 10000 "صف".

معالجة القائمة يمكن معالجتها عن طريق معالجة الجداول (الأرقام) أحادية البعد والمزدوجة. لقد استخدمنا هذه العناصر بنجاح في مجالات مختلفة ، لكن أجهزة TVP المُدارة جيدًا تكون أكثر أداء عندما يكون هناك أكثر من بضع مئات من "الصفوف".

كما هو الحال مع جميع الخيارات المتعلقة بمعالجة SQL Server ، يجب عليك تحديد اختيارك بناءً على نموذج الاستخدام.

5
Robert Miller

أتيحت لي الفرصة أخيرًا للقيام ببعض TableValuedParameters ويعملون بشكل رائع ، لذلك سأقوم بلصق رمز لوتا بالكامل يوضح كيفية استخدامهم ، مع عينة من بعض الكود الحالي: (ملاحظة: نستخدم ADO .شبكة)

لاحظ أيضًا: إنني أكتب بعض التعليمات البرمجية للخدمة ، ولدي الكثير من وحدات بت التعليمات البرمجية المحددة مسبقًا في الفصل الآخر ، لكنني أكتب هذا كتطبيق وحدة تحكم حتى أتمكن من تصحيحه ، لذلك قمت بنسخ كل هذا من تطبيق وحدة التحكم. عذرًا على نمط الترميز الخاص بي (مثل سلاسل الاتصال المشفرة) حيث كان نوعًا من "بناء واحد للتخلص منه". أردت أن أبين كيف أستخدم List<customObject> وادفعها إلى قاعدة البيانات بسهولة كجدول ، يمكنني استخدامها في الإجراء المخزن. C # ورمز TSQL أدناه:

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

أيضا ، سوف أتلقى انتقادات بناءة على أسلوب الترميز الخاص بي إذا كان لديك ذلك لتقديمه (لجميع القراء الذين يواجهون هذا السؤال) ولكن يرجى الحفاظ عليه بناء ؛) ... إذا كنت تريدني حقًا ، فوجدني في غرفة الدردشة هنا . نأمل في هذا الجزء من الكود أن نرى كيف يمكنهم استخدام List<Current> كما حددتها كجدول في قاعدة البيانات و List<T> في تطبيقهم.

5
jcolebrand

أود أن أذهب مع الاقتراح رقم 1 أو ، كبديل ، أنشئ جدول خدش يحتوي فقط على معرفات معالجة. أدخل في هذا الجدول أثناء المعالجة ، ثم بمجرد الانتهاء ، اتصل برقم مشابه أدناه:

BEGIN TRAN

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

TRUNCATE TABLE processedIds

COMMIT TRAN

ستقوم بالعديد من الإدخالات ، لكنها ستكون على طاولة صغيرة ، لذا يجب أن تكون سريعة. يمكنك أيضًا إدخال إدخالاتك باستخدام ADO.net أو أي محول بيانات تستخدمه.

3
Eric Humphrey - lotsahelp

يتضمن عنوان السؤال مهمة إرسال البيانات من تطبيق إلى الإجراء المخزن. تم استبعاد هذا الجزء من قبل هيئة السؤال ، ولكن دعني أحاول الإجابة على هذا أيضًا.

في سياق sql-server-2008 كما هو محدد بواسطة العلامات ، هناك مقالة رائعة أخرى كتبها E. Sommarskog المصفوفات والقوائم في SQL Server 2008 . راجع للشغل لقد وجدتها في المقالة التي أشار إليها ماريان في إجابته.

بدلاً من مجرد إعطاء الرابط ، أقتبس قائمة محتواه:

  • المقدمة
  • خلفية
  • معلمات قيمة الجدول في T-SQL
  • تمرير معلمات بقيمة الجدول من ADO .NET
    • باستخدام قائمة
    • باستخدام DataTable
    • باستخدام DataReader
    • الملاحظات الختامية
  • استخدام معلمات بقيمة الجدول من واجهات برمجة التطبيقات الأخرى
    • ODBC
    • OLE DB
    • ADO
    • LINQ وإطار الكيان
    • JDBC
    • PHP
    • بيرل
    • ماذا لو كانت API الخاصة بك لا تدعم TVPs
  • اعتبارات الأداء
    • جانب الخادم
    • من جانب العميل
    • المفتاح الأساسي أم لا؟
  • شكر وتقدير وملاحظات
  • مراجعة التاريخ

بخلاف التقنيات المذكورة هناك ، لدي شعور أنه في بعض الحالات ، يجب ذكر النسخ المجمعة والإدراج بالجملة للتناسب مع الحالة العامة.

2
bernd_k

تمرير معلمات الصفيف إلى إجراء مخزن

لأحدث إصدار MS SQL 2016

مع MS SQL 2016 يقدمون وظيفة جديدة: SPLIT_STRING () لتحليل قيم متعددة.

هذا يمكن أن يحل مشكلتك بسهولة.

لإصدار MS SQL أقدم

إذا كنت تستخدم إصدارًا أقدم ، فاتبع هذه الخطوة:

أولاً قم بعمل واحد:

 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

بعد القيام بذلك ، فقط قم بتمرير السلسلة الخاصة بك إلى هذه الوظيفة باستخدام الفاصل.

عذرا، الحجز غير متوفر في الوقت الحاضر. : -)

1
Ankit Bhalala