[Code] Nueva versión crear tabla a partir de un texto separado por comas (CSV)

En un artículo anterior, [Code] Crear una tabla a partir de un texto separado por comas (CSV), se presentó cómo realizar ésta tarea de la manera más simple, ahora, se complementará ésta solución presentando una alternativa que usa CTEs para realizar el trabajo recursivo de analizar las palabras en la sentencia

Usando la capacida de las expresiones CTEs de crear iteraciones se permite la creación de la iteración consigo mismo, es decir en la primera parte, se pone la semilla y en la segunda parte (luego de union all) se implementa la iteración, la cual devuelve las posiciones inicial y final del texto al que se le va a aplicar substring:

CREATE FUNCTION dbo.Split (@sep char(1), @s varchar(512)) 
RETURNS table 
AS 
RETURN ( 
    WITH Pieces(pn, start, stop) AS ( 
      SELECT 1, 1, CHARINDEX(@sep, @s) 
      UNION ALL 
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1) 
      FROM Pieces 
      WHERE stop > 0 
    ) 
    SELECT pn, 
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s 
    FROM Pieces 
  ) 

La solución anterior presenta una forma interesante de aproximarse al problema pero aún usa recursión, esto se puede eliminar usando XML como base de análisis

CREATE FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX)) 
 
RETURNS @t TABLE 
    ( 
        val VARCHAR(MAX) 
    )    
AS 
    BEGIN 
        DECLARE @xml XML 
        SET @XML = N'' + REPLACE(@s, @sep, '') + '' 
 
        INSERT INTO @t(val) 
        SELECT r.value('.','VARCHAR(255)') as Item 
        FROM @xml.nodes('//root/r') AS RECORDS(r) 
 
        RETURN 
    END 

El truco consiste en convertir el texto, en un xml y luego usando el tipo de datos especial (y sus funciones) leemos la información como una tabla desde XML, el problema con esta solución es que tiene problemas de desempeño y se presenta solo como referencia

También se presenta uno de mis trucos favoritos el uso de la función dm_fts_parser, la cual permite realizar éste análisis rápidamente, con la ventaja de:

  • ser una función del sistema,
  • permitir configurar el idioma
  • Ser sensible al acentro y mayúsculas
  • Poder usar una lista de palabras a ignorar
  • Más funcionalidades
select display_term from sys.dm_fts_parser('"' + 'Mi texto separado por espacios' + '"', 1033, 0,0) 

Es ejemplo está para idioma inglés con el código 1033, para un listado completo se puede consultar el listado LCID 

Espero sea de ayuda,

FREDY LEANDRO ANGARITA CASTELLANOS
Sql Server MVP

[Code] Cómo convertir un número a letras

Lo primero que se debe advertir antes de poner el código a disposición de todos, es que no recomiendo esta aproximación de manera práctica dado que SQL está optimizado para cálculos basados en conjuntos (Selects, Joins y demás); manejo de cadenas y cálculos se deberían hacer de otras maneras (CLR, Código Ciente, etc.), pero si alguna vez se necesita o si se quiere traducir a otro lenguaje se puede usar el siguiente código para generar números como letras. Para tener más claridad sobre el tema de buenas prácticas se puede revisar el artículo de optimización de consultas

No es el código más eficiente que se puede escribir para el tema, pero funciona:  

create PROCEDURE [dbo].[sp_Dinero_a_Texto]
(
  @Numero NUMERIC(19,4)
 )
AS
BEGIN
 
  SET NOCOUNT ON
  DECLARE @lnEntero INT,
    @lcRetorno VARCHAR(512),
    @lnTerna INT,
    @lcMiles VARCHAR(512),
    @lcCadena VARCHAR(512),
    @lnUnidades INT,
    @lnDecenas INT,
    @lnCentenas INT,
    @lnFraccion INT
 
  SELECT @lnEntero = CAST(@Numero AS INT),
    @lnFraccion = (@Numero - @lnEntero) * 100,
    @lcRetorno = '',
    @lnTerna = 1
 
  WHILE @lnEntero > 0
  BEGIN /* WHILE */
 
    -- Recorro columna por columna
    SELECT @lcCadena = ''
    SELECT @lnUnidades = @lnEntero % 10
    SELECT @lnEntero = CAST(@lnEntero/10 AS INT)
    SELECT @lnDecenas = @lnEntero % 10
    SELECT @lnEntero = CAST(@lnEntero/10 AS INT)
    SELECT @lnCentenas = @lnEntero % 10
    SELECT @lnEntero = CAST(@lnEntero/10 AS INT)	

	--print cast(@lnCentenas as varchar(3)) + cast(@lnDecenas as varchar(3))+ cast(@lnUnidades as varchar(3))
	--print @lnEntero
 
    -- Analizo las unidades
    SELECT @lcCadena =
    CASE /* UNIDADES */
      WHEN @lnUnidades = 1 AND @lnTerna = 1 THEN 'UNO ' + @lcCadena
      WHEN @lnUnidades = 1 AND @lnTerna <> 1 THEN 'UN ' + @lcCadena
      WHEN @lnUnidades = 2 THEN 'DOS ' + @lcCadena
      WHEN @lnUnidades = 3 THEN 'TRES ' + @lcCadena
      WHEN @lnUnidades = 4 THEN 'CUATRO ' + @lcCadena
      WHEN @lnUnidades = 5 THEN 'CINCO ' + @lcCadena
      WHEN @lnUnidades = 6 THEN 'SEIS ' + @lcCadena
      WHEN @lnUnidades = 7 THEN 'SIETE ' + @lcCadena
      WHEN @lnUnidades = 8 THEN 'OCHO ' + @lcCadena
      WHEN @lnUnidades = 9 THEN 'NUEVE ' + @lcCadena
      ELSE @lcCadena
    END /* UNIDADES */

	print @lcCadena
 
    -- Analizo las decenas
    SELECT @lcCadena =
    CASE /* DECENAS */
      WHEN @lnDecenas = 1 THEN
        CASE @lnUnidades
          WHEN 0 THEN 'DIEZ '
          WHEN 1 THEN 'ONCE '
          WHEN 2 THEN 'DOCE '
          WHEN 3 THEN 'TRECE '
          WHEN 4 THEN 'CATORCE '
          WHEN 5 THEN 'QUINCE '
          ELSE 'DIECI' + @lcCadena
        END
      WHEN @lnDecenas = 2 AND @lnUnidades = 0 THEN 'VEINTE ' + @lcCadena
      WHEN @lnDecenas = 2 AND @lnUnidades <> 0 THEN 'VEINTI' + @lcCadena
      WHEN @lnDecenas = 3 AND @lnUnidades = 0 THEN 'TREINTA ' + @lcCadena
      WHEN @lnDecenas = 3 AND @lnUnidades <> 0 THEN 'TREINTA Y ' + @lcCadena
      WHEN @lnDecenas = 4 AND @lnUnidades = 0 THEN 'CUARENTA ' + @lcCadena
      WHEN @lnDecenas = 4 AND @lnUnidades <> 0 THEN 'CUARENTA Y ' + @lcCadena
      WHEN @lnDecenas = 5 AND @lnUnidades = 0 THEN 'CINCUENTA ' + @lcCadena
      WHEN @lnDecenas = 5 AND @lnUnidades <> 0 THEN 'CINCUENTA Y ' + @lcCadena
      WHEN @lnDecenas = 6 AND @lnUnidades = 0 THEN 'SESENTA ' + @lcCadena
      WHEN @lnDecenas = 6 AND @lnUnidades <> 0 THEN 'SESENTA Y ' + @lcCadena
      WHEN @lnDecenas = 7 AND @lnUnidades = 0 THEN 'SETENTA ' + @lcCadena
      WHEN @lnDecenas = 7 AND @lnUnidades <> 0 THEN 'SETENTA Y ' + @lcCadena
      WHEN @lnDecenas = 8 AND @lnUnidades = 0 THEN 'OCHENTA ' + @lcCadena
      WHEN @lnDecenas = 8 AND @lnUnidades <> 0 THEN 'OCHENTA Y ' + @lcCadena
      WHEN @lnDecenas = 9 AND @lnUnidades = 0 THEN 'NOVENTA ' + @lcCadena
      WHEN @lnDecenas = 9 AND @lnUnidades <> 0 THEN 'NOVENTA Y ' + @lcCadena
      ELSE @lcCadena
    END /* DECENAS */

	print @lcCadena
 
    -- Analizo las centenas
    SELECT @lcCadena =
    CASE /* CENTENAS */
      WHEN @lnCentenas = 1 AND @lnUnidades = 0 AND @lnDecenas = 0 THEN 'CIEN ' + @lcCadena
      WHEN @lnCentenas = 1 AND NOT(@lnUnidades = 0 AND @lnDecenas = 0) THEN 'CIENTO ' + @lcCadena
      WHEN @lnCentenas = 2 THEN 'DOSCIENTOS ' + @lcCadena
      WHEN @lnCentenas = 3 THEN 'TRESCIENTOS ' + @lcCadena
      WHEN @lnCentenas = 4 THEN 'CUATROCIENTOS ' + @lcCadena
      WHEN @lnCentenas = 5 THEN 'QUINIENTOS ' + @lcCadena
      WHEN @lnCentenas = 6 THEN 'SEISCIENTOS ' + @lcCadena
      WHEN @lnCentenas = 7 THEN 'SETECIENTOS ' + @lcCadena
      WHEN @lnCentenas = 8 THEN 'OCHOCIENTOS ' + @lcCadena
      WHEN @lnCentenas = 9 THEN 'NOVECIENTOS ' + @lcCadena
      ELSE @lcCadena
    END /* CENTENAS */
	print @lcCadena
 
    -- Analizo los millares
    SELECT @lcCadena =
    CASE /* TERNA */
      WHEN @lnTerna = 1 THEN @lcCadena
      WHEN @lnTerna = 2 AND (@lnUnidades + @lnDecenas + @lnCentenas <> 0) THEN @lcCadena + ' MIL '
      WHEN @lnTerna = 3 AND (@lnUnidades + @lnDecenas + @lnCentenas <> 0) AND
        @lnUnidades = 1 AND @lnDecenas = 0 AND @lnCentenas = 0 THEN @lcCadena + ' MILLON '
      WHEN @lnTerna = 3 AND (@lnUnidades + @lnDecenas + @lnCentenas <> 0) AND
        NOT (@lnUnidades = 1 AND @lnDecenas = 0 AND @lnCentenas = 0) THEN @lcCadena + ' MILLONES '
      WHEN @lnTerna = 4 AND (@lnUnidades + @lnDecenas + @lnCentenas <> 0) THEN @lcCadena + ' MIL MILLONES '
      ELSE ''
    END /* MILLARES */
	print @lcCadena
 
    -- Armo el retorno columna a columna
	--print @lcCadena
    SELECT @lcRetorno = @lcCadena + @lcRetorno
    SELECT @lnTerna = @lnTerna + 1
 
  END /* WHILE */
 
  IF @lnTerna = 1
    SELECT @lcRetorno = 'CERO'
 
  SELECT RTRIM(@lcRetorno) + CASE WHEN LEN(LTRIM(STR(@lnFraccion,2))) = 1 THEN '0'+LTRIM(STR(@lnFraccion,2))
                                               ELSE LTRIM(STR(@lnFraccion,2)) END AS Texto
 
END

 

Los comentarios son bienvenidos, espero sea de ayuda,

FREDY LEANDRO ANGARITA CASTELLANOS
Sql Server MVP

[Code] Llenado de Espacio en Datos (Generación de Datos Acumulados) por Grupos

Llenado de Espacio en Datos (Generación de Datos Acumulados) por Grupos

Normalmente los productos de software llevan un registro de los hechos que se Fsentan en el negocio, ventas, desembolsos, cobros, intereses, etc. Pero por supuesto no todos los días se vende, se desembolsa, se cobra initereses, cómo podemos resolver de una eficiente la necesidad ver los datos acumulados, este escenario, por lo meno de manera parcial, fué resuelto en un artículo anterior [Code] Cómo calcular un total acumulado Rápido SIN Cursores. Este artículo complementa esa solución presentando cómo se haría lo mismo para generar datos acumulados (o llenar espacios) teniendo en cuenta diferentes grupos dentro de los datos

También se presentará una aplicación de otro artículo llamado [Code] Cómo generar N Filas Rápido Sql Server el cual se usará para generar los datos necesarios para llenar los espacios de la información.

Datos de ejemplo

declare @hechos table (id int, fecha date, producto int, venta decimal)

insert into @hechos	values (1, '2011-09-01', 1, 10)
insert into @hechos	values (2, '2011-09-03', 1, 20)
insert into @hechos	values (3, '2011-09-04', 1, 2)
insert into @hechos	values (4, '2011-09-08', 1, 1)
insert into @hechos	values (5, '2011-09-10', 1, 3)
insert into @hechos	values (6, '2011-09-12', 1, 4)

insert into @hechos	values (7, '2011-09-01', 2, 3)
insert into @hechos	values (8, '2011-09-03', 2, 2)
insert into @hechos	values (9, '2011-09-04', 2, 1)
insert into @hechos	values (10, '2011-09-08', 2, 5)
insert into @hechos	values (11, '2011-09-10', 2, 6)
insert into @hechos	values (12, '2011-09-12', 2, 7)

Los datos en formato tabla:

Id Fecha Producto Venta
1 2011-09-01 1 10
2 2011-09-03 1 20
3 2011-09-04 1 2
4 2011-09-08 1 1
5 2011-09-10 1 3
6 2011-09-12 1 4
7 2011-09-01 2 3
8 2011-09-03 2 2
9 2011-09-04 2 1
10 2011-09-08 2 5
11 2011-09-10 2 6
12 2011-09-12 2 7

Observemos que las fechas de venta para cada producto no suceden en días consecutivos, se tienen 2 diferentes productos a los que se quiere calcularle sus ventas acumuladas por día y una cantidad de ventas

Lo primero que haremos es calcular el número de días de diferencia entre la mayor y menos fecha de toda la tabla (para todos los productos)

declare @FechaMin date, @FechaMax Date, @Diferencia int
select  @FechaMin = min(Fecha), @FechaMax = MAX(Fecha) from @hechos

set @Diferencia = DATEDIFF(dd,@FechaMin, @FechaMax)

Alternativamente podríamos crear una tabla que tenga la fecha mínima y máxima por cada producto, pero se presentará así para mantener simple el ejemplo.

Pasos:

  • Se generea un CTE con una tabla que tiene todas las fechas posibles (incluso las que no están en la tabla @hechos), llamado Fechas, El código de GenTable se encuentra en [Code] Cómo generar N Filas Rápido Sql Server
  • También se declara una tabla @Acumulado que tendrá el resultado final con los valores acumulados por producto
  • En el CTE ProductoFecha aplicamos una función de conjunto cross join para aplicar cada fecha generada a cada una de los productos de @hechos, lo que nos dá como resultado todas las fechas posibles para cada producto
  • Luego, se inserta en @Acumulado el resutaldo de hacer left join entre ProductoFecha (que tiene todas las fechas) y @Hechos lo que nos da, cada producto y cada fecha posible, en las fechas que no se realizaron ventas se agrega cero (0)
declare @Acumulado table (fecha date, producto int, venta decimal, VentaAcumulada decimal)

;With Fechas (Fecha) as
(
select DATEADD(dd,GT.i,@FechaMin) Fecha from dbo.GenTable(0,@Diferencia,1) GT
)
, ProductoFecha (Producto, Fecha) as (
select distinct Producto, Fechas.Fecha from @hechos cross join Fechas
)
insert into @Acumulado (fecha, producto, venta, VentaAcumulada)
Select PF.Fecha, PF.Producto, isnull(H.Venta,0) as Ventas, 0  from ProductoFecha PF left join @hechos H ON PF.Producto = H.producto and PF.Fecha = h.fecha

Ahora se procede a calcular el total acumulado por grupos:

declare @runningtotal decimal , @ProductoActual int

set @runningtotal = 0 
select @ProductoActual = 0 

--Anterior
update @Acumulado 
set 
@runningtotal = VentaAcumulada = case when not @ProductoActual = Producto then venta else 
case when Venta = 0 then @runningtotal else venta end
end
,@ProductoActual = Producto

La parte clave es donde se define el Producto actual por que permite la detección de cambio de grupos para generar así su acumulado, esta técnica puede ser aplicada a múltiples grupos, por ejemplo, ciudad – producto solo agregando otro grupo (para el caso del ejemplo sería @ProductoActual) en el orden que se necesite y así generar todos los grupos necesarios

Los resultados:


fecha producto venta VentaAcumulada
2011-09-01 1 10 10
2011-09-02 1 0 10
2011-09-03 1 20 20
2011-09-04 1 2 2
2011-09-05 1 0 2
2011-09-06 1 0 2
2011-09-07 1 0 2
2011-09-08 1 1 1
2011-09-09 1 0 1
2011-09-10 1 3 3
2011-09-11 1 0 3
2011-09-01 2 3 3
2011-09-02 2 0 3
2011-09-03 2 2 2
2011-09-04 2 1 1
2011-09-05 2 0 1
2011-09-06 2 0 1
2011-09-07 2 0 1
2011-09-08 2 5 5
2011-09-09 2 0 5
2011-09-10 2 6 6
2011-09-11 2 0 6

Para que ésta solución funcione, los datos deben estar previamente ordenados (por producto y fecha)

Espero sea de ayuda,

FREDY LEANDRO ANGARITA CASTELLANOS
Sql Server MVP

[Code] Conteo de Filas de cada una de las tablas de la base de datos

Conteo de Filas de cada tabla

Haciendo diagnóstico de las bases de datos, realizando seguimientos al crecimiento del número de registros y en fin para conocer el número de registros en todas las tablas existen varias técnicas pero existen unas mejores que otras y a contiuación se presentarán:

Una de las aproximaciones es usar uno de los procedimientos almacenados más prácticas que tiene TSql sp_MSforeachtable la cual nos permite ejecutar algún tipo de instrucción para cada una de las tablas de la base de datos, incluso admite filtros

exec sp_MSforeachtable 'select ''?'' TableName, count(*) Cnt from ?';

El asunto con esta solución es que se genera un resultset para cada una de las tablas (es decir, no es un solo resultado, sino uno por cada tabla) lo cual para cosas como un reporte son complicadas de manejar.

Entonces la siguiente aproximación sería desarrollar un procedimiento almacenado que genere la consulta y luego la una y me presente un solo resultado, algo como:

declare @tempTable table
(
    TableSchema nvarchar(256),
    TableName nvarchar(256),
    Cnt bigint
);

declare @sql nvarchar(4000);
declare @tableSchema nvarchar(256);
declare @tableName nvarchar(256);
declare @columnName sysname;
declare @cnt bigint;

declare tableCursor cursor for
    select TABLE_SCHEMA, TABLE_NAME from INFORMATION_SCHEMA.TABLES
    where TABLE_TYPE = 'BASE TABLE';

open tableCursor;

fetch next from tableCursor into @tableSchema, @tableName;

while @@FETCH_STATUS = 0
begin
    set @sql = 'select @cnt = COUNT(*) from [' + @tableSchema + '].[' + @tableName + ']';

    exec sp_executesql @sql, N'@cnt bigint output', @cnt = @cnt output;

    insert into @tempTable select @tableSchema, @tableName, @cnt;

    fetch next from tableCursor into @tableSchema, @tableName;
end;

close tableCursor;
deallocate tableCursor;

select * from @tempTable;

Por supuesto esta solución tiene varios inconvenientes, para empezar usa un cursor para leer el listado de tablas de la consulta estandar INFORMATION_SCHEMA, segundo usa un sql dinámico para armar poco a poco la consulta que se desea ejecutar y  algo que no se ve a simple vista y es la necesidad de ejecutar COUNT para cada una de las tablas, por lo cual ésta no es la solución adecuada tampoco

Ahora veremos cómo podemos encontrar el conteo y de paso saber cual es la tabla que cuenta con mayor cantidad de registros

Sql 2000 Sql 2005+
SELECT OBJECT_NAME(id),rowcnt
FROM SYSINDEXES
WHERE OBJECTPROPERTY(id,’isUserTable’)=1 AND indid < 2
ORDER BY rowcnt DESC
select sc.name +'.'+ ta.name
,sum(pa.rows) -- Valor aproximado
from sys.tables ta
inner join sys.partitions pa
on pa.object_id = ta.object_id
inner join sys.schemas sc
on ta.schema_id = sc.schema_id
where ta.is_ms_shipped = 0	AND pa.index_id IN (1,0)
group by sc.name,ta.name
ORDER BY sum(pa.rows) DESC

Sql Server crea estadísticas para cada tabla lo que le permite optimizar las consultas que ejecutamos por lo que podemos usar estas estadísticas para obtener éste dato, también, analizar la consulta, observamos que se tiene en cuenta si la tabla está particionada y asimismo se consideran los esquemas para que la información quede completa

 

Espero sea de ayuda,

FREDY LEANDRO ANGARITA CASTELLANOS
Sql Server MVP

[Code] Pivote dinámico con sql server

Pivotes Dinámicos

En muchas ocaciones tenemos información en filas y queremos convertirlas en columnas, ésto se presenta especialmente en escenarios de análisis en donde tener la información en colunas es un requerimiento.

Revisemos el ejemplo que presenta MSDN en su documentación

Si tenemos una consulta:

USE AdventureWorks2008R2 ;
GO
SELECT DaysToManufacture, AVG(StandardCost) AS AverageCost 
FROM Production.Product
GROUP BY DaysToManufacture;
 

La cual arroja los siguiente resultados

DaysToManufacture AverageCost
0 5.0885
1 223.88
2 359.1082
4 949.4105

y queremos verla en columnas

Cost_Sorted_By_Production_Days 0 1 2 3 4
AverageCost 5.0885 223.88 359.1082 NULL 949.4105

Debemos usar PIVOT

 

SELECT 'AverageCost' AS
Cost_Sorted_By_Production_Days, [0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost FROM Production.Product) AS SourceTable
PIVOT (AVG(StandardCost) FOR DaysToManufacture IN ([0], [1], [2], [3], [4]) ) AS PivotTable;

Revisando la sintaxis de la consulta tenemos que debemos definir una a una las columnas que deseamos obtener con la consulta y que éstas correspondan al Valor que queremos poner en esa columna, es decir, para cero (0) días de manufactura debemos crear una columan con el nombre 0 para que se asocie el valor que se tiene para esa columna, y se observa también que se debe incluir una función de agregación para crear un contexto para cada celda en el momento de poner el valor en la columna, esto es clave para poder generar el pivote.

Si se ha trabajado anteriormente con tablas dinámicas se verá que el comportamiento es similar tan solo debemos escribir nosotros mismos la consulta

EN SQL 2000

Acá tenemos una idea de cómo se podría solucionar en Sql 2000, algunas de las funciones que se presentan en ésta solución pronto será descontinuadas en las futuras versiones de SQL

use AdventureWorks2008R2;
go

SELECT  
  [No. Sales per year]=CASE WHEN row IS NULL THEN 'Sum'  
                   ELSE CONVERT(VARCHAR(80),[row]) END , 
  [1996] =SUM( CASE col WHEN '2005' THEN data ELSE 0 END ), 
  [1997] =SUM( CASE col WHEN '2006' THEN data ELSE 0 END ), 
  [1998] =SUM( CASE col WHEN '2007' THEN data ELSE 0 END ), 
  [Total]= SUM( data ) 
FROM  
   (SELECT [row]=P.firstname+' '+ P.lastname,  
           [col]=YEAR(SOH.OrderDate),  
           [data]=COUNT(*) 
    FROM Sales.SalesPerson SP INNER JOIN Sales.SalesOrderHeader SOH ON (SP.BusinessEntityID=SOH.SalesPersonID)  
    inner join HumanResources.Employee E ON SP.BusinessEntityID = E.BusinessEntityID 
    inner join Person.Person P ON E.BusinessEntityID = P.BusinessEntityID 
    GROUP BY P.firstname+' '+ P.lastname, YEAR(SOH.OrderDate) 
    )f 
GROUP BY row WITH ROLLUP 
ORDER BY GROUPING(row),total DESC

 

IDEAS

Siempre que aprendemos algo se nos ocurren mas ideas, tales como: Qué pasa si se tienen días de manufactura diferentes? qué pasa si quiero una columna para cada uno de esos días, que pasa si lo hacemos por producto? por pais? qué pasa si cada uno de ellos tiene un comportamiento diferente?

Muchas de esas preguntas se responden realizando un pivote con columnas dinámicas, es decir, al contrario de la sintaxis normal en donde debemos definir cada columna, que se interprete y de obtengan todas las columnas que necesito sin necesidad de definirlas una a una

CONSULTA DINÁMICA

 

use AdventureWorks2008R2;
go

declare @columnas varchar(max)

set @columnas = ''

select @columnas =  coalesce(@columnas + '[' + cast(DaysToManufacture as varchar(12)) + '],', '')
FROM (select distinct DaysToManufacture from Production.Product) as DTM

set @columnas = left(@columnas,LEN(@columnas)-1)

DECLARE @SQLString nvarchar(500);

set @SQLString = N'
SELECT *
FROM
(SELECT DaysToManufacture, StandardCost 
    FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN (' + @columnas + ')
) AS PivotTable;'

EXECUTE sp_executesql @SQLString

Pero esta solución tiene el inconveniente de tener que lidiar con consultas Ad-Hoc (Dinámicas) que asocian posibles problemas de seguridad y rendimiento a la solución, esta solución así como la demás que se puedan implementar debe considerar dónde se van a usar, posiblemente sea mas eficiente realizar el pivote en reporting services (Matriz) que en Sql, esto debe evaluarse para cada y depende de lo que se quiera realizar

OPCION CTE – Nombres de columna dinámicos

Para 2005 o superior existe una alternativa para facilitar, mas no soluciona del todo el problema y es crear una consulta que asigna valores (números a las columnas) facilitando la construcción final de la consulta. En este caso queremos pivotear por el nombre del mes, pero supongamos que el rango que damos en la primera consulta cambia, entonces los nombre de las columnas (Nombre del mes) no podrían ser usados.

Por ejemplo si enviamos como parámetro el primero de agosto,  hasta el 31 de diciembre o si enviamos el 1 de enero al 30 de junio, tendríamos que cambiar constantemente la consulta o presentar todos los meses, para solucionar esto tenemos el siguiente ejemplo

use AdventureWorksDW2008R2;
go

;With monthyears as (
SELECT DISTINCT SpanishMonthName as CalendarMonthYearName, MonthNumberOfYear as MonthOfYear,CalendarYear
      FROM dbo.DimDate 
      WHERE DateKey > @FechaInicio AND DateKey < @FechaFinal
),
CalMonthYears AS
(
SELECT CalendarMonthYearName AS 'CalendarMonthYearName',
      MonthOfYear,
      CalendarYear,
      ROW_NUMBER() OVER (ORDER BY CalendarYear DESC) AS 'YearMonthNum'  
      FROM monthyears
)
SELECT ProductKey,
      [1],[2],[3],[4],[5],[6]
FROM
(
-- your query to get your data, pivot this data
SELECT YearMonthNum,s.ProductKey,
      SUM(OrderQuantity) AS 'Quantity'
      FROM FactResellerSales s
      INNER JOIN dbo.DimDate d ON s.OrderDateKey = d.DateKey
      INNER JOIN CalMonthYears cmy ON d.SpanishMonthName = cmy.CalendarMonthYearName
GROUP BY YearMonthNum,s.ProductKey
) AS SourceTable
PIVOT
(
SUM(Quantity)
FOR YearMonthNum IN (
      [1],[2],[3],[4],[5],[6]
      )
) AS PivotTable

 

Espero sea de ayuda, 

FREDY LEANDRO ANGARITA CASTELLANOS
Sql Server MVP