क्वेरी द्वारा लौटाई गई प्रत्येक पंक्ति के लिए मैं एक बार संग्रहीत प्रक्रिया को कैसे निष्पादित करूं?


206

मेरे पास एक संग्रहीत प्रक्रिया है जो उपयोगकर्ता डेटा को एक निश्चित तरीके से बदल देती है। मैं इसे user_id पास करता हूँ और यह बात है। मैं एक तालिका पर एक क्वेरी चलाना चाहता हूं और फिर प्रत्येक user_id के लिए मैं उस user_id पर एक बार संग्रहीत कार्यविधि चलाता हूं

मैं इसके लिए क्वेरी कैसे लिखूंगा?


5
आपको यह निर्दिष्ट करने की आवश्यकता है कि RDBMS - SQL सर्वर, Oracle, MySql इत्यादि के लिए उत्तर अलग होगा
Gary.Ray

5
संभावना है कि आपको एक संग्रहीत प्रक्रिया की आवश्यकता नहीं है। क्या आप संग्रहीत कार्यविधि "क्या" की रूपरेखा तैयार कर सकते हैं, बिल्कुल? हो सकता है कि पूरी प्रक्रिया को एकल अपडेट स्टेटमेंट के रूप में व्यक्त किया जा सकता है। यदि संभव हो तो "प्रत्येक रिकॉर्ड के लिए एक बार करें" पैटर्न को आमतौर पर टाला जाना चाहिए।
तोमलक

आप किस डेटाबेस का उपयोग कर रहे हैं?
SO उपयोगकर्ता

1
आपको इस लेख को पढ़ना चाहिए ... आइटम 2 कहता है कि कर्सर का उपयोग न करें कोडप्रोजेक्ट. com /KB/ database / sqldodont.aspx ... mind मैं समय से पहले अनुकूलन के खिलाफ भी हूं।
माइकल प्रीवेकी

7
@MichaelPrewecki: यदि आप उस खराब लिखे गए लेख में आगे पढ़ते हैं, तो आप देखेंगे कि आइटम 10 है "सर्वर साइड कर्सर का उपयोग न करें जब तक कि आप नहीं जानते कि आपका क्या कर रहे हैं।" मुझे लगता है कि यह "मुझे पता है कि मैं क्या कर रहा हूं" का मामला है।
गाबे

जवाबों:


246

एक कर्सर का उपयोग करें

ADDENDUM: [MS SQL कर्सर उदाहरण]

declare @field1 int
declare @field2 int
declare cur CURSOR LOCAL for
    select field1, field2 from sometable where someotherfield is null

open cur

fetch next from cur into @field1, @field2

while @@FETCH_STATUS = 0 BEGIN

    --execute your sproc on each row
    exec uspYourSproc @field1, @field2

    fetch next from cur into @field1, @field2
END

close cur
deallocate cur

MS SQL में, यहाँ एक उदाहरण लेख है

ध्यान दें कि कर्सर सेट-आधारित संचालन की तुलना में धीमा हैं, लेकिन मैनुअल-लूप्स की तुलना में तेज़ हैं; इस एसओ प्रश्न में अधिक जानकारी

ADDENDUM 2: यदि आप केवल कुछ अभिलेखों से अधिक प्रसंस्करण कर रहे हैं, तो उन्हें पहले एक अस्थायी तालिका में खींचें और अस्थायी तालिका पर कर्सर चलाएं; यह एसक्यूएल को टेबल-लॉक में बढ़ने और ऑपरेशन को गति देने से रोकेगा

ADDENDUM 3: और निश्चित रूप से, यदि आप प्रत्येक उपयोगकर्ता आईडी के लिए अपनी संग्रहीत कार्यविधि को इनलाइन कर सकते हैं और पूरे कार्य को एकल SQL अद्यतन कथन के रूप में चला सकते हैं, तो यह इष्टतम होगा


21
आप घोषणा के बाद 'ओपन कर्व' से चूक गए - यह मुझे 'कर्सर नहीं खुला है' त्रुटियां दे रहा था। मेरे पास संपादन करने के लिए प्रतिनिधि नहीं है।
फियोना - myaccessible.website

5
आप लोगों को उनकी टिप्पणी पर मतदान करके धन्यवाद कर सकते हैं। कौन जानता है, शायद इस तरह वे अगली बार संपादित करने के लिए प्रतिनिधि होगा! :-)
रॉबिनो

सुनिश्चित करें कि आप अपने संग्रहीत कार्यविधि में उपयोग किए गए फ़ील्ड में JOINS और WHERE क्लॉस पर अपने अनुक्रमित की जाँच करें। मैं उपयुक्त अनुक्रमणिका जोड़ने के बाद नाटकीय रूप से अपने SP को लूप में बुलाता हूं।
मैथ्यू

1
लंबी निष्पादन के कारण संभावित लॉकिंग समस्याओं से बचने के लिए अस्थायी तालिका का उपयोग करने की याद दिलाने के लिए धन्यवाद।
टोनी

कभी-कभी संग्रहीत प्रक्रिया बहुत बड़ी या जटिल इनलाइन होती है जो बग्स को पेश किए बिना जोखिम में होती है। जहां प्रदर्शन अंतिम प्राथमिकता नहीं है, SP को कर्सर लूप में निष्पादित करना अक्सर सबसे व्यावहारिक विकल्प होता है।
सनकैट

55

यदि आपको लूप की आवश्यकता है तो अपनी विधि को बदलने की कोशिश करें!

माता-पिता संग्रहीत प्रक्रिया के भीतर, एक # तालिका बनाएं जिसमें वह डेटा हो जिसे आपको संसाधित करने की आवश्यकता है। बच्चे को संग्रहीत प्रक्रिया को कॉल करें, # टैम्प टेबल दिखाई देगी और आप इसे प्रोसेस कर सकते हैं, उम्मीद है कि डेटा के पूरे सेट के साथ और बिना कर्सर या लूप के काम कर रहे हैं।

यह वास्तव में इस बात पर निर्भर करता है कि यह बच्चा संग्रहीत प्रक्रिया क्या कर रहा है। यदि आप अद्यतन हैं, तो आप # टेबल में शामिल होने से "अपडेट" कर सकते हैं और एक लूप के बिना एक बयान में सभी काम कर सकते हैं। ऐसा ही INSERT और DELETE के लिए भी किया जा सकता है। यदि आपको UPDATE FROMIFs के साथ कई अपडेट करने की आवश्यकता है तो आप उन लोगों को #temp तालिका के साथ एकाधिक में परिवर्तित कर सकते हैं और CASE स्टेटमेंट या WHERE की शर्तों का उपयोग कर सकते हैं।

जब डेटाबेस में काम करना लूपिंग की मानसिकता को खोने की कोशिश करता है, तो यह एक वास्तविक प्रदर्शन नाली है, जिससे लॉकिंग / अवरुद्ध हो जाएगा और प्रसंस्करण धीमा हो जाएगा। यदि आप हर जगह लूप करते हैं, तो आपका सिस्टम बहुत अच्छी तरह से स्केल नहीं करेगा, और जब उपयोगकर्ता धीमी गति से रिफ्रेश होने की शिकायत करने लगेंगे तो उन्हें गति देना बहुत कठिन होगा।

इस प्रक्रिया की सामग्री पोस्ट करें जिसे आप लूप में कॉल करना चाहते हैं, और मैं 10 में से 9 बार शर्त लगाऊंगा, आप इसे पंक्तियों के सेट पर काम करने के लिए लिख सकते हैं।


3
एक बहुत अच्छे वर्कअराउंड के लिए +1, यह मानते हुए कि आप बच्चे पर नियंत्रण रखते हैं
स्टीवन ए। लोवे

थोड़ी सोच, यह अतिशयता श्रेष्ठ है!
एनसीसी

7
सेट आधारित ऑपरेशन हमेशा बेहतर होते हैं। हालांकि, ध्यान रखें कि एक एसपी को संशोधित करना हमेशा एक विकल्प नहीं होता है - लगता है कि विक्रेता ने समाधान प्रदान किया। कुछ उपयोगकर्ताओं को दृश्यता भी नहीं हो सकती है, केवल कर्सर या लूप विकल्प छोड़कर। मेरी दुकान में, हमारे देवता सब कुछ देख सकते हैं, लेकिन ट्रिगर्स, नेस्टेड प्रोक्स, हेरफेर किए जाने वाले रिकॉर्ड्स की संख्या आदि के कारण विक्रेता के आवेदन के बाहर एक समाधान बनाया जाता है, तो यह स्पष्ट करने के लिए कई बाधाएं हैं। कई बार उनका सबसे अच्छा विकल्प, कारण। आवेदन की जटिलता, केवल अभिलेखों के माध्यम से कर्सर करना है।
स्टीव मंगिअली

11

आपके टेबल और फ़ील्ड नामों के लिए कुछ इस तरह की प्रतिस्थापन की आवश्यकता होगी।

Declare @TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare @i Int, @MaxI Int, @UserID nVarchar(50)

Insert into @TableUser
Select User_ID
From Users 
Where (My Criteria)
Select @MaxI = @@RowCount, @i = 1

While @i <= @MaxI
Begin
Select @UserID = UserID from @TableUsers Where MyRowCount = @i
Exec prMyStoredProc @UserID
Select

 @i = @i + 1, @UserID = null
End

2
जबकि लूपर्स अभिशापों की तुलना में धीमे होते हैं
स्टीवन ए। लोवे

Declare कर्सर SQL निर्माण या कथन समर्थित नहीं है (??)
मेटागुरु

9

आप इसे एक गतिशील क्वेरी के साथ कर सकते हैं।

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);

6

क्या यह उपयोगकर्ता द्वारा परिभाषित फ़ंक्शन के साथ नहीं किया जा सकता है जो आपके संग्रहीत कार्यविधि को करने के लिए प्रतिकृति है?

SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition

जहां udfMyFunction एक फ़ंक्शन है जो आप बनाते हैं जो उपयोगकर्ता आईडी में लेता है और आपको इसके साथ जो भी करने की आवश्यकता होती है वह करता है।

कुछ और पृष्ठभूमि के लिए http://www.sqlteam.com/article/user-defined-functions देखें

मैं मानता हूं कि जहां संभव हो वहां से बचने के लिए अभिशापों से बचना चाहिए। और यह आमतौर पर संभव है!

(बेशक, मेरा जवाब यह बताता है कि आप केवल एसपी से आउटपुट प्राप्त करने में रुचि रखते हैं और आप वास्तविक डेटा नहीं बदल रहे हैं। मुझे मूल प्रश्न से थोड़ा अस्पष्ट "एक निश्चित तरीके से उपयोगकर्ता डेटा को बदल देता है", इसलिए सोचा कि मैं इसे एक संभावित समाधान के रूप में पेश करूंगा। आप जो कर रहे हैं उस पर पूरी तरह निर्भर करता है! '


1
ओपी: "संग्रहीत प्रक्रिया जो उपयोगकर्ता डेटा को एक निश्चित तरीके से बदल देती है" MSDN : उपयोगकर्ता-परिभाषित फ़ंक्शन का उपयोग डेटाबेस राज्य को संशोधित करने वाली क्रियाओं के लिए नहीं किया जा सकता है। हालाँकि SQLSVR 2014 में इसके साथ कोई समस्या नहीं है
जॉनी 5

6

एक टेबल चर या एक अस्थायी तालिका का उपयोग करें।

जैसा कि पहले उल्लेख किया गया है, एक कर्सर एक अंतिम उपाय है। ज्यादातर इसलिए कि यह बहुत सारे संसाधनों, मुद्दों के ताले का उपयोग करता है और एक संकेत हो सकता है कि आप समझ नहीं पा रहे हैं कि एसक्यूएल का सही उपयोग कैसे करें।

साइड नोट: मैं एक बार एक समाधान में आया था जो एक तालिका में पंक्तियों को अपडेट करने के लिए कर्सर का उपयोग करता था। कुछ जांच के बाद, यह पूरी बात एक एकल आदेश के साथ प्रतिस्थापित किया जा सकता है। हालाँकि, इस स्थिति में, जहाँ एक संग्रहीत कार्यविधि निष्पादित की जानी चाहिए, एक एकल SQL-कमांड काम नहीं करेगा।

इस तरह एक तालिका चर बनाएं (यदि आप बहुत सारे डेटा के साथ काम कर रहे हैं या मेमोरी पर कम हैं, तो इसके बजाय एक अस्थायी तालिका का उपयोग करें):

DECLARE @menus AS TABLE (
    id INT IDENTITY(1,1),
    parent NVARCHAR(128),
    child NVARCHAR(128));

idमहत्वपूर्ण है।

बदलें parentऔर childकुछ अच्छे डेटा के साथ, जैसे प्रासंगिक पहचानकर्ता या डेटा के पूरे सेट को संचालित किया जाना चाहिए।

तालिका में डेटा डालें, उदाहरण के लिए:

INSERT INTO @menus (parent, child) 
  VALUES ('Some name',  'Child name');
...
INSERT INTO @menus (parent,child) 
  VALUES ('Some other name', 'Some other child name');

कुछ चर घोषित करें:

DECLARE @id INT = 1;
DECLARE @parentName NVARCHAR(128);
DECLARE @childName NVARCHAR(128);

और अंत में, तालिका में डेटा पर थोड़ी देर का लूप बनाएं:

WHILE @id IS NOT NULL
BEGIN
    SELECT @parentName = parent,
           @childName = child 
        FROM @menus WHERE id = @id;

    EXEC myProcedure @parent=@parentName, @child=@childName;

    SELECT @id = MIN(id) FROM @menus WHERE id > @id;
END

पहली चुनिंदा अस्थायी तालिका से डेटा प्राप्त करता है। दूसरा चयन @id अद्यतन करता है। MINयदि कोई पंक्तियों का चयन नहीं किया गया है तो अशक्त है।

एक वैकल्पिक दृष्टिकोण लूप है जबकि तालिका में पंक्तियाँ हैं, SELECT TOP 1और अस्थायी तालिका से चयनित पंक्ति को हटा दें:

WHILE EXISTS(SELECT 1 FROM @menuIDs) 
BEGIN
    SELECT TOP 1 @menuID = menuID FROM @menuIDs;

    EXEC myProcedure @menuID=@menuID;

    DELETE FROM @menuIDs WHERE menuID = @menuID;
END;

3

मुझे डेव रिंकोन का गतिशील क्वेरी तरीका पसंद है क्योंकि यह कर्सर का उपयोग नहीं करता है और छोटा और आसान है। साझा करने के लिए डेव धन्यवाद।

लेकिन Azure SQL पर और क्वेरी में एक "अलग" के साथ मेरी जरूरतों के लिए, मुझे इस तरह कोड को संशोधित करना पड़ा:

Declare @SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines 
SELECT @SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' + 
              convert(varchar(8),tenantid) + ';' 
              from Dim_Machine
              where iscurrent = 1
              FOR XML PATH(''))

--for debugging print the sql 
print @SQL;

--execute the generated sql script
exec sp_executesql @SQL;

मुझे उम्मीद है इससे किसी को सहायता मिलेगी...

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.