ฉันจะรันโพรซีเดอร์ที่เก็บหนึ่งครั้งสำหรับแต่ละแถวที่ส่งคืนโดยเคียวรีได้อย่างไร


206

ฉันมีขั้นตอนการจัดเก็บที่เปลี่ยนแปลงข้อมูลผู้ใช้ด้วยวิธีใดวิธีหนึ่ง ฉันผ่านมัน user_id และมันก็เป็นอย่างนั้น ฉันต้องการเรียกใช้แบบสอบถามบนโต๊ะและสำหรับแต่ละ user_id ฉันพบว่าเรียกใช้กระบวนงานที่เก็บไว้หนึ่งครั้งใน user_id นั้น

ฉันจะเขียนแบบสอบถามสำหรับสิ่งนี้ได้อย่างไร


5
คุณต้องระบุสิ่งที่ RDBMS - คำตอบจะแตกต่างกันไปสำหรับ SQL Server, Oracle, MySql และอื่น ๆ
Gary.Ray

5
โอกาสที่คุณไม่ต้องการขั้นตอนการจัดเก็บเลย คุณสามารถร่าง "สิ่งที่" ขั้นตอนการจัดเก็บไม่ บางทีกระบวนการทั้งหมดสามารถแสดงเป็นคำสั่งปรับปรุงเดียว ควรหลีกเลี่ยงรูปแบบ "ทำหนึ่งครั้งสำหรับแต่ละระเบียน" ถ้าเป็นไปได้
Tomalak

คุณใช้ฐานข้อมูลใดอยู่
ผู้ใช้ SO

1
คุณควรอ่านบทความนี้ ... รายการที่ 2 กล่าวว่าไม่ได้ใช้เคอร์เซอร์codeproject.com/KB/database/sqldodont.aspx...mindฉันยังกับการเพิ่มประสิทธิภาพก่อนวัยอันควร
Michael Prewecki

7
@MichaelPrewecki: ถ้าคุณอ่านเพิ่มเติมในบทความที่เขียนไม่ดีคุณจะเห็นว่ารายการ 10 คือ "ไม่ใช้เคอร์เซอร์ฝั่งเซิร์ฟเวอร์เว้นแต่คุณจะรู้ว่าคุณกำลังทำอะไรอยู่" ฉันคิดว่านี่เป็นกรณีของ "ฉันรู้ว่าฉันกำลังทำอะไร"
Gabe

คำตอบ:


246

ใช้เคอร์เซอร์

เพิ่ม: [ตัวอย่างเคอร์เซอร์ 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 นี่คือตัวอย่างบทความ

โปรดทราบว่าเคอร์เซอร์จะช้ากว่าการใช้งานแบบเซ็ต แต่เร็วกว่าแบบแมนนวลในขณะที่ลูป; รายละเอียดเพิ่มเติมในคำถาม SO นี้

ADDENDUM 2: หากคุณต้องการประมวลผลมากกว่าสองสามระเบียนให้ดึงระเบียนเหล่านั้นลงในตารางชั่วคราวก่อนแล้วเรียกใช้เคอร์เซอร์เหนือตารางชั่วคราว สิ่งนี้จะป้องกัน SQL จากการเพิ่มเข้าไปในตารางตัวล็อค

ADDENDUM 3: และแน่นอนถ้าคุณสามารถอินไลน์สิ่งที่กระบวนการจัดเก็บของคุณทำกับ ID ผู้ใช้แต่ละคนและเรียกใช้สิ่งทั้งหมดเป็นคำสั่ง SQL update เดียวนั่นจะเหมาะสมที่สุด


21
คุณพลาด 'open cur' หลังจากประกาศ - นี่ทำให้ฉัน 'เคอร์เซอร์ไม่ได้เปิด' ข้อผิดพลาด ฉันไม่มีตัวแทนในการแก้ไข
Fiona - myaccessible.website

5
คุณสามารถขอบคุณผู้คนได้ด้วยการโหวตความคิดเห็นของพวกเขา ใครจะรู้บางทีวิธีการที่พวกเขาจะมีตัวแทนในการแก้ไขในครั้งต่อไป! :-)
Robino

ตรวจสอบให้แน่ใจว่าคุณตรวจสอบดัชนีของคุณในส่วน JOINS และ WHERE ในฟิลด์ที่ใช้ในขั้นตอนการจัดเก็บของคุณ ฉันเร่งโทร SP ของฉันอย่างมากหลังจากเพิ่มดัชนีที่เหมาะสม
แมทธิว

1
ขอบคุณสำหรับการเตือนการใช้ตารางชั่วคราวเพื่อหลีกเลี่ยงปัญหาการล็อคที่อาจเกิดขึ้นจากการใช้งานที่ยาวนาน
โทนี่

บางครั้งขั้นตอนการจัดเก็บมีขนาดใหญ่เกินไปหรือซับซ้อนเกินกว่าจะอินไลน์โดยไม่ต้องเสี่ยงกับการแนะนำบั๊ก ในกรณีที่ประสิทธิภาพไม่ได้เป็นสิ่งสำคัญอันดับแรกการใช้ SP ในเคอร์เซอร์วนรอบมักเป็นตัวเลือกที่ใช้งานได้จริงที่สุด
Suncat2000

55

ลองเปลี่ยนวิธีการของคุณถ้าคุณจำเป็นต้องวนซ้ำ!

ภายในโพรซีเดอร์ที่เก็บไว้ให้สร้างตาราง #temp ที่มีข้อมูลที่คุณต้องดำเนินการ เรียกกระบวนงานที่เก็บไว้ของเด็กตาราง #temp จะมองเห็นได้และคุณสามารถประมวลผลหวังว่าจะทำงานกับชุดข้อมูลทั้งหมดโดยไม่ต้องใช้เคอร์เซอร์หรือลูป

สิ่งนี้ขึ้นอยู่กับกระบวนการที่เด็กเก็บไว้กำลังทำอยู่ หากคุณกำลังอัปเดตคุณสามารถ "อัปเดตจาก" การเข้าร่วมในตาราง #temp และทำงานทั้งหมดในคำสั่งเดียวโดยไม่ต้องวนซ้ำ สามารถทำเช่นเดียวกันสำหรับ INSERT และ DELETE หากคุณต้องการอัปเดตหลายรายการด้วย IFs คุณสามารถแปลงการอัปเดตเป็นหลาย ๆUPDATE FROMด้วยตาราง #temp และใช้คำสั่ง CASE หรือเงื่อนไข WHERE

เมื่อทำงานในฐานข้อมูลพยายามที่จะสูญเสียความคิดในการวนซ้ำมันเป็นตัวระบายประสิทธิภาพที่แท้จริงซึ่งจะทำให้การล็อค / การบล็อกและการประมวลผลช้าลง หากคุณวนรอบทุกที่ระบบของคุณจะปรับขนาดได้ไม่ดีนักและจะเร่งความเร็วได้ยากเมื่อผู้ใช้เริ่มบ่นเกี่ยวกับการรีเฟรชช้า

โพสต์เนื้อหาของโพรซีเดอร์นี้ที่คุณต้องการโทรเป็นลูปและฉันจะวางเดิมพัน 9 จาก 10 ครั้งคุณสามารถเขียนมันเพื่อทำงานกับแถว


3
+1 สำหรับวิธีแก้ปัญหาที่ดีมากสมมติว่าคุณสามารถควบคุมการ sproc เด็ก
Steven A. Lowe

คิดนิดหน่อยการแก้ปัญหานี้ยอดเยี่ยม!
encc

7
การดำเนินการตามชุดมักจะดีกว่า อย่างไรก็ตามโปรดทราบว่าการแก้ไข SP ไม่ได้เป็นตัวเลือกเสมอไป - คิดว่าผู้ขายมีวิธีแก้ปัญหา ผู้ใช้บางคนอาจไม่สามารถมองเห็นได้ทำให้เหลือเพียงเคอร์เซอร์หรือตัวเลือกการวนซ้ำเท่านั้น ในร้านค้าของฉัน devs ของเราสามารถเห็นทุกอย่าง แต่มีอุปสรรคมากมายที่จะล้างหากโซลูชันถูกสร้างขึ้นนอกแอปพลิเคชันของผู้ขายเนื่องจากทริกเกอร์, โปรเซสซ้อน, จำนวนบันทึกที่ถูกควบคุม ฯลฯ หลายครั้งที่ตัวเลือกที่ดีที่สุดเนื่องจาก ความซับซ้อนของแอปพลิเคชันคือเพียงเลื่อนเคอร์เซอร์ไปตามระเบียน
Steve Mangiameli

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
ในขณะที่ลูปจะช้ากว่าเม้าส์
Steven A. Lowe

ไม่รองรับการประกาศเคอร์เซอร์ SQL หรือคำสั่ง (??)
MetaGuru

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 เป็นฟังก์ชันที่คุณใช้ใน ID ผู้ใช้และทำทุกสิ่งที่คุณต้องการ

ดูhttp://www.sqlteam.com/article/user-defined-functionsเพื่อดูพื้นหลังอีกเล็กน้อย

ฉันยอมรับว่าเคอร์เซอร์ควรหลีกเลี่ยงถ้าเป็นไปได้จริง ๆ และมักเป็นไปได้!

(แน่นอนคำตอบของฉันตั้งสมมติฐานว่าคุณสนใจที่จะรับเอาท์พุทจาก SP เท่านั้นและคุณไม่ได้เปลี่ยนข้อมูลจริงฉันพบว่า "แก้ไขข้อมูลผู้ใช้ด้วยวิธีใดวิธีหนึ่ง" เล็กน้อยคลุมเครือจากคำถามเดิม ดังนั้นคิดว่าฉันจะให้สิ่งนี้เป็นวิธีแก้ปัญหาที่เป็นไปได้มากที่สุดขึ้นอยู่กับสิ่งที่คุณกำลังทำ!)


1
OP: "กระบวนงานที่เก็บไว้ซึ่งเปลี่ยนแปลงข้อมูลผู้ใช้ในลักษณะที่แน่นอน" MSDN : ไม่สามารถใช้ฟังก์ชันที่ผู้ใช้กำหนดเองเพื่อทำการกระทำที่แก้ไขสถานะฐานข้อมูล อย่างไรก็ตาม SQLSVR 2014 ดูเหมือนจะไม่มีปัญหา
johnny 5

6

ใช้ตัวแปรตารางหรือตารางชั่วคราว

ดังที่ได้กล่าวมาแล้วเคอร์เซอร์เป็นทางเลือกสุดท้าย ส่วนใหญ่เป็นเพราะใช้ทรัพยากรจำนวนมากปัญหาในการล็อคและอาจเป็นสัญญาณว่าคุณไม่เข้าใจวิธีการใช้ SQL อย่างถูกต้อง

หมายเหตุด้านข้าง: ฉันเคยเจอโซลูชันที่ใช้เคอร์เซอร์เพื่ออัปเดตแถวในตาราง หลังจากการตรวจสอบข้อเท็จจริงบางอย่างปรากฎว่าสิ่งทั้งหมดสามารถถูกแทนที่ด้วยคำสั่ง UPDATE เดียว อย่างไรก็ตามในกรณีนี้ควรดำเนินการขั้นตอนการจัดเก็บคำสั่ง 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ส่งคืนค่า null ถ้าไม่มีการเลือกแถว

อีกทางเลือกหนึ่งคือการวนซ้ำในขณะที่ตารางมีแถว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

ฉันชอบวิธีสืบค้นแบบไดนามิกของ Dave Rincon เนื่องจากไม่ได้ใช้เคอร์เซอร์และมีขนาดเล็กและใช้งานง่าย ขอบคุณเดฟสำหรับการแบ่งปัน

แต่สำหรับความต้องการของฉันใน 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;

ฉันหวังว่านี่จะช่วยคน ...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.