Есть ли способ перебрать переменную таблицы в TSQL без использования курсора?
Скажем, у меня есть следующая простая переменная таблицы:
declare @databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into @databases
Я объявляю и использую курсор только для моего варианта, если захочу итерации по строкам? Есть ли другой способ?
Ответы
Ответ 1
Прежде всего, вы должны быть абсолютно уверены, что вам нужно выполнять итерацию по каждой строке - операции на основе набора будут выполняться быстрее в каждом случае, о котором я могу думать, и обычно используют более простой код.
В зависимости от ваших данных может быть возможно выполнить цикл только с помощью операторов select, как показано ниже:
Declare @Id int
While (Select Count(*) From ATable Where Processed = 0) > 0
Begin
Select Top 1 @Id = Id From ATable Where Processed = 0
--Do some processing here
Update ATable Set Processed = 1 Where Id = @Id
End
Другой альтернативой является использование временной таблицы:
Select *
Into #Temp
From ATable
Declare @Id int
While (Select Count(*) From #Temp) > 0
Begin
Select Top 1 @Id = Id From #Temp
--Do some processing here
Delete #Temp Where Id = @Id
End
Выбор, который вы должны выбрать, действительно зависит от структуры и объема ваших данных.
Примечание.. Если вы используете SQL Server, вам лучше использовать:
WHILE EXISTS(SELECT * FROM #Temp)
Использование COUNT
должно касаться каждой отдельной строки в таблице, EXISTS
нужно только коснуться первой (см. ответ Йозефа ниже).
Ответ 2
Просто быстрое замечание: если вы используете SQL Server (2008 и выше), примеры, которые имеют:
While (Select Count(*) From #Temp) > 0
Лучше бы подали с
While EXISTS(SELECT * From #Temp)
Счет должен будет касаться каждой строки в таблице, EXISTS
должен касаться только первой.
Ответ 3
Вот как я это делаю:
declare @RowNum int, @CustId nchar(5), @Name1 nchar(25)
select @CustId=MAX(USERID) FROM UserIDs --start with the highest ID
Select @RowNum = Count(*) From UserIDs --get total number of records
WHILE @RowNum > 0 --loop until no more records
BEGIN
select @Name1 = username1 from UserIDs where USERID= @CustID --get other info from that row
print cast(@RowNum as char(12)) + ' ' + @CustId + ' ' + @Name1 --do whatever
select top 1 @CustId=USERID from UserIDs where USERID < @CustID order by USERID desc--get the next one
set @RowNum = @RowNum - 1 --decrease count
END
Нет курсоров, временных таблиц и дополнительных столбцов.
Столбец USERID должен быть уникальным целым числом, так как большинство основных ключей.
Ответ 4
Определите таблицу темпа как это -
declare @databases table
(
RowID int not null identity(1,1) primary key,
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into @databases
Тогда сделайте это -
declare @i int
select @i = min(RowID) from @databases
declare @max int
select @max = max(RowID) from @databases
while @i <= @max begin
select DatabaseID, Name, Server from @database where RowID = @i --do some stuff
set @i = @i + 1
end
Ответ 5
Вот как я это сделаю:
Select Identity(int, 1,1) AS PK, DatabaseID
Into #T
From @databases
Declare @maxPK int;Select @maxPK = MAX(PK) From #T
Declare @pk int;Set @pk = 1
While @pk <= @maxPK
Begin
-- Get one record
Select DatabaseID, Name, Server
From @databases
Where DatabaseID = (Select DatabaseID From #T Where PK = @pk)
--Do some processing here
--
Select @pk = @pk + 1
End
[Edit] Поскольку я, вероятно, пропустил слово "переменная", когда впервые прочитал вопрос, вот обновленный ответ...
declare @databases table
(
PK int IDENTITY(1,1),
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into @databases
--/*
INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MainDB', 'MyServer'
INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MyDB', 'MyServer2'
--*/
Declare @maxPK int;Select @maxPK = MAX(PK) From @databases
Declare @pk int;Set @pk = 1
While @pk <= @maxPK
Begin
/* Get one record (you can read the values into some variables) */
Select DatabaseID, Name, Server
From @databases
Where PK = @pk
/* Do some processing here */
/* ... */
Select @pk = @pk + 1
End
Ответ 6
Если у вас нет выбора, чем идти по строкам, создавая курсор FAST_FORWARD. Это будет так же быстро, как создание замкнутой петли и намного легче поддерживать в течение длительного времени.
FAST_FORWARD Задает курсор FORWARD_ONLY, READ_ONLY с включенной оптимизацией производительности. FAST_FORWARD не может быть указан, если также указан параметр SCROLL или FOR_UPDATE.
Ответ 7
Другой подход без изменения вашей схемы или использования временных таблиц:
DECLARE @rowCount int = 0
,@currentRow int = 1
,@databaseID int
,@name varchar(15)
,@server varchar(15);
SELECT @rowCount = COUNT(*)
FROM @databases;
WHILE (@currentRow <= @rowCount)
BEGIN
SELECT TOP 1
@databaseID = rt.[DatabaseID]
,@name = rt.[Name]
,@server = rt.[Server]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY t.[DatabaseID], t.[Name], t.[Server]
) AS [RowNumber]
,t.[DatabaseID]
,t.[Name]
,t.[Server]
FROM @databases t
) rt
WHERE rt.[RowNumber] = @currentRow;
EXEC [your_stored_procedure] @databaseID, @name, @server;
SET @currentRow = @currentRow + 1;
END
Ответ 8
Вы можете использовать цикл while:
While (Select Count(*) From #TempTable) > 0
Begin
Insert Into @Databases...
Delete From #TempTable Where x = x
End
Ответ 9
-- [PO_RollBackOnReject] 'FININV10532'
alter procedure PO_RollBackOnReject
@CaseID nvarchar(100)
AS
Begin
SELECT *
INTO #tmpTable
FROM PO_InvoiceItems where CaseID = @CaseID
Declare @Id int
Declare @PO_No int
Declare @Current_Balance Money
While (Select ROW_NUMBER() OVER(ORDER BY PO_LineNo DESC) From #tmpTable) > 0
Begin
Select Top 1 @Id = PO_LineNo, @Current_Balance = Current_Balance,
@PO_No = PO_No
From #Temp
update PO_Details
Set Current_Balance = Current_Balance + @Current_Balance,
Previous_App_Amount= Previous_App_Amount + @Current_Balance,
Is_Processed = 0
Where PO_LineNumber = @Id
AND PO_No = @PO_No
update PO_InvoiceItems
Set IsVisible = 0,
Is_Processed= 0
,Is_InProgress = 0 ,
Is_Active = 0
Where PO_LineNo = @Id
AND PO_No = @PO_No
End
End
Ответ 10
Легкий, без необходимости делать дополнительные таблицы, если у вас есть целое число ID
в таблице
Declare @id int = 0, @anything nvarchar(max)
WHILE(1=1) BEGIN
Select Top 1 @anything=[Anything],@[email protected]+1 FROM Table WHERE ID>@id
if(@@ROWCOUNT=0) break;
--Process @anything
END
Ответ 11
Я действительно не понимаю, зачем вам прибегать к использованию страшного cursor
.
Но вот еще один вариант, если вы используете SQL Server версии 2005/2008
Используйте Рекурсия
declare @databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
--; Insert records into @databases...
--; Recurse through @databases
;with DBs as (
select * from @databases where DatabaseID = 1
union all
select A.* from @databases A
inner join DBs B on A.DatabaseID = B.DatabaseID + 1
)
select * from DBs
Ответ 12
Я собираюсь предоставить решение на основе набора.
insert @databases (DatabaseID, Name, Server)
select DatabaseID, Name, Server
From ... (Use whatever query you would have used in the loop or cursor)
Это намного быстрее, чем любая петлевая техника, и ее легче писать и поддерживать.
Ответ 13
Это будет работать в версии SQL SERVER 2012.
declare @Rowcount int
select @Rowcount=count(*) from AddressTable;
while( @Rowcount>0)
begin
select @[email protected];
SELECT * FROM AddressTable order by AddressId desc OFFSET @Rowcount ROWS FETCH NEXT 1 ROWS ONLY;
end
Ответ 14
Я предпочитаю использовать Offset Fetch, если у вас есть уникальный идентификатор, вы можете сортировать таблицу:
DECLARE @TableVariable (ID int, Name varchar(50));
DECLARE @RecordCount int;
SELECT @RecordCount = COUNT(*) FROM @TableVariable;
WHILE @RecordCount > 0
BEGIN
SELECT ID, Name FROM @TableVariable ORDER BY ID OFFSET @RecordCount - 1 FETCH NEXT 1 ROW;
SET @RecordCount = @RecordCount - 1;
END
Таким образом, мне не нужно добавлять поля в таблицу или использовать функцию окна.
Ответ 15
Для этого можно использовать курсор:
создать функцию [dbo].f_teste_loop
возвращает таблицу @tabela
( cod int, ном варчар (10)
)
в виде
начать
insert into @tabela values (1, 'verde');
insert into @tabela values (2, 'amarelo');
insert into @tabela values (3, 'azul');
insert into @tabela values (4, 'branco');
return;
конец
создать процедуру [dbo]. [sp_teste_loop]
в виде
начать
DECLARE @cod int, @nome varchar(10);
DECLARE curLoop CURSOR STATIC LOCAL
FOR
SELECT
cod
,nome
FROM
dbo.f_teste_loop();
OPEN curLoop;
FETCH NEXT FROM curLoop
INTO @cod, @nome;
WHILE (@@FETCH_STATUS = 0)
BEGIN
PRINT @nome;
FETCH NEXT FROM curLoop
INTO @cod, @nome;
END
CLOSE curLoop;
DEALLOCATE curLoop;
конец
Ответ 16
Я согласен с предыдущим сообщением в том, что операции с набором основаны, как правило, лучше, но если вам нужно перебирать строки в этом порядке, я бы взял:
- Добавить новое поле в переменную таблицы (Тип данных Бит, по умолчанию 0)
- Вставьте свои данные
- Выберите Top 1 Row, где fUsed = 0 (Примечание: fUsed - это имя поля на шаге 1)
- Выполняйте любую обработку, которую вам нужно выполнить
- Обновите запись в переменной таблицы, установив fUsed = 1 для записи
-
Выберите следующую неиспользуемую запись из таблицы и повторите процесс
DECLARE @databases TABLE
(
DatabaseID int,
Name varchar(15),
Server varchar(15),
fUsed BIT DEFAULT 0
)
-- insert a bunch rows into @databases
DECLARE @DBID INT
SELECT TOP 1 @DBID = DatabaseID from @databases where fUsed = 0
WHILE @@ROWCOUNT <> 0 and @DBID IS NOT NULL
BEGIN
-- Perform your processing here
--Update the record to "used"
UPDATE @databases SET fUsed = 1 WHERE DatabaseID = @DBID
--Get the next record
SELECT TOP 1 @DBID = DatabaseID from @databases where fUsed = 0
END
Ответ 17
Этот подход требует только одной переменной и не удаляет строки из @databases. Я знаю, что здесь есть много ответов, но я не вижу того, который использует MIN, чтобы получить следующий идентификатор, подобный этому.
DECLARE @databases TABLE
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into @databases
DECLARE @CurrID INT
SELECT @CurrID = MIN(DatabaseID)
FROM @databases
WHILE @CurrID IS NOT NULL
BEGIN
-- Do stuff for @CurrID
SELECT @CurrID = MIN(DatabaseID)
FROM @databases
WHERE DatabaseID > @CurrID
END
Ответ 18
Здесь мое решение, использующее бесконечный цикл, оператор BREAK
и @@ROWCOUNT
. Никаких курсоров или временной таблицы не требуется, и мне нужно только написать один запрос, чтобы получить следующую строку в таблице @databases
:
declare @databases table
(
DatabaseID int,
[Name] varchar(15),
[Server] varchar(15)
);
-- Populate the [@databases] table with test data.
insert into @databases (DatabaseID, [Name], [Server])
select X.DatabaseID, X.[Name], X.[Server]
from (values
(1, 'Roger', 'ServerA'),
(5, 'Suzy', 'ServerB'),
(8675309, 'Jenny', 'TommyTutone')
) X (DatabaseID, [Name], [Server])
-- Create an infinite loop & ensure that a break condition is reached in the loop code.
declare @databaseId int;
while (1=1)
begin
-- Get the next database ID.
select top(1) @databaseId = DatabaseId
from @databases
where DatabaseId > isnull(@databaseId, 0);
-- If no rows were found by the preceding SQL query, you're done; exit the WHILE loop.
if (@@ROWCOUNT = 0) break;
-- Otherwise, do whatever you need to do with the current [@databases] table row here.
print 'Processing @databaseId #' + cast(@databaseId as varchar(50));
end
Ответ 19
Это код, который я использую 2008 R2. Этот код, который я использую, заключается в создании индексов в ключевых полях (SSNO и EMPR_NO) n всех сказок
if object_ID('tempdb..#a')is not NULL drop table #a
select 'IF EXISTS (SELECT name FROM sysindexes WHERE name ='+CHAR(39)+''+'IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+char(39)+')'
+' begin DROP INDEX [IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+'] ON '+table_schema+'.'+table_name+' END Create index IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+ ' on '+ table_schema+'.'+table_name+' ('+COLUMN_NAME+') ' 'Field'
,ROW_NUMBER() over (order by table_NAMe) as 'ROWNMBR'
into #a
from INFORMATION_SCHEMA.COLUMNS
where (COLUMN_NAME like '%_SSNO_%' or COLUMN_NAME like'%_EMPR_NO_')
and TABLE_SCHEMA='dbo'
declare @loopcntr int
declare @ROW int
declare @String nvarchar(1000)
set @loopcntr=(select count(*) from #a)
set @ROW=1
while (@ROW <= @loopcntr)
begin
select top 1 @String=a.Field
from #A a
where a.ROWNMBR = @ROW
execute sp_executesql @String
set @ROW = @ROW + 1
end
Ответ 20
SELECT @pk = @pk + 1
будет лучше:
SET @pk += @pk
Избегайте использования SELECT, если вы не ссылаетесь на таблицы, которые просто присваивают значения.
Ответ 21
Шаг1: оператор "Следующий оператор" создает временную таблицу с уникальным номером строки для каждой записи.
select eno,ename,eaddress,mobno int,row_number() over(order by eno desc) as rno into #tmp_sri from emp
Шаг 2: объявить требуемые переменные
DECLARE @ROWNUMBER INT
DECLARE @ename varchar(100)
Шаг 3: выведите общее количество строк из таблицы temp
SELECT @ROWNUMBER = COUNT(*) FROM #tmp_sri
declare @rno int
Шаг4: Временная таблица Loop на основе уникального номера строки создает в temp
while @rownumber>0
begin
set @[email protected]
select @ename=ename from #tmp_sri where [email protected] **// You can take columns data from here as many as you want**
set @[email protected]
print @ename **// instead of printing, you can write insert, update, delete statements**
end