2009-10-16 18 views
56

¿Hay alguna forma de recorrer una variable de tabla en T-SQL?¿Puedo recorrer una variable de tabla en T-SQL?

DECLARE @table1 TABLE (col1 int) 
INSERT into @table1 SELECT col1 FROM table2 

que utilizar los cursores también, pero cursores parecen menos flexibles que las variables de tabla.

DECLARE cursor1 CURSOR 
    FOR SELECT col1 FROM table2 
OPEN cursor1 
FETCH NEXT FROM cursor1 

Me gustaría poder utilizar una variable de tabla de la misma manera que un cursor. De esa manera podría ejecutar alguna consulta sobre la variable de la tabla en una parte del procedimiento, y luego ejecutar algún código para cada fila en la variable de la tabla.

Cualquier ayuda es muy apreciada.

+0

pregunta similar aquí: http://stackoverflow.com/questions/61967/is-there-a-way-to-loop-through-a-table-variable-in-tsql-without-using-a- cursor – demp

+2

"los cursores parecen menos flexibles que las variables de tabla". Esta afirmación realmente no tiene sentido. Son cosas completamente diferentes. Ciertamente puede usar un cursor para iterar a través de una variable de tabla. –

Respuesta

79

Añadir una identidad a la variable de tabla y hacer un bucle fácil desde 1 a la @@ ROWCOUNT del INSERT-SELECT.

Prueba esto:

DECLARE @RowsToProcess int 
DECLARE @CurrentRow  int 
DECLARE @SelectCol1  int 

DECLARE @table1 TABLE (RowID int not null primary key identity(1,1), col1 int) 
INSERT into @table1 (col1) SELECT col1 FROM table2 
SET @[email protected]@ROWCOUNT 

SET @CurrentRow=0 
WHILE @CurrentRow<@RowsToProcess 
BEGIN 
    SET @[email protected]+1 
    SELECT 
     @SelectCol1=col1 
     FROM @table1 
     WHERE [email protected] 

    --do your thing here-- 

END 
+4

Esto parece ser el más simple del lote. ¡Gracias! – Kuyenda

12
DECLARE @table1 TABLE (
    idx int identity(1,1), 
    col1 int) 

DECLARE @counter int 

SET @counter = 1 

WHILE(@counter < SELECT MAX(idx) FROM @table1) 
BEGIN 
    DECLARE @colVar INT 

    SELECT @colVar = col1 FROM @table1 WHERE idx = @counter 

    -- Do your work here 

    SET @counter = @counter + 1 
END 

Lo creas o no, esto es realmente más eficiente y eficiente que usar un cursor.

+0

¿por qué seleccionar el máximo cada vez en el ciclo? –

+0

Puede seleccionarlo una vez y almacenarlo en una variable con la suficiente facilidad ... esto fue solo unas pocas teclas más cortas. –

+1

¿por qué seleccionar el máximo cada vez en el ciclo? Como resultado, debe presionar la variable de tabla dos veces por iteración. Puede eliminar SELECT MAX() en WHILE() si captura @@ ROWCOUNT de la población de tabla vaivable, como hago en mi respuesta. –

6

Puede recorrer la variable de la tabla o puede desplazar el cursor por ella. Esto es lo que generalmente llamamos RBAR: pronunciado Reebar y significa Fila por Agonización.

Sugeriría encontrar una respuesta BASADA EN EL SET para su pregunta (podemos ayudar con eso) y alejarse de rbars tanto como sea posible.

+0

Esta es la razón por la que quiero usar una variable de tabla en lugar de un cursor. Generalmente busco una forma de obtener mi resultado deseado utilizando una variable JOIN en una tabla, pero si no puedo encontrar una manera de utilizar una JOIN, puedo recurrir a un bucle en la misma variable de la tabla. Pero estoy de acuerdo, basado en conjunto es lo mejor. – Kuyenda

+0

Looping en una variable de tabla no es mejor que un cursor. De hecho, en realidad puede ser peor. El único beneficio real de cambiar código de cursores a bucles es "derechos de fanfarronear". Ejemplo: "No tengo ningún curso en mi código". –

2

Aquí hay otra respuesta, similar a la de Justin, pero no necesita una identidad o agregado, solo una clave primaria (única).

declare @table1 table(dataKey int, dataCol1 varchar(20), dataCol2 datetime) 
declare @dataKey int 
while exists select 'x' from @table1 
begin 
    select top 1 @dataKey = dataKey 
    from @table1 
    order by /*whatever you want:*/ dataCol2 desc 

    -- do processing 

    delete from @table1 where dataKey = @dataKey 
end 
+0

cada iteración se golpea la variable de la tabla 3 veces, que no puede ser tan eficiente –

2

Aquí está mi variante. Casi como todos los demás, pero solo uso una variable para administrar el bucle.

DECLARE 
    @LoopId int 
,@MyData varchar(100) 

DECLARE @CheckThese TABLE 
(
    LoopId int not null identity(1,1) 
    ,MyData varchar(100) not null 
) 


INSERT @CheckThese (YourData) 
select MyData from MyTable 
order by DoesItMatter 

SET @LoopId = @@rowcount 

WHILE @LoopId > 0 
BEGIN 
    SELECT @MyData = MyData 
    from @CheckThese 
    where LoopId = @LoopId 

    -- Do whatever 

    SET @LoopId = @LoopId - 1 
END 

El punto de Raj More es relevante: solo realice bucles si es necesario.

2

No sabía acerca de la estructura WHILE.

La estructura WHILE con una variable de tabla, sin embargo, se ve similar a usar un CURSOR, en el sentido de que todavía tiene que SELECCIONAR la fila en una variable basada en la fila IDENTIDAD, que efectivamente es un FETCH.

¿Hay alguna diferencia entre usar WHERE y algo como lo siguiente?

DECLARE @table1 TABLE (col1 int) 
INSERT into @table1 SELECT col1 FROM table2 

DECLARE cursor1 CURSOR 
    FOR @table1 
OPEN cursor1 
FETCH NEXT FROM cursor1 

No sé si eso es posible. Supongo que debe hacer esto:

DECLARE cursor1 CURSOR 
    FOR SELECT col1 FROM @table1 
OPEN cursor1 
FETCH NEXT FROM cursor1 

Gracias por su ayuda!

+1

tu código: _DECLARE cursor1 CURSOR FOR @ table1 OPEN cursor1_ no funcionará. Los cursores deben tener un SELECTO en su definición, como su segundo ejemplo de código. Si realiza algunas pruebas, encontrará que el bucle sin usar un cursor es más rápido que el bucle con un cursor. –

1

Aquí está mi versión de la misma solución ...

declare @id int 

     SELECT @id = min(fPat.PatientID) 
     FROM tbPatients fPat 
     WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0) 

while @id is not null 
begin 
    SELECT fPat.PatientID, fPat.InsNotes 
    FROM tbPatients fPat 
    WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0) AND [email protected] 

    SELECT @id = min(fPat.PatientID) 
    FROM tbPatients fPat 
    WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0)AND fPat.PatientID>@id 

end 
5

aspecto que presentan demo:

DECLARE @vTable TABLE (IdRow int not null primary key identity(1,1),ValueRow int); 

-------Initialize--------- 
insert into @vTable select 345; 
insert into @vTable select 795; 
insert into @vTable select 565; 
--------------------------- 

DECLARE @cnt int = 1; 
DECLARE @max int = (SELECT MAX(IdRow) FROM @vTable); 

WHILE @cnt <= @max 
BEGIN 
    DECLARE @tempValueRow int = (Select ValueRow FROM @vTable WHERE IdRow = @cnt); 

    ---work demo---- 
    print '@tempValueRow:' + convert(varchar(10),@tempValueRow); 
    print '@cnt:' + convert(varchar(10),@cnt); 
    print''; 
    -------------- 

    set @cnt = @cnt+1; 
END 

Versión sin IDRow, utilizando ROW_NUMBER

DECLARE @vTable TABLE (ValueRow int); 
-------Initialize--------- 
insert into @vTable select 345; 
insert into @vTable select 795; 
insert into @vTable select 565; 
--------------------------- 

DECLARE @cnt int = 1; 
DECLARE @max int = (select count(*) from @vTable); 

WHILE @cnt <= @max 
BEGIN 
    DECLARE @tempValueRow int = (
     select ValueRow 
     from (select ValueRow 
      , ROW_NUMBER() OVER(ORDER BY (select 1)) as RowId 
      from @vTable 
     ) T1 
    where t1.RowId = @cnt 
    ); 

    ---work demo---- 
    print '@tempValueRow:' + convert(varchar(10),@tempValueRow); 
    print '@cnt:' + convert(varchar(10),@cnt); 
    print''; 
    -------------- 

    set @cnt = @cnt+1; 
END 
5

Mis dos centavos .. A partir de la respuesta de KM, si desea colocar una. variable, puede hacer una cuenta atrás en @RowsToProcess en lugar de contar.

DECLARE @RowsToProcess int; 

DECLARE @table1 TABLE (RowID int not null primary key identity(1,1), col1 int) 
INSERT into @table1 (col1) SELECT col1 FROM table2 
SET @RowsToProcess = @@ROWCOUNT 

WHILE @RowsToProcess > 0 -- Countdown 
BEGIN 
    SELECT * 
     FROM @table1 
     WHERE [email protected] 

    --do your thing here-- 

    SET @RowsToProcess = @RowsToProcess - 1; -- Countdown 
END 
+0

Esta es una solución mejor como respuesta aceptada ya que no depende del contenido de la variable de tabla. – beerwin

0

El siguiente procedimiento almacenado recorre la variable de tabla y la imprime en orden ascendente. Este ejemplo usa WHILE LOOP.

CREATE PROCEDURE PrintSequenceSeries 
    -- Add the parameters for the stored procedure here 
    @ComaSeperatedSequenceSeries nVarchar(MAX) 
AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    DECLARE @SERIES_COUNT AS INTEGER 
    SELECT @SERIES_COUNT = COUNT(*) FROM PARSE_COMMA_DELIMITED_INTEGER(@ComaSeperatedSequenceSeries, ',') --- ORDER BY ITEM DESC 

    DECLARE @CURR_COUNT AS INTEGER 
    SET @CURR_COUNT = 1 

    DECLARE @SQL AS NVARCHAR(MAX) 

    WHILE @CURR_COUNT <= @SERIES_COUNT 
    BEGIN 
     SET @SQL = 'SELECT TOP 1 T.* FROM ' + 
      '(SELECT TOP ' + CONVERT(VARCHAR(20), @CURR_COUNT) + ' * FROM PARSE_COMMA_DELIMITED_INTEGER(''' + @ComaSeperatedSequenceSeries + ''' , '','') ORDER BY ITEM ASC) AS T ' + 
      'ORDER BY T.ITEM DESC ' 
     PRINT @SQL 
     EXEC SP_EXECUTESQL @SQL 
     SET @CURR_COUNT = @CURR_COUNT + 1 
    END; 

siguiente declaración ejecuta el procedimiento almacenado:

EXEC PrintSequenceSeries '11,2,33,14,5,60,17,98,9,10' 

El resultado mostrado en la ventana de consulta SQL se muestra a continuación:

The Result of PrintSequenceSeries

La función PARSE_COMMA_DELIMITED_INTEGER() que devuelve variable de tabla es como se muestra a continuación:

CREATE FUNCTION [dbo].[parse_comma_delimited_integer] 
     (
      @LIST  VARCHAR(8000), 
      @DELIMITER VARCHAR(10) = ', 
      ' 
     ) 

     -- TABLE VARIABLE THAT WILL CONTAIN VALUES 
     RETURNS @TABLEVALUES TABLE 
     (
      ITEM INT 
     ) 
     AS 
     BEGIN 
      DECLARE @ITEM VARCHAR(255) 

      /* LOOP OVER THE COMMADELIMITED LIST */ 
      WHILE (DATALENGTH(@LIST) > 0) 
       BEGIN 
        IF CHARINDEX(@DELIMITER,@LIST) > 0 
         BEGIN 
          SELECT @ITEM = SUBSTRING(@LIST,1,(CHARINDEX(@DELIMITER, @LIST)-1)) 
          SELECT @LIST = SUBSTRING(@LIST,(CHARINDEX(@DELIMITER, @LIST) + 
          DATALENGTH(@DELIMITER)),DATALENGTH(@LIST)) 
         END 
        ELSE 
         BEGIN 
          SELECT @ITEM = @LIST 
          SELECT @LIST = NULL 
         END 

        -- INSERT EACH ITEM INTO TEMP TABLE 
        INSERT @TABLEVALUES 
        (
         ITEM 
        ) 
        SELECT ITEM = CONVERT(INT, @ITEM) 
       END 
     RETURN 
     END 
Cuestiones relacionadas