2009-07-01 18 views
72

पर पैरामीटर की एक सरणी पास करना मुझे तालिका से सभी पंक्तियों को हटाने के लिए "आईडी के" की एक सरणी को संग्रहीत करने की आवश्यकता है, जो पंक्तियों से बाहर की पंक्तियों से मेल खाती है।संग्रहीत प्रक्रिया

मैं इसे सबसे सरल तरीके से कैसे कर सकता हूं?

http://www.sommarskog.se/arrays-in-sql.html

लिंक का उपयोग कर एक विभाजन समारोह बनाते हैं, और इसका इस्तेमाल चाहते:

DELETE YourTable 
    FROM YourTable       d 
    LEFT OUTER JOIN dbo.splitFunction(@Parameter) s ON d.ID=s.Value 
    WHERE s.Value IS NULL 

I prefer the number table approach

इस आधार पर कोड है

+2

देखते हैं कि यह नहीं http का डुप्लिकेट है stackoverflow.com/questions/114504/is-it-possible-to-send-a-collection-of-ids-as-a-ado-net-sql-parameter? –

+3

@ जॉन सॉंडर्स, कई "पैरामीटर के रूप में पास सरणी" एसक्यूएल सर्वर प्रश्न हैं। हालांकि, इसमें एक जोड़ा मोड़ है, प्रश्न के पैरामीटर भाग को छोड़कर सभी पंक्तियों को हटा दें। नतीजतन, मुझे नहीं लगता कि यह एक डुप्लिकेट है। –

+0

जॉन सॉंडर्स, मुझे नहीं पता कि मैंने एक खोज की है लेकिन मुझे वह नहीं मिला जो मैं ढूंढ रहा था। क्या इसमें कोई समस्या है? – markiz

उत्तर

42

एक संग्रहीत प्रक्रिया का उपयोग करें:

संपादित करें: serialize सूची (या कुछ और) के लिए एक पूरक:

List<string> testList = new List<int>(); 

testList.Add(1); 
testList.Add(2); 
testList.Add(3); 

XmlSerializer xs = new XmlSerializer(typeof(List<int>)); 
MemoryStream ms = new MemoryStream(); 
xs.Serialize(ms, testList); 

string resultXML = UTF8Encoding.UTF8.GetString(ms.ToArray()); 

परिणाम (XML पैरामीटर के साथ उपयोग के लिए तैयार):

<?xml version="1.0"?> 
<ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <int>1</int> 
    <int>2</int> 
    <int>3</int> 
</ArrayOfInt> 

मूल पोस्ट:

पैरामीटर के रूप में पासिंग एक्सएमएल:

<ids> 
    <id>1</id> 
    <id>2</id> 
</ids> 

CREATE PROCEDURE [dbo].[DeleteAllData] 
(
    @XMLDoc XML 
) 
AS 
BEGIN 

DECLARE @handle INT 

EXEC sp_xml_preparedocument @handle OUTPUT, @XMLDoc 

DELETE FROM 
    YOURTABLE 
WHERE 
    YOUR_ID_COLUMN NOT IN (
     SELECT * FROM OPENXML (@handle, '/ids/id') WITH (id INT '.') 
    ) 
EXEC sp_xml_removedocument @handle 

+4

+1 यह एक अच्छा समाधान है यदि आपका डेटा पहले से ही एक XML संरचना में है - तो जब आप XML के निर्माण के क्लाइंट-साइड ओवरहेड को जोड़ते हैं तो इतना गर्म नहीं होता है। – RolandTumble

+1

ए रोलैंड टम्बल ने उल्लेख किया है, इसके लिए मेरे आईपूट डेटा सरणी (सूची) को एक्सएमएल में परिवर्तित करने की आवश्यकता होगी, इसलिए मुझे लगता है कि यह मेरे मामले में सबसे अच्छा समाधान नहीं है। – markiz

+0

आप ऐसा करने के लिए सीरियलाइज़ का उपयोग कर सकते हैं ... यह स्प्लिट तारों की तुलना में अधिक विश्वसनीय उपयोग XML है ... – Zanoni

20

यह सबसे अच्छा स्रोत है उपर्युक्त लिंक जो आपके लिए यह करना चाहिए ...

इससे पहले कि आप मेरी फ़ंक्शन का उपयोग करें, आप एक "सहायक" तालिका सेट करने की आवश्यकता है, आप केवल डेटाबेस प्रति इस एक बार करने की जरूरत है: अपने स्ट्रिंग है, जो पाश नहीं है विभाजित करने के लिए

CREATE TABLE Numbers 
(Number int NOT NULL, 
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
DECLARE @x int 
SET @x=0 
WHILE @x<8000 
BEGIN 
    SET @[email protected]+1 
    INSERT INTO Numbers VALUES (@x) 
END 

उपयोग इस समारोह और बहुत तेजी से है:

CREATE FUNCTION [dbo].[FN_ListToTable] 
(
    @SplitOn    char(1)    --REQUIRED, the character to split the @List string on 
    ,@List     varchar(8000)  --REQUIRED, the list to split apart 
) 
RETURNS 
@ParsedList table 
(
    ListValue varchar(500) 
) 
AS 
BEGIN 

/** 
Takes the given @List string and splits it apart based on the given @SplitOn character. 
A table is returned, one row per split item, with a column name "ListValue". 
This function workes for fixed or variable lenght items. 
Empty and null items will not be included in the results set. 


Returns a table, one row per item in the list, with a column name "ListValue" 

EXAMPLE: 
---------- 
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B') 

    returns: 
     ListValue 
     ----------- 
     1 
     12 
     123 
     1234 
     54321 
     6 
     A 
     * 
     ||| 
     B 

     (10 row(s) affected) 

**/ 



---------------- 
--SINGLE QUERY-- --this will not return empty rows 
---------------- 
INSERT INTO @ParsedList 
     (ListValue) 
    SELECT 
     ListValue 
     FROM (SELECT 
        LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue 
        FROM (
          SELECT @SplitOn + @List + @SplitOn AS List2 
         ) AS dt 
         INNER JOIN Numbers n ON n.Number < LEN(dt.List2) 
        WHERE SUBSTRING(List2, number, 1) = @SplitOn 
      ) dt2 
     WHERE ListValue IS NOT NULL AND ListValue!='' 



RETURN 

END --Function FN_ListToTable 

आप में एक तालिका के रूप में इस समारोह का उपयोग कर सकते एक में शामिल होने:

SELECT 
    Col1, COl2, Col3... 
    FROM YourTable 
     INNER JOIN dbo.FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue 

यहाँ हटाना है:

DELETE YourTable 
    FROM YourTable        d 
    LEFT OUTER JOIN dbo.FN_ListToTable(',',@Parameter) s ON d.ID=s.ListValue 
    WHERE s.ListValue IS NULL 
+0

@ केएम: अधिकतम 8000 वर्ण क्यों? क्या हम उससे अधिक तारों का उपयोग कर सकते हैं? – Pandincus

+0

आप इनपुट पैरामीटर वर्कर (अधिकतम) बना सकते हैं, बस सुनिश्चित करें कि आपकी संख्या तालिका में पर्याप्त पंक्तियां हैं। मैं सिर्फ 8000 का उपयोग करता हूं, क्योंकि मैंने कभी लंबे तारों को विभाजित नहीं किया है। आप मेरा यह अन्य उत्तर देख सकते हैं: http://stackoverflow.com/questions/4227552/using-microsoft-query-and-odbc-to-sql-server-complicated-query/4227685#4227685 जहां मैंने विभाजन को अपडेट किया है एक टेबल फ़ंक्शन का उपयोग करने के लिए फ़ंक्शन और संख्या तालिका निर्माण में भी सुधार किया। –

0

एक सरणी पास करने के बजाय XML डेटा प्रकार का उपयोग करने के बारे में क्या। मुझे लगता है कि एक बेहतर समाधान और एसक्यूएल 2005

+1

क्या आप कोड + स्पष्टीकरण प्रदान कर सकते हैं? – markiz

+1

एक्सएमएल के माध्यम से पढ़ना विभाजन स्ट्रिंग की तुलना में धीमा है! – thijs

+1

आप विभाजित तारों का उपयोग करने पर भरोसा करते हैं?गति से पहले ट्रस्ट ... – Zanoni

1

पर मैं अच्छी तरह से काम करता हूं, मैं आपकी आईडी को एक्सएमएल स्ट्रिंग के रूप में पास करने पर विचार करता हूं, और फिर आप एक्सएमएल को एक अस्थायी तालिका में शामिल करने के लिए झुका सकते हैं, या आप इसके खिलाफ भी पूछ सकते हैं एक्सएमएल सीधे SP_XML_PREPAREDOCUMENT और OPENXML का उपयोग कर।

3

तुम एक अस्थायी तालिका जो संग्रहीत प्रक्रिया मौजूद होने की उम्मीद है इस्तेमाल कर सकते हैं। यह SQL सर्वर के पुराने संस्करणों पर काम करेगा, जो एक्सएमएल आदि का समर्थन नहीं करता है।

CREATE TABLE #temp 
(INT myid) 
GO 
CREATE PROC myproc 
AS 
BEGIN 
    DELETE YourTable 
    FROM YourTable      
    LEFT OUTER JOIN #temp T ON T.myid=s.id 
    WHERE s.id IS NULL 
END 
12

आप इस कोशिश कर सकते:



DECLARE @List VARCHAR(MAX) 

SELECT @List = '1,2,3,4,5,6,7,8' 

EXEC(
'DELETE 
FROM TABLE 
WHERE ID NOT IN (' + @List + ')' 
) 

+5

... जब तक आप सुनिश्चित हैं कि @List पैरामीटर उपयोगकर्ता इनपुट – Joe

+0

से पॉप्युलेट नहीं किया गया है सौंदर्य सौंदर्य सादगी है। – wwmbes

55

आप के बजाय & serializing अपनी सूची डेटा deserializing के प्रयोग कर रहे हैं Sql सर्वर 2008 या बेहतर, आप एक टेबल-वैल्यूड पैरामीटर (TVP) कहा जाता है कुछ का उपयोग कर सकते हर बार जब आप इसे संग्रहीत प्रक्रिया में पास करना चाहते हैं।

के एक सरल स्कीमा बनाते हमारे खेल का मैदान के रूप में सेवा करने के लिए कर शुरू करते हैं:

CREATE TYPE T1Ids AS Table (
     t1Id INT 
); 
GO 


CREATE PROCEDURE dbo.FindMatchingRowsInTable1(@Table1Ids AS T1Ids READONLY) 
AS 
BEGIN 
     SET NOCOUNT ON; 

     SELECT Table1.t1Id FROM dbo.Table1 AS Table1 
     JOIN @Table1Ids AS paramTable1Ids ON Table1.t1Id = paramTable1Ids.t1Id; 
END 
GO 
:

CREATE DATABASE [TestbedDb] 
GO 


USE [TestbedDb] 
GO 

    /* First, setup the sample program's account & credentials*/ 
CREATE LOGIN [testbedUser] WITH PASSWORD=N'µ×? 
?S[°¿Q­¥½q?_Ĭ¼Ð)3õļ%dv', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=ON 
GO 

CREATE USER [testbedUser] FOR LOGIN [testbedUser] WITH DEFAULT_SCHEMA=[dbo] 
GO 

EXEC sp_addrolemember N'db_owner', N'testbedUser' 
GO 


    /* Now setup the schema */ 
CREATE TABLE dbo.Table1 (t1Id INT NOT NULL PRIMARY KEY); 
GO 

INSERT INTO dbo.Table1 (t1Id) 
VALUES 
    (1), 
    (2), 
    (3), 
    (4), 
    (5), 
    (6), 
    (7), 
    (8), 
    (9), 
    (10); 
GO 
हमारे स्कीमा और जगह में नमूना डेटा के साथ

, अब हम हमारे TVP संग्रहीत प्रक्रिया बनाने के लिए तैयार कर रहे हैं

हमारे स्कीमा और एपीआई दोनों जगहों पर, हम अपने कार्यक्रम से टीवीपी संग्रहीत प्रक्रिया को कॉल कर सकते हैं जैसे:

 // Curry the TVP data 
     DataTable t1Ids = new DataTable(); 
     t1Ids.Columns.Add("t1Id", 
          typeof(int)); 

     int[] listOfIdsToFind = new[] {1, 5, 9}; 
     foreach (int id in listOfIdsToFind) 
     { 
      t1Ids.Rows.Add(id); 
     } 
     // Prepare the connection details 
     SqlConnection testbedConnection = 
       new SqlConnection(
         @"Data Source=.\SQLExpress;Initial Catalog=TestbedDb;Persist Security Info=True;User ID=testbedUser;Password=letmein12;Connect Timeout=5"); 

     try 
     { 
      testbedConnection.Open(); 

      // Prepare a call to the stored procedure 
      SqlCommand findMatchingRowsInTable1 = new SqlCommand("dbo.FindMatchingRowsInTable1", 
                    testbedConnection); 
      findMatchingRowsInTable1.CommandType = CommandType.StoredProcedure; 

      // Curry up the TVP parameter 
      SqlParameter sqlParameter = new SqlParameter("Table1Ids", 
                  t1Ids); 
      findMatchingRowsInTable1.Parameters.Add(sqlParameter); 

      // Execute the stored procedure 
      SqlDataReader sqlDataReader = findMatchingRowsInTable1.ExecuteReader(); 

      while (sqlDataReader.Read()) 
      { 
       Console.WriteLine("Matching t1ID: {0}", 
            sqlDataReader[ "t1Id" ]); 
      } 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e.ToString()); 
     } 
    /* Output: 
    * Matching t1ID: 1 
    * Matching t1ID: 5 
    * Matching t1ID: 9 
    */ 

एंटिटी फ्रेमवर्क जैसे अधिक अमूर्त एपीआई का उपयोग करके ऐसा करने के लिए शायद कम दर्दनाक तरीका है। हालांकि, मेरे पास इस समय खुद के लिए देखने का समय नहीं है।

+0

मुझे लगता है कि यह करने का यह सबसे अच्छा तरीका है। मुझे आश्चर्य है कि इसके लिए आपको कोई वोट नहीं मिला है। तो मैंने तुम्हें एक दिया। –

+1

मुझे यह दृष्टिकोण पसंद है। साझा करने के लिए धन्यवाद। – Justin

+0

पुन: प्रयोज्यता के लिए, मेरा मानना ​​है कि यह अब तक का सबसे अच्छा दृष्टिकोण है। निम्नलिखित सामान्य वास्तविक-दुनिया अनुप्रयोग पर विचार करें: मेरे पास एक खोज है, जो एक अद्वितीय प्राथमिक कुंजी सहित परिणाम सेट उत्पन्न करती है। मैं फिर उस परिणाम को दूसरे ऑपरेशन के माध्यम से पास करना चाहता हूं (उदाहरण के लिए, एक्सेल स्प्रैडशीट के लिए भविष्य की जानकारी पुनर्प्राप्त करना)। इस विधि के साथ, मैं प्रारंभिक परिणाम से आसानी से आईडी की एक सूची पुनर्प्राप्त कर सकता हूं और सूची को अपने दूसरे चरण संग्रहीत प्रक्रिया में पास कर सकता हूं जो "@IDs T1IDs को पढ़ने के लिए एक पैरामीटर स्वीकार करता है"। इस प्रकार मैं प्रारंभिक खोज के भारी भार को दूर करता हूं। – mrmillsy

2
declare @ids nvarchar(1000) 

set @ids = '100,2,3,4,5' --Parameter passed 

set @ids = ',' + @ids + ',' 

select * 
from  TableName 
where charindex(',' + CAST(Id as nvarchar(50)) + ',', @ids) > 0 
+0

बिंगो विटालिव्स! पहला पुरस्कार कम से कम कोड है और कम से कम समय में आवश्यक चीज़ों का उत्पादन करता है। एम्बेडेड व्हाइट-स्पेस "@ids" में परेशानी का कारण बन सकता है। – wwmbes

0

मैं यह पसंद है, क्योंकि यह एक XElement, जो SqlCommand

के लिए उपयुक्त है के रूप में पारित किया जा लिए अनुकूल है

(क्षमा करें यह VB.NET है, लेकिन आप अंदाजा हो)

<Extension()> 
Public Function ToXml(Of T)(array As IEnumerable(Of T)) As XElement 
    Return XElement.Parse(
      String.Format("<doc>{0}</doc>", String.Join("", array.Select(Function(s) String.Concat("<d>", s.ToString(), "</d>")))), LoadOptions.None) 
End Function 

यह एसक्यूएल संग्रहित proc है, छोटा, पूरा नहीं हुआ!

बनाएं प्रक्रिया [dbo]। [Myproc] (@blah xml)
के रूप में ... जहां SomeID में (चयन doc.t.value ('।', 'Int') से @ netwerkids.nodes //: (एन '/ doc/डी) दस्तावेज़ के रूप में (टी))

संबंधित मुद्दे