Arka arkaya sütunları sp giriş parametreleri nerede nasıl biri bir tablodaki her satır için saklı yordam çağırabilir olmadan bir imleç kullanarak?
Arka arkaya sütunları sp giriş parametreleri nerede nasıl biri bir tablodaki her satır için saklı yordam çağırabilir olmadan bir imleç kullanarak?
Yanıtlar:
Genel olarak konuşursak her zaman kümeye dayalı bir yaklaşım ararım (bazen şemayı değiştirmek pahasına).
Ancak bu pasajın yeri var ..
-- 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
Bunun gibi bir şey yapabilirsiniz: tablonuzu örneğin MüşteriNo (AdventureWorks Sales.Customer
örnek tablosunu kullanarak) ile sipariş edin ve bir WHILE döngüsü kullanarak bu müşteriler üzerinde tekrarlayın:
-- 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
ORDER BY
Bazı sütunlarda bir tür tanımlayabildiğiniz sürece bu herhangi bir tablo ile çalışmalıdır .
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)
Tamam, bu yüzden bu kodu asla üretime sokmam, ancak gereksinimlerinizi karşılar.
Marc'ın cevabı iyidir (nasıl yapılacağını öğrenebilirsem yorum yapardım!)
Sadece döngüyü değiştirmenin daha iyi olabileceğini düşündüm, bu yüzden SELECT
sadece bir kez var (gerektiğinde gerçek bir durumda) bunu yapmak SELECT
oldukça karmaşıktı ve iki kez yazmak riskli bir bakım sorunuydu).
-- 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
Saklı yordamı bir tablo döndüren bir işleve dönüştürebilirseniz, çapraz uygulama kullanabilirsiniz.
Örneğin, bir müşteri tablonuz olduğunu ve siparişlerinin toplamını hesaplamak istediğinizi varsayarak, MüşteriNo'yu alan ve toplamı döndüren bir işlev yaratabilirsiniz.
Ve bunu yapabilirsiniz:
SELECT CustomerID, CustomerSum.Total
FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
Fonksiyonun nerede görüneceği:
CREATE FUNCTION ComputeCustomerTotal
(
@CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = @CustomerID
)
Açıkçası, yukarıdaki örnek tek bir sorguda kullanıcı tanımlı bir işlev olmadan yapılabilir.
Dezavantajı, fonksiyonların çok sınırlı olmasıdır - saklı bir prosedürün özelliklerinin çoğu kullanıcı tanımlı bir fonksiyonda mevcut değildir ve saklı bir prosedürü bir fonksiyona dönüştürmek her zaman işe yaramaz.
Kabul edilen cevabı kullanırdım, ancak başka bir olasılık, sayılar kümesi (bu durumda sadece bir tablonun ID alanı) tutmak için bir tablo değişkeni kullanmak ve tabloya bir JOIN ile satır numarası ile döngü döngü içindeki eylem için ihtiyacınız olan her şeyi alın.
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
SQL Server 2005 için, bunu CROSS APPLY ve tablo değerli bir işlevle yapabilirsiniz.
Sadece netlik için, saklı yordamın tablo değerli bir işleve dönüştürülebileceği durumlardan bahsediyorum.
Bu yukarıdaki n3rds çözümünün bir varyasyonudur. MIN () kullanıldığından, ORDER BY kullanarak sıralama gerekmez.
CustomerID'nin (veya ilerleme için kullandığınız diğer sayısal sütunların) benzersiz bir kısıtlaması olması gerektiğini unutmayın. Ayrıca, olabildiğince hızlı hale getirmek için CustomerID endekslenmelidir.
-- 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
Bu yaklaşımı, bir kimlik vermek için önce geçici bir tabloya koyarak göz atmam gereken bazı varcharlar üzerinde kullanıyorum.
Bu zaten verilen cevapların bir varyasyonudur, ancak ORDER BY, COUNT veya MIN / MAX gerektirmediği için daha iyi performans göstermelidir. Bu yaklaşımın tek dezavantajı, tüm Kimlikleri tutmak için bir geçici tablo oluşturmanız gerektiğidir (varsayım, CustomerID'ler listenizde boşluklar olduğudur).
Bununla birlikte, @Mark Powell'a katılıyorum, ancak genel olarak konuşursak, set temelli bir yaklaşımın daha iyi olması gerekir.
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
Genellikle birkaç satır olduğunda bu şekilde yaparım:
(Daha büyük veri kümelerinde yukarıda belirtilen çözümlerden birini kullanırım).
DELIMITER //
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;
Bunun için daha iyi bir çözüm
- Saklı Yordamın kodunu kopyala / geç
- Bu kodu, tekrar çalıştırmak istediğiniz tabloyla birleştirin (her satır için)
Bu tablo biçimlendirilmiş temiz bir çıktı almak oldu. Her satır için SP çalıştırırsanız, çirkin olan her yineleme için ayrı bir sorgu sonucu alırsınız.
Siparişin önemli olması durumunda
--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
Bir seferde sadece 20 çalışanı işleyebilecek bazı üretim kodum vardı, kodun çerçevesi aşağıda. Sadece üretim kodunu kopyaladım ve aşağıdaki şeyleri kaldırdım.
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
Buna benzer bir şey yapmayı seviyorum (yine de imleç kullanmaya çok benzer)
[Kod]
-- 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
[/ Kod]
Eğer döngü veya temp / değişken tablonuzda isIterated sütun gerekmez , ben sadece bu şekilde yapmayı tercih böylece i döngü üzerinden yineleme olarak koleksiyonundan üst kayıt silmek zorunda değilsiniz.