2011-11-13 10 views
7

¿Cómo acumulo valores en T-SQL? AFAIK no hay ningún tipo de ARRAY.
Quiero reutilizar los valores en la misma consulta como se demuestra en este ejemplo de PostgreSQL usando array_agg().Servidor SQL: recopilar valores en una agregación temporalmente y volver a utilizarlos en la misma consulta

SELECT a[1] || a[i] AS foo 
     ,a[2] || a[5] AS bar -- assuming we have >= 5 rows for simplicity 
FROM (
    SELECT array_agg(text_col ORDER BY text_col) AS a 
      ,count(*)::int4 AS i 
    FROM tbl 
    WHERE id between 10 AND 100 
    ) x 

¿Cómo iba a mejor resolver esto con T-SQL?
mejor que pude llegar a son dos CTE y subselects: configuración

;WITH x AS (
    SELECT row_number() OVER (ORDER BY name) AS rn 
     ,name AS a 
    FROM #t 
    WHERE id between 10 AND 100 
), i AS (
    SELECT count(*) AS i 
    FROM x 
) 
SELECT (SELECT a FROM x WHERE rn = 1) + (SELECT a FROM x WHERE rn = i) AS foo 
     ,(SELECT a FROM x WHERE rn = 2) + (SELECT a FROM x WHERE rn = 5) AS bar 
FROM i 

prueba:

CREATE TABLE #t(
id INT PRIMARY KEY 
,name NVARCHAR(100)) 

INSERT INTO #t VALUES 
(3 , 'John') 
,(5 , 'Mary') 
,(8 , 'Michael') 
,(13, 'Steve') 
,(21, 'Jack') 
,(34, 'Pete') 
,(57, 'Ami') 
,(88, 'Bob') 

¿Hay una manera más simple?

+0

una matriz se llama una mesa, una variable de tabla, una tabla temporal o un cursor en tsql. –

+0

He agregado la cuarta solución (a mi respuesta) que muestra cómo simular ARRAY_AGG en SQL Server. –

Respuesta

12

Editar 1: He agregado otra solución que muestra cómo simular ARRAY_AGG en SQL Server (la última respuesta).

Editar 2: Para el número de solución 4) He agregado el tercer método para la concatenación.

No estoy seguro de haber entendido correctamente su pregunta.

a) En lugar de utilizar matrices en SQL Server, utilizaría las variables de tabla o XML.

b) Para concatenar cadenas (en este caso) usaría las declaraciones SELECT @var = @var + Name FROM tbl o XML xqueries.

c) La solución basada en CTE y múltiples subconsultas (WITH cte AS() FROM SELECT (SELECT * FROM cte.rn=1) +()...) generará una gran cantidad de escaneos y lecturas lógicas.

Soluciones: 1) variable de tabla + SELECT @var = @var + Name FROM tbl:

--Creating the "array" 
DECLARE @Array TABLE 
(
    Idx  INT PRIMARY KEY, 
    Val  NVARCHAR(100) NOT NULL 
); 

WITH Base 
AS 
(
    SELECT Val = t.name, 
      Idx = ROW_NUMBER() OVER(ORDER BY t.name ASC) 
    FROM #t t 
    WHERE t.id between 10 AND 100 
) 
INSERT @Array (Idx, Val) 
SELECT b.Idx, b.Val 
FROM Base b; 

--Concatenating all names 
DECLARE @AllNames NVARCHAR(4000); 
--”Reset”/Init @AllNames 
SET  @AllNames = ''; 
--String concatenation 
SELECT @AllNames = @AllNames + ',' + a.Val 
FROM @Array a; 
--Remove first char (',') 
SELECT @AllNames = STUFF(@AllNames, 1, 1, ''); 
--The final result 
SELECT @AllNames [Concatenating all names - using a table variable]; 
/* 
Concatenating all names - using a table variable 
------------------------------------------------ 
Ami,Bob,Jack,Pete,Steve 
*/ 

--Concatenating Idx=2 and Idx=5 
--”Reset” @AllNames value 
SET  @AllNames = ''; 
--String concatenation 
SELECT @AllNames = @AllNames + ',' + a.Val 
FROM @Array a 
WHERE a.Idx IN (2,5) --or a.Idx IN (2, (SELECT COUNT(*) FROM @Array)) 
ORDER BY a.Idx ASC; 
--Remove first char (',') 
SELECT @AllNames = STUFF(@AllNames, 1, 1, ''); 
--The final result 
SELECT @AllNames [Concatenating Idx=2 and Idx=5 - using a table variable]; 
/* 
Concatenating Idx=2 and Idx=5 - using a table variable 
------------------------------------------------------ 
Bob,Steve 
*/ 

2) variable de tabla + PIVOT:

--Concatenating a finite number of elements (names) 
SELECT pvt.[1] + ',' + pvt.[0] AS [PIVOT Concat_1_and_i(0)] 
     ,pvt.[2] + ',' + pvt.[5] AS [PIVOT Concat_2_and_5] 
     ,pvt.* 
FROM  
(
     SELECT a.Idx, a.Val 
     FROM @Array a 
     WHERE a.Idx IN (1,2,5) 
     UNION ALL 
     SELECT 0, a.Val --The last element has Idx=0 
     FROM @Array a 
     WHERE a.Idx = (SELECT COUNT(*) FROM @Array) 
) src 
PIVOT (MAX(src.Val) FOR src.Idx IN ([1], [2], [5], [0])) pvt; 
/* 
PIVOT Concat_1_and_i(0) PIVOT Concat_2_and_5 
----------------------- -------------------- 
Ami,Steve    Bob,Steve   
*/ 

3) XML + XQuery:

SET ANSI_WARNINGS ON; 
GO 

DECLARE @x XML; 
;WITH Base 
AS 
(
    SELECT Val = t.name, 
      Idx = ROW_NUMBER() OVER(ORDER BY t.name ASC) 
    FROM #t t 
    WHERE t.id BETWEEN 10 AND 100 
) 
SELECT @x = 
(
    SELECT b.Idx AS [@Idx] 
      ,b.Val AS [text()] 
    FROM Base b 
    FOR XML PATH('Element'), ROOT('Array') 
); 
/* @x content 
<Array> 
    <Element Idx="1">Ami</Element> 
    <Element Idx="2">Bob</Element> 
    <Element Idx="3">Jack</Element> 
    <Element Idx="4">Pete</Element> 
    <Element Idx="5">Steve</Element> 
</Array> 
*/ 

--Concatenating all names (the result is XML, so a cast is needed) 
DECLARE @r XML; --XML result 
SELECT @[email protected](' 
(: $e = array element :) 
for $e in (//Array/Element) 
    return string($e) 
'); 
SELECT REPLACE(CONVERT(NVARCHAR(4000), @r), ' ', ',') AS [Concatenating all names - using XML]; 
/* 
Concatenating all names - using XML 
----------------------------------- 
Ami,Bob,Jack,Pete,Steve 
*/ 

--Concatenating Idx=1 and all names 
SELECT @[email protected](' 
(: $e = array element :) 
for $e in (//Array/Element[@Idx=1], //Array/Element) 
    return string($e) 
'); 
SELECT REPLACE(CONVERT(NVARCHAR(4000), @r), ' ', ',') AS [Concatenating Idx=1 and all names - using XML]; 
/* 
Concatenating Idx=1 and all names - using XML 
--------------------------------------------- 
Ami,Ami,Bob,Jack,Pete,Steve 
*/ 

--Concatenating Idx=1 and i(last name) 
DECLARE @i INT; 
SELECT @[email protected](' 
(: $e = array element :) 
for $e in (//Array/Element[@Idx=1], //Array/Element[@Idx=count(//Array/Element)]) 
    return string($e) 
'); 
SELECT REPLACE(CONVERT(NVARCHAR(4000), @r), ' ', ',') AS [Concatenating Idx=1 and i(last name) - using XML]; 
/* 
Concatenating Idx=1 and i(last name) - using XML 
------------------------------------------------ 
Ami,Steve 
*/ 


--Concatenating Idx=2 and Idx=5 
SELECT @[email protected](' 
(: $e = array element :) 
for $e in (//Array/Element[@Idx=2], //Array/Element[@Idx=5]) 
    return string($e) 
'); 
SELECT REPLACE(CONVERT(NVARCHAR(4000), @r), ' ', ',') AS [Concatenating Idx=2 and Idx=5 - using XML (method 1)]; 
/* 
Concatenating Idx=2 and Idx=5 - using XML (method 1) 
---------------------------------------------------- 
Bob,Steve 
*/ 

--Concatenating Idx=2 and Idx=5 
SELECT @x.value('(//Array/Element)[@Idx=2][1]', 'NVARCHAR(100)') 
     + ',' 
     + @x.value('(//Array/Element)[@Idx=5][1]', 'NVARCHAR(100)') AS [Concatenating Idx=2 and Idx=5 - using XML (method 2)];; 
/* 
Concatenating Idx=2 and Idx=5 - using XML (method 2) 
---------------------------------------------------- 
Bob,Steve 
*/ 

4) Si la pregunta es cómo sim ulate ARRAY_AGG en SQL Server, entonces, una respuesta podría ser: mediante el uso de XML. Ejemplo:

SET ANSI_WARNINGS ON; 
GO 

DECLARE @Test TABLE 
(
    Id   INT PRIMARY KEY 
    ,GroupID INT NOT NULL 
    ,Name  NVARCHAR(100) NOT NULL 
); 

INSERT INTO @Test (Id, GroupID, Name) 
VALUES 
(3 , 1, 'John') 
,(5 , 1, 'Mary') 
,(8 , 1, 'Michael') 
,(13, 1, 'Steve') 
,(21, 1, 'Jack') 
,(34, 2, 'Pete') 
,(57, 2, 'Ami') 
,(88, 2, 'Bob'); 

WITH BaseQuery 
AS 
(
     SELECT a.GroupID, a.Name 
     FROM @Test a 
     WHERE a.Id BETWEEN 10 AND 100 
) 
SELECT x.* 
     , CONVERT(XML,x.SQLServer_Array_Agg).query 
     (' 
     for $e in (//Array/Element[@Idx=1], //Array/Element[@Idx=count(//Array/Element)]) 
      return string($e) 
     ') AS [Concat Idx=1 and Idx=i (method 1)] 
     , CONVERT(XML,x.SQLServer_Array_Agg).query(' 
      let $a := string((//Array/Element[@Idx=1])[1]) 
      let $b := string((//Array/Element[@Idx=count(//Array/Element)])[1]) 
      let $c := concat($a , "," , $b) (: " is used as a string delimiter :) 
      return $c 
     ') AS [Concat Idx=1 and Idx=i (method 2)] 
     , CONVERT(XML,x.SQLServer_Array_Agg).query 
     (' 
     for $e in (//Array/Element[@Idx=(1,count(//Array/Element))]) 
      return string($e) 
     ') AS [Concat Idx=1 and Idx=i (method 3)] 
FROM 
(
    SELECT a.GroupID 
     ,(SELECT ROW_NUMBER() OVER(ORDER BY b.Name) AS [@Idx] 
       ,b.Name AS [text()] 
     FROM BaseQuery b 
     WHERE a.GroupID = b.GroupID 
     ORDER BY b.Name 
     FOR XML PATH('Element'), ROOT('Array')) AS SQLServer_Array_Agg 
    FROM BaseQuery a 
    GROUP BY a.GroupID 
) x; 

Resultados:

GroupID SQLServer_Array_Agg                      Concat Idx=1 and Idx=i (method 1) Concat Idx=1 and Idx=i (method 2) Concat Idx=1 and Idx=i (method 3) 
------- ---------------------------------------------------------------------------------------------------------- --------------------------------- --------------------------------- --------------------------------- 
1  <Array><Element Idx="1">Jack</Element><Element Idx="2">Steve</Element></Array>        Jack Steve      Jack,Steve      Jack Steve 
2  <Array><Element Idx="1">Ami</Element><Element Idx="2">Bob</Element><Element Idx="3">Pete</Element></Array> Ami Pete       Ami,Pete       Ami Pete 
+2

¡Gran respuesta! Educativo, integral, bien formateado. Y la solución 4) responde a la pregunta formulada. Dos cosas menores 1) Para que el código sea genérico, probablemente deba usar 'NVARCHAR (max)' donde tenga 'NVARCHAR (4000)'. 2) Para que la solución 4) se ajuste mejor a la pregunta, es posible que desee incluir el elemento * last * en la salida con 'count (// Array/Element)' como se demostró anteriormente. Comenzaré una recompensa tan pronto como mi pregunta sea elegible para otorgar puntos extra por esta respuesta excepcional. –

+1

Actualicé mi respuesta. Generalmente, el rendimiento para los tipos de datos '[N] VARCHAR (max)' es menor que para los tipos de datos '[N] VARCHAR (n)' [(fuente)] (http://rusanu.com/2010/03/22/performance-comparison-of-varcharmax-vs-varcharn /). –

-1

No estoy seguro si esto ayuda, pero siempre se puede ...

select * into #MyTempTable from SomeTable 
1

Si acaba de recoger algunos valores de reutilizar, intente una variable de tabla en lugar de una tabla temporal

DECLARE @t TABLE 
(
    id INT PRIMARY KEY, 
    name NVARCHAR(100) 
) 

INSERT @t VALUES (3 , 'John') 
-- etc 

La variable de la tabla solo está en la memoria, en lugar de ir en la base de datos tempdb como lo hace una tabla temporal.

Compruebe Should I use a #temp table or table variable para obtener más información.

+0

Aunque en realidad no estoy respondiendo mi pregunta (agregar y reutilizar en la misma consulta), esto es interesante. Enlace muy informativo. –

+1

"Bajo la presión de la memoria, las páginas que pertenecen a una variable de tabla pueden enviarse a tempdb." [(Fuente)] (http://blogs.msdn.com/b/sqlserverstorageengine/archive/2008/03/30/sql -server-table-variable-vs-local-temporary-table.aspx # comments) –

+0

@BogdanSahlean Gracias, no lo sabía, es bueno saberlo. –

Cuestiones relacionadas