Làm cách nào để tôi thực hiện một thủ tục được lưu trữ một lần cho mỗi hàng được trả về bởi truy vấn?


206

Tôi có một quy trình được lưu trữ làm thay đổi dữ liệu người dùng theo một cách nhất định. Tôi vượt qua nó user_id và nó làm điều đó. Tôi muốn chạy truy vấn trên một bảng và sau đó với mỗi user_id tôi thấy chạy thủ tục được lưu trữ một lần trên user_id đó

Làm thế nào tôi có thể viết truy vấn cho điều này?


5
Bạn cần xác định RDBMS là gì - câu trả lời sẽ khác nhau đối với SQL Server, Oracle, MySql, v.v.
Gary.Ray

5
Rất có thể là bạn không cần một thủ tục lưu trữ nào cả. Bạn có thể phác thảo "những gì" thủ tục được lưu trữ, chính xác? Có lẽ toàn bộ quá trình có thể được thể hiện dưới dạng một tuyên bố cập nhật duy nhất. Nói chung, nên tránh mẫu "làm một lần cho mỗi bản ghi".
Tomalak

Bạn đang sử dụng cơ sở dữ liệu nào?
Người dùng SO

1
Bạn nên đọc bài viết này ... mục 2 nói KHÔNG sử dụng con trỏ codeproject.com/KB/database/sqldodont.aspx..tôi cũng chống lại tối ưu hóa sớm.
Michael Prewecki

7
@MichaelPrewecki: Nếu bạn đọc thêm trong bài viết kém đó, bạn sẽ thấy mục 10 đó là "KHÔNG sử dụng con trỏ phía máy chủ trừ khi bạn biết bạn đang làm gì." Tôi nghĩ rằng đây là một trường hợp "Tôi biết những gì tôi đang làm".
Gabe

Câu trả lời:


245

sử dụng một con trỏ

ĐỊA CHỈ: [Ví dụ về con trỏ 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

trong MS SQL, đây là một bài viết ví dụ

lưu ý rằng các con trỏ chậm hơn các thao tác dựa trên tập hợp, nhưng nhanh hơn các vòng lặp thủ công; biết thêm chi tiết trong câu hỏi SO này

ĐỊA CHỈ 2: nếu bạn sẽ xử lý nhiều hơn chỉ một vài bản ghi, trước tiên hãy kéo chúng vào bảng tạm thời và chạy con trỏ qua bảng tạm thời; điều này sẽ ngăn SQL leo thang vào các khóa bảng và tăng tốc hoạt động

ĐỊA CHỈ 3: và tất nhiên, nếu bạn có thể nội tuyến bất cứ quy trình được lưu trữ nào của bạn đang làm với từng ID người dùng và chạy toàn bộ dưới dạng một câu lệnh cập nhật SQL duy nhất, điều đó sẽ tối ưu


21
Bạn đã bỏ lỡ 'cur cur' sau khi khai báo - điều này đã cho tôi 'con trỏ không mở'. Tôi không có đại diện để chỉnh sửa.
Fiona - myaccessible.website

5
Bạn có thể cảm ơn mọi người bằng cách bỏ phiếu bình luận của họ. Ai biết được, có lẽ theo cách đó họ sẽ có đại diện để thực hiện chỉnh sửa, lần sau! :-)
Robino

Hãy chắc chắn rằng bạn kiểm tra các chỉ mục của mình trên các mệnh đề THAM GIA và WHERE trong các trường được sử dụng trong quy trình được lưu trữ của bạn. Tôi đột ngột tăng tốc gọi SP của mình trong một vòng lặp sau khi thêm các chỉ mục thích hợp.
Matthew

1
Cảm ơn bạn đã nhắc nhở sử dụng bảng tạm thời để tránh các sự cố khóa tiềm ẩn do thực thi lâu.
Tony

Đôi khi thủ tục được lưu trữ quá lớn hoặc phức tạp để nội tuyến mà không có nguy cơ giới thiệu các lỗi. Trong đó hiệu suất không phải là ưu tiên cuối cùng, thực thi SP trong vòng lặp con trỏ thường là lựa chọn thiết thực nhất.
Suncat2000

55

cố gắng thay đổi phương pháp của bạn nếu bạn cần lặp!

trong thủ tục được lưu trữ cha, hãy tạo bảng #temp chứa dữ liệu mà bạn cần xử lý. Gọi thủ tục lưu trữ con, bảng #temp sẽ hiển thị và bạn có thể xử lý nó, hy vọng sẽ làm việc với toàn bộ tập hợp dữ liệu và không có con trỏ hoặc vòng lặp.

điều này thực sự phụ thuộc vào những gì thủ tục lưu trữ con này đang làm. Nếu bạn đang CẬP NHẬT, bạn có thể "cập nhật" từ việc tham gia vào bảng #temp và thực hiện tất cả công việc trong một câu lệnh mà không cần vòng lặp. Điều tương tự có thể được thực hiện đối với CHERTN và XÓA. Nếu bạn cần thực hiện nhiều cập nhật với IF, bạn có thể chuyển đổi chúng thành nhiều bản UPDATE FROMvới bảng #temp và sử dụng các câu lệnh CASE hoặc điều kiện WHERE.

Khi làm việc trong cơ sở dữ liệu cố gắng làm mất tư duy lặp, đó là một hiệu suất thực sự, sẽ gây ra khóa / chặn và làm chậm quá trình xử lý. Nếu bạn lặp ở mọi nơi, hệ thống của bạn sẽ không mở rộng rất tốt và sẽ rất khó tăng tốc khi người dùng bắt đầu phàn nàn về việc làm mới chậm.

Đăng nội dung của quy trình này mà bạn muốn gọi trong một vòng lặp và tôi sẽ đặt cược 9 trên 10 lần, bạn có thể viết nó để làm việc trên một tập hợp các hàng.


3
+1 cho một cách giải quyết rất tốt, giả sử rằng bạn điều khiển con quay
Steven A. Lowe

một chút suy nghĩ, sự giải thích này là vượt trội!
encc

7
Đặt hoạt động dựa trên luôn luôn thích hợp hơn. Tuy nhiên, hãy nhớ rằng sửa đổi SP không phải lúc nào cũng là một lựa chọn - hãy nghĩ rằng nhà cung cấp cung cấp giải pháp. Một số người dùng thậm chí có thể không có khả năng hiển thị, chỉ để lại các tùy chọn con trỏ hoặc vòng lặp. Trong cửa hàng của tôi, các nhà phát triển của chúng tôi có thể nhìn thấy mọi thứ nhưng có rất nhiều trở ngại để làm rõ nếu một giải pháp được xây dựng bên ngoài ứng dụng của nhà cung cấp do trình kích hoạt, procs lồng nhau, số lượng hồ sơ bị thao túng, v.v. độ phức tạp của ứng dụng, chỉ đơn giản là con trỏ qua các bản ghi.
Steve Mangiameli

11

Một cái gì đó như sự thay thế này sẽ cần thiết cho các bảng và tên trường của bạn.

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
trong khi các vòng lặp chậm hơn các con trỏ
Steven A. Lowe

Cấu trúc hoặc câu lệnh SQL con trỏ khai báo không được hỗ trợ (??)
MetaGuru

9

Bạn có thể làm điều đó với một truy vấn động.

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

6

Điều này có thể không được thực hiện với chức năng do người dùng xác định để sao chép bất cứ quy trình được lưu trữ nào của bạn không?

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

trong đó udfMyFunction là một chức năng bạn thực hiện trong ID người dùng và làm bất cứ điều gì bạn cần làm với nó.

Xem http://www.sqlteam.com/article/user-dained-fifts để biết thêm một chút nền tảng

Tôi đồng ý rằng các con trỏ thực sự nên tránh nếu có thể. Và nó thường là có thể!

(tất nhiên, câu trả lời của tôi giả định rằng bạn chỉ quan tâm đến việc nhận đầu ra từ SP và rằng bạn không thay đổi dữ liệu thực tế. Tôi thấy "thay đổi dữ liệu người dùng theo một cách nhất định" một chút mơ hồ từ câu hỏi ban đầu, vì vậy tôi nghĩ rằng tôi sẽ cung cấp điều này như một giải pháp khả thi. Hoàn toàn phụ thuộc vào những gì bạn đang làm!)


1
OP: "thủ tục được lưu trữ làm thay đổi dữ liệu người dùng theo một cách nhất định" MSDN : Các hàm do người dùng xác định có thể được sử dụng để thực hiện các hành động sửa đổi trạng thái cơ sở dữ liệu. Tuy nhiên, SQLSVR 2014 dường như không có vấn đề gì với nó
johnny 5/12

6

Sử dụng một biến bảng hoặc một bảng tạm thời.

Như đã đề cập trước đây, một con trỏ là phương sách cuối cùng. Chủ yếu là vì nó sử dụng nhiều tài nguyên, các vấn đề khóa và có thể là một dấu hiệu bạn không hiểu cách sử dụng SQL đúng cách.

Lưu ý bên lề: Tôi đã từng gặp một giải pháp sử dụng con trỏ để cập nhật các hàng trong bảng. Sau khi xem xét kỹ lưỡng, hóa ra toàn bộ điều có thể được thay thế bằng một lệnh CẬP NHẬT duy nhất. Tuy nhiên, trong trường hợp này, khi một thủ tục được lưu trữ sẽ được thực thi, một lệnh SQL sẽ không hoạt động.

Tạo một biến bảng như thế này (nếu bạn đang làm việc với nhiều dữ liệu hoặc thiếu bộ nhớ, thay vào đó hãy sử dụng bảng tạm thời ):

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

Điều idquan trọng.

Thay thế parentchildbằng một số dữ liệu tốt, ví dụ: số nhận dạng có liên quan hoặc toàn bộ bộ dữ liệu sẽ được vận hành.

Chèn dữ liệu vào bảng, vd:

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

Khai báo một số biến:

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

Và cuối cùng, tạo một vòng lặp while trên dữ liệu trong bảng:

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

Lựa chọn đầu tiên lấy dữ liệu từ bảng tạm thời. Lựa chọn thứ hai cập nhật @id. MINtrả về null nếu không có hàng nào được chọn.

Một cách tiếp cận khác là lặp trong khi bảng có các hàng SELECT TOP 1và xóa hàng đã chọn khỏi bảng tạm thời:

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

Tôi thích cách truy vấn động của Dave Rincon vì nó không sử dụng con trỏ và nhỏ và dễ dàng. Cảm ơn Dave đã chia sẻ.

Nhưng đối với nhu cầu của tôi trên Azure SQL và với "sự khác biệt" trong truy vấn, tôi đã phải sửa đổi mã như thế này:

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;

Tôi hi vọng điêu nay se giup được ai đo...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.