Bagaimana cara menjalankan prosedur tersimpan sekali untuk setiap baris yang dikembalikan oleh permintaan?

206

Saya memiliki prosedur tersimpan yang mengubah data pengguna dengan cara tertentu. Saya meneruskannya user_id dan itu tidak apa-apa. Saya ingin menjalankan kueri pada tabel dan kemudian untuk setiap user_id saya menemukan menjalankan prosedur tersimpan sekali pada user_id itu

Bagaimana saya menulis permintaan untuk ini?

MetaGuru
sumber
5
Anda perlu menentukan RDBMS apa - jawabannya akan berbeda untuk SQL Server, Oracle, MySql, dll.
Gary.Ray
5
Kemungkinannya adalah Anda tidak memerlukan prosedur tersimpan sama sekali. Bisakah Anda menguraikan "apa" yang dilakukan prosedur tersimpan, tepatnya? Mungkin seluruh proses dapat dinyatakan sebagai pernyataan pembaruan tunggal. Pola "lakukan sekali untuk setiap catatan" umumnya harus dihindari, jika memungkinkan.
Tomalak
Database mana yang Anda gunakan?
SO Pengguna
1
Anda harus membaca artikel ini ... item 2 mengatakan JANGAN menggunakan kursor codeproject.com/KB/database/sqldodont.aspx..mind saya juga menentang optimasi prematur.
Michael Prewecki
7
@MichaelPrewecki: Jika Anda membaca lebih lanjut di artikel yang ditulis dengan buruk itu, Anda akan melihat bahwa item 10 adalah "JANGAN gunakan kursor sisi server kecuali Anda tahu apa yang Anda lakukan." Saya pikir ini adalah kasus "Saya tahu apa yang saya lakukan".
Gabe

Jawaban:

246

gunakan kursor

ADDENDUM: [Contoh kursor 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

di MS SQL, inilah contoh artikel

perhatikan bahwa kursor lebih lambat dari operasi berbasis set, tetapi lebih cepat dari loop sementara manual; lebih detail dalam pertanyaan SO ini

ADDENDUM 2: jika Anda akan memproses lebih dari hanya beberapa catatan, tarik mereka ke dalam tabel temp pertama dan jalankan kursor di atas tabel temp; ini akan mencegah SQL naik ke kunci-tabel dan mempercepat operasi

LAMPIRAN 3: dan tentu saja, jika Anda dapat menyesuaikan apa pun prosedur tersimpan yang Anda lakukan untuk setiap ID pengguna dan menjalankan semuanya sebagai pernyataan pembaruan SQL tunggal, itu akan menjadi optimal

Steven A. Lowe
sumber
21
Anda melewatkan 'buka cur' setelah deklarasi - ini memberi saya 'kursor tidak terbuka' kesalahan. Saya tidak memiliki perwakilan untuk melakukan pengeditan.
Fiona - myaccessible.website
5
Anda dapat berterima kasih kepada orang-orang dengan memilih komentar mereka. Siapa tahu, mungkin dengan begitu mereka akan memiliki perwakilan untuk melakukan pengeditan, lain kali! :-)
Robino
Pastikan Anda memeriksa indeks Anda pada klausa GABUNG dan DI MANA di bidang yang digunakan dalam prosedur tersimpan Anda. Saya secara dramatis mempercepat memanggil SP saya dalam satu lingkaran setelah menambahkan indeks yang sesuai.
Matius
1
Terima kasih atas pengingat menggunakan tabel temp untuk menghindari kemungkinan masalah penguncian yang disebabkan oleh eksekusi yang lama.
Tony
Kadang-kadang prosedur yang disimpan terlalu besar atau rumit untuk di-inline tanpa risiko memperkenalkan bug. Di mana kinerja bukan prioritas utama, mengeksekusi SP dalam loop kursor seringkali merupakan pilihan paling praktis.
Suncat2000
55

cobalah untuk mengubah metode Anda jika Anda perlu mengulang!

dalam prosedur tersimpan induk, buat tabel #temp yang berisi data yang Anda perlu proses. Panggil prosedur tersimpan anak, tabel #temp akan terlihat dan Anda dapat memprosesnya, semoga bekerja dengan seluruh rangkaian data dan tanpa kursor atau loop.

ini sangat tergantung pada apa yang dilakukan oleh prosedur penyimpanan anak ini. Jika Anda UPDATE, Anda dapat "memperbarui dari" bergabung dalam tabel #temp dan melakukan semua pekerjaan dalam satu pernyataan tanpa loop. Hal yang sama dapat dilakukan untuk INSERT dan DELETEs. Jika Anda perlu melakukan beberapa pembaruan dengan IF, Anda dapat mengonversinya menjadi banyak UPDATE FROMdengan tabel #temp dan menggunakan pernyataan KASUS atau kondisi WHERE.

Ketika bekerja dalam suatu basis data mencoba menghilangkan pola pikir dari perulangan, itu adalah penguras kinerja nyata, akan menyebabkan penguncian / pemblokiran dan memperlambat pemrosesan. Jika Anda berputar di mana-mana, sistem Anda tidak akan skala dengan sangat baik, dan akan sangat sulit untuk mempercepat ketika pengguna mulai mengeluh tentang refresh lambat.

Posting konten dari prosedur ini yang ingin Anda panggil dalam satu lingkaran, dan saya akan bertaruh 9 dari 10 kali, Anda bisa menulisnya untuk bekerja pada satu set baris.

KM.
sumber
3
+1 untuk solusi yang sangat baik, dengan asumsi bahwa Anda mengontrol sproc anak
Steven A. Lowe
sedikit berpikir, solusi ini jauh lebih unggul!
encc
7
Atur operasi berbasis selalu lebih disukai. Namun, perlu diingat bahwa memodifikasi SP tidak selalu merupakan opsi - pikir vendor memberikan solusi. Beberapa pengguna bahkan mungkin tidak memiliki visibilitas, hanya menyisakan opsi kursor atau loop. Di toko saya, pengembang kami dapat melihat semuanya tetapi ada banyak rintangan untuk dihapus jika solusi dibangun di luar aplikasi vendor karena pemicu, proksi bersarang, jumlah catatan dimanipulasi dll. Banyak kali pilihan terbaik mereka, karena kompleksitas aplikasi, adalah hanya dengan kursor melalui catatan.
Steve Mangiameli
11

Sesuatu seperti pergantian ini akan diperlukan untuk tabel dan nama bidang Anda.

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
u07ch
sumber
2
sementara loop lebih lambat dari kursor
Steven A. Lowe
Pernyataan deklarasi kursor SQL tidak didukung (??)
MetaGuru
9

Anda dapat melakukannya dengan kueri dinamis.

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

Apakah ini tidak dapat dilakukan dengan fungsi yang ditentukan pengguna untuk mereplikasi apa pun yang dilakukan oleh prosedur tersimpan Anda?

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

di mana udfMyFunction adalah fungsi yang Anda buat yang mengambil ID pengguna dan melakukan apa pun yang perlu Anda lakukan dengannya.

Lihat http://www.sqlteam.com/article/user-defined-functions untuk sedikit lebih banyak latar belakang

Saya setuju bahwa kursor benar-benar harus dihindari jika memungkinkan. Dan biasanya itu mungkin!

(tentu saja, jawaban saya mengandaikan bahwa Anda hanya tertarik untuk mendapatkan output dari SP dan bahwa Anda tidak mengubah data aktual. Saya menemukan "mengubah data pengguna dengan cara tertentu" sedikit ambigu dari pertanyaan awal, jadi saya pikir saya akan menawarkan ini sebagai solusi yang mungkin. Sangat tergantung pada apa yang Anda lakukan!)

pengacakan
sumber
1
OP: "prosedur tersimpan yang mengubah data pengguna dengan cara tertentu" MSDN : Fungsi yang ditentukan pengguna tidak dapat digunakan untuk melakukan tindakan yang mengubah keadaan basis data. Namun SQLSVR 2014 tampaknya tidak memiliki masalah dengan itu
johnny 5
6

Gunakan variabel tabel atau tabel sementara.

Seperti yang telah disebutkan sebelumnya, kursor adalah pilihan terakhir. Sebagian besar karena menggunakan banyak sumber daya, masalah mengunci dan mungkin menjadi tanda Anda tidak mengerti cara menggunakan SQL dengan benar.

Catatan: Saya pernah menemukan solusi yang menggunakan kursor untuk memperbarui baris dalam sebuah tabel. Setelah beberapa penelitian, ternyata semuanya bisa diganti dengan satu perintah UPDATE. Namun, dalam kasus ini, di mana prosedur tersimpan harus dijalankan, satu perintah SQL tidak akan berfungsi.

Buat variabel tabel seperti ini (jika Anda bekerja dengan banyak data atau kekurangan memori, gunakan tabel sementara sebagai gantinya):

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

Itu idpenting.

Ganti parentdan childdengan beberapa data yang baik, misalnya pengidentifikasi yang relevan atau seluruh rangkaian data yang akan dioperasikan.

Masukkan data dalam tabel, misalnya:

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

Nyatakan beberapa variabel:

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

Dan akhirnya, buat loop sementara di atas data dalam tabel:

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

Pilih pertama mengambil data dari tabel sementara. Pilihan kedua memperbarui @id. MINmengembalikan nol jika tidak ada baris yang dipilih.

Pendekatan alternatif adalah untuk mengulang sementara tabel memiliki baris, SELECT TOP 1dan menghapus baris yang dipilih dari tabel temp:

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;
Erk
sumber
3

Saya suka cara permintaan dinamis dari Dave Rincon karena tidak menggunakan kursor dan kecil dan mudah. Terima kasih, Dave, telah berbagi.

Tetapi untuk kebutuhan saya pada Azure SQL dan dengan "berbeda" dalam permintaan, saya harus memodifikasi kode seperti ini:

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;

Saya harap ini membantu seseorang ...

Tom
sumber