Làm thế nào người ta có thể gọi một thủ tục được lưu trữ cho mỗi hàng trong một bảng, trong đó các cột của một hàng là các tham số đầu vào cho sp mà không cần sử dụng Con trỏ?
Làm thế nào người ta có thể gọi một thủ tục được lưu trữ cho mỗi hàng trong một bảng, trong đó các cột của một hàng là các tham số đầu vào cho sp mà không cần sử dụng Con trỏ?
Câu trả lời:
Nói chung, tôi luôn tìm kiếm một cách tiếp cận dựa trên tập hợp (đôi khi phải trả giá cho việc thay đổi lược đồ).
Tuy nhiên, đoạn trích này có vị trí của nó ..
-- Declare & init (2008 syntax)
DECLARE @CustomerID INT = 0
-- Iterate over all customers
WHILE (1 = 1)
BEGIN
-- Get next customerId
SELECT TOP 1 @CustomerID = CustomerID
FROM Sales.Customer
WHERE CustomerID > @CustomerId
ORDER BY CustomerID
-- Exit loop if no more customers
IF @@ROWCOUNT = 0 BREAK;
-- call your sproc
EXEC dbo.YOURSPROC @CustomerId
END
Bạn có thể làm một cái gì đó như thế này: đặt hàng bảng của bạn bằng cách ví dụ CustomerID (sử dụng Sales.Customer
bảng mẫu AdventureWorks ) và lặp lại các khách hàng đó bằng vòng lặp WHILE:
-- define the last customer ID handled
DECLARE @LastCustomerID INT
SET @LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE @CustomerIDToHandle INT
-- select the next customer to handle
SELECT TOP 1 @CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > @LastCustomerID
ORDER BY CustomerID
-- as long as we have customers......
WHILE @CustomerIDToHandle IS NOT NULL
BEGIN
-- call your sproc
-- set the last customer handled to the one we just handled
SET @LastCustomerID = @CustomerIDToHandle
SET @CustomerIDToHandle = NULL
-- select the next customer to handle
SELECT TOP 1 @CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > @LastCustomerID
ORDER BY CustomerID
END
Điều đó sẽ làm việc với bất kỳ bảng nào miễn là bạn có thể định nghĩa một loại nào đó ORDER BY
trên một cột nào đó.
DECLARE @SQL varchar(max)=''
-- MyTable has fields fld1 & fld2
Select @SQL = @SQL + 'exec myproc ' + convert(varchar(10),fld1) + ','
+ convert(varchar(10),fld2) + ';'
From MyTable
EXEC (@SQL)
Ok, vì vậy tôi sẽ không bao giờ đưa mã như vậy vào sản xuất, nhưng nó đáp ứng yêu cầu của bạn.
Câu trả lời của Marc là tốt (Tôi sẽ nhận xét về nó nếu tôi có thể tìm ra cách!)
Chỉ cần nghĩ rằng tôi chỉ ra rằng có thể tốt hơn để thay đổi vòng lặp để SELECT
chỉ tồn tại một lần (trong trường hợp thực tế mà tôi cần làm điều này, SELECT
khá phức tạp và viết nó hai lần là một vấn đề bảo trì rủi ro).
-- define the last customer ID handled
DECLARE @LastCustomerID INT
SET @LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE @CustomerIDToHandle INT
SET @CustomerIDToHandle = 1
-- as long as we have customers......
WHILE @LastCustomerID <> @CustomerIDToHandle
BEGIN
SET @LastCustomerId = @CustomerIDToHandle
-- select the next customer to handle
SELECT TOP 1 @CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > @LastCustomerId
ORDER BY CustomerID
IF @CustomerIDToHandle <> @LastCustomerID
BEGIN
-- call your sproc
END
END
Nếu bạn có thể biến thủ tục được lưu trữ thành một hàm trả về một bảng, thì bạn có thể sử dụng áp dụng chéo.
Ví dụ: giả sử bạn có một bảng khách hàng và bạn muốn tính tổng số đơn đặt hàng của họ, bạn sẽ tạo một hàm lấy ID khách hàng và trả lại tổng.
Và bạn có thể làm điều này:
SELECT CustomerID, CustomerSum.Total
FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
Chức năng sẽ như thế nào:
CREATE FUNCTION ComputeCustomerTotal
(
@CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = @CustomerID
)
Rõ ràng, ví dụ trên có thể được thực hiện mà không có hàm do người dùng xác định trong một truy vấn duy nhất.
Hạn chế là các chức năng rất hạn chế - nhiều tính năng của một thủ tục được lưu trữ không có sẵn trong một hàm do người dùng xác định và việc chuyển đổi một thủ tục được lưu trữ thành một chức năng không phải lúc nào cũng hoạt động.
Tôi sẽ sử dụng câu trả lời được chấp nhận, nhưng một khả năng khác là sử dụng biến bảng để giữ một bộ giá trị được đánh số (trong trường hợp này chỉ là trường ID của bảng) và lặp qua các số đó bằng Row Number với THAM GIA vào bảng để lấy bất cứ thứ gì bạn cần cho hành động trong vòng lặp.
DECLARE @RowCnt int; SET @RowCnt = 0 -- Loop Counter
-- Use a table variable to hold numbered rows containg MyTable's ID values
DECLARE @tblLoop TABLE (RowNum int IDENTITY (1, 1) Primary key NOT NULL,
ID INT )
INSERT INTO @tblLoop (ID) SELECT ID FROM MyTable
-- Vars to use within the loop
DECLARE @Code NVarChar(10); DECLARE @Name NVarChar(100);
WHILE @RowCnt < (SELECT COUNT(RowNum) FROM @tblLoop)
BEGIN
SET @RowCnt = @RowCnt + 1
-- Do what you want here with the data stored in tblLoop for the given RowNum
SELECT @Code=Code, @Name=LongName
FROM MyTable INNER JOIN @tblLoop tL on MyTable.ID=tL.ID
WHERE tl.RowNum=@RowCnt
PRINT Convert(NVarChar(10),@RowCnt) +' '+ @Code +' '+ @Name
END
Đối với SQL Server 2005 trở đi, bạn có thể thực hiện việc này với CROSS ỨNG DỤNG và hàm có giá trị bảng.
Để rõ ràng, tôi đang đề cập đến những trường hợp trong đó thủ tục được lưu trữ có thể được chuyển đổi thành một hàm có giá trị bảng.
Đây là một biến thể của giải pháp n3rd ở trên. Không cần sắp xếp bằng cách sử dụng ORDER BY, vì MIN () được sử dụng.
Hãy nhớ rằng CustomerID (hoặc bất kỳ cột số nào khác mà bạn sử dụng để tiến bộ) phải có một ràng buộc duy nhất. Hơn nữa, để làm cho nó càng nhanh càng tốt, ID khách hàng phải được lập chỉ mục.
-- Declare & init
DECLARE @CustomerID INT = (SELECT MIN(CustomerID) FROM Sales.Customer); -- First ID
DECLARE @Data1 VARCHAR(200);
DECLARE @Data2 VARCHAR(200);
-- Iterate over all customers
WHILE @CustomerID IS NOT NULL
BEGIN
-- Get data based on ID
SELECT @Data1 = Data1, @Data2 = Data2
FROM Sales.Customer
WHERE [ID] = @CustomerID ;
-- call your sproc
EXEC dbo.YOURSPROC @Data1, @Data2
-- Get next customerId
SELECT @CustomerID = MIN(CustomerID)
FROM Sales.Customer
WHERE CustomerID > @CustomerId
END
Tôi sử dụng phương pháp này trên một số varchars tôi cần xem qua, bằng cách đặt chúng vào một bảng tạm thời trước, để cung cấp cho chúng một ID.
Đây là một biến thể của các câu trả lời đã được cung cấp, nhưng sẽ hoạt động tốt hơn vì nó không yêu cầu ĐẶT HÀNG B, NG, hoặc MIN / MAX. Nhược điểm duy nhất với cách tiếp cận này là bạn phải tạo một bảng tạm thời để chứa tất cả các Id (giả định là bạn có những khoảng trống trong danh sách ID khách hàng).
Điều đó nói rằng, tôi đồng ý với @Mark Powell mặc dù nói chung, cách tiếp cận dựa trên tập hợp vẫn sẽ tốt hơn.
DECLARE @tmp table (Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, CustomerID INT NOT NULL)
DECLARE @CustomerId INT
DECLARE @Id INT = 0
INSERT INTO @tmp SELECT CustomerId FROM Sales.Customer
WHILE (1=1)
BEGIN
SELECT @CustomerId = CustomerId, @Id = Id
FROM @tmp
WHERE Id = @Id + 1
IF @@rowcount = 0 BREAK;
-- call your sproc
EXEC dbo.YOURSPROC @CustomerId;
END
Tôi thường làm theo cách này khi nó có một vài hàng:
(Trên các bộ dữ liệu lớn hơn, tôi sẽ sử dụng một trong những giải pháp được đề cập ở trên).
GIAO HÀNG //
CREATE PROCEDURE setFakeUsers (OUT output VARCHAR(100))
BEGIN
-- define the last customer ID handled
DECLARE LastGameID INT;
DECLARE CurrentGameID INT;
DECLARE userID INT;
SET @LastGameID = 0;
-- define the customer ID to be handled now
SET @userID = 0;
-- select the next game to handle
SELECT @CurrentGameID = id
FROM online_games
WHERE id > LastGameID
ORDER BY id LIMIT 0,1;
-- as long as we have customers......
WHILE (@CurrentGameID IS NOT NULL)
DO
-- call your sproc
-- set the last customer handled to the one we just handled
SET @LastGameID = @CurrentGameID;
SET @CurrentGameID = NULL;
-- select the random bot
SELECT @userID = userID
FROM users
WHERE FIND_IN_SET('bot',baseInfo)
ORDER BY RAND() LIMIT 0,1;
-- update the game
UPDATE online_games SET userID = @userID WHERE id = @CurrentGameID;
-- select the next game to handle
SELECT @CurrentGameID = id
FROM online_games
WHERE id > LastGameID
ORDER BY id LIMIT 0,1;
END WHILE;
SET output = "done";
END;//
CALL setFakeUsers(@status);
SELECT @status;
Một giải pháp tốt hơn cho việc này là
- Sao chép / mã quá khứ của Thủ tục lưu trữ
- Tham gia mã đó với bảng mà bạn muốn chạy lại nó (cho mỗi hàng)
Đây là bạn có được một đầu ra định dạng bảng sạch. Trong khi nếu bạn chạy SP cho mỗi hàng, bạn sẽ nhận được một kết quả truy vấn riêng cho mỗi lần lặp đó là xấu.
Trong trường hợp thứ tự là quan trọng
--declare counter
DECLARE @CurrentRowNum BIGINT = 0;
--Iterate over all rows in [DataTable]
WHILE (1 = 1)
BEGIN
--Get next row by number of row
SELECT TOP 1 @CurrentRowNum = extendedData.RowNum
--here also you can store another values
--for following usage
--@MyVariable = extendedData.Value
FROM (
SELECT
data.*
,ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RowNum
FROM [DataTable] data
) extendedData
WHERE extendedData.RowNum > @CurrentRowNum
ORDER BY extendedData.RowNum
--Exit loop if no more rows
IF @@ROWCOUNT = 0 BREAK;
--call your sproc
--EXEC dbo.YOURSPROC @MyVariable
END
Tôi đã có một số mã sản xuất chỉ có thể xử lý 20 nhân viên một lúc, dưới đây là khung cho mã. Tôi chỉ sao chép mã sản xuất và loại bỏ những thứ bên dưới.
ALTER procedure GetEmployees
@ClientId varchar(50)
as
begin
declare @EEList table (employeeId varchar(50));
declare @EE20 table (employeeId varchar(50));
insert into @EEList select employeeId from Employee where (ClientId = @ClientId);
-- Do 20 at a time
while (select count(*) from @EEList) > 0
BEGIN
insert into @EE20 select top 20 employeeId from @EEList;
-- Call sp here
delete @EEList where employeeId in (select employeeId from @EE20)
delete @EE20;
END;
RETURN
end
Tôi thích làm một cái gì đó tương tự như thế này (mặc dù nó vẫn rất giống với việc sử dụng một con trỏ)
[mã]
-- Table variable to hold list of things that need looping
DECLARE @holdStuff TABLE (
id INT IDENTITY(1,1) ,
isIterated BIT DEFAULT 0 ,
someInt INT ,
someBool BIT ,
otherStuff VARCHAR(200)
)
-- Populate your @holdStuff with... stuff
INSERT INTO @holdStuff (
someInt ,
someBool ,
otherStuff
)
SELECT
1 , -- someInt - int
1 , -- someBool - bit
'I like turtles' -- otherStuff - varchar(200)
UNION ALL
SELECT
42 , -- someInt - int
0 , -- someBool - bit
'something profound' -- otherStuff - varchar(200)
-- Loop tracking variables
DECLARE @tableCount INT
SET @tableCount = (SELECT COUNT(1) FROM [@holdStuff])
DECLARE @loopCount INT
SET @loopCount = 1
-- While loop variables
DECLARE @id INT
DECLARE @someInt INT
DECLARE @someBool BIT
DECLARE @otherStuff VARCHAR(200)
-- Loop through item in @holdStuff
WHILE (@loopCount <= @tableCount)
BEGIN
-- Increment the loopCount variable
SET @loopCount = @loopCount + 1
-- Grab the top unprocessed record
SELECT TOP 1
@id = id ,
@someInt = someInt ,
@someBool = someBool ,
@otherStuff = otherStuff
FROM @holdStuff
WHERE isIterated = 0
-- Update the grabbed record to be iterated
UPDATE @holdAccounts
SET isIterated = 1
WHERE id = @id
-- Execute your stored procedure
EXEC someRandomSp @someInt, @someBool, @otherStuff
END
[/ mã]
Lưu ý rằng bạn không cần danh tính hoặc cột isIterated trên bảng temp / biến của bạn, tôi chỉ muốn làm theo cách này vì vậy tôi không phải xóa bản ghi hàng đầu khỏi bộ sưu tập khi tôi lặp qua vòng lặp.