2009-10-16 12 views
6

Necesito realizar un pivote en una columna XML en una tabla, donde el XML contiene múltiples elementos con una serie de atributos. Los atributos en cada elemento son siempre los mismos, sin embargo, la cantidad de elementos variará. Déjeme darle un ejemplo ...Cómo pivotar sobre los atributos de una columna XML en T-SQL

FormEntryId |    FormXML         | DateCreated 
==================================================================================== 
1   |<Root>             | 10/15/2009 
      | <Form>             | 
      | <FormData FieldName="Username" FieldValue="stevem" /> | 
      | <FormData FieldName="FirstName" FieldValue="Steve" /> | 
      | <FormData FieldName="LastName" FieldValue="Mesa" /> | 
      | </Form>             | 
      |</Root>             | 
      |               | 
------------------------------------------------------------------------------------ 
2   |<Root>             | 10/16/2009 
      | <Form>             | 
      | <FormData FieldName="Username" FieldValue="bobs" /> | 
      | <FormData FieldName="FirstName" FieldValue="Bob" /> | 
      | <FormData FieldName="LastName" FieldValue="Suggs" /> | 
      | <FormData FieldName="NewField" FieldValue="test" /> | 
      | </Form>             | 
      |</Root>             | 

tengo que terminar con un conjunto de resultados para cada NombreCampo distintos valores de atributos (En este ejemplo, nombre de usuario, Nombre, Apellido, y NewField) con sus correspondientes atributos como ValorDelCampo el valor. Los resultados para el ejemplo que di anteriormente se vería así:

FormEntryId | Username | FirstName | LastName | NewField | DateCreated 
====================================================================== 
1   | stevem | Steve  | Mesa  | NULL  | 10/15/2009 
---------------------------------------------------------------------- 
2   | bobs  | Bob  | Suggs | test  | 10/16/2009 

que he descubierto una manera de lograr esto con columnas estáticas

SELECT 
    FormEntryId, 
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="Username"][1]/@FieldValue','varchar(max)') AS Username, 
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="FirstName"][1]/@FieldValue','varchar(max)') AS FirstName, 
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="LastName"][1]/@FieldValue','varchar(max)') AS LastName, 
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="NewField"][1]/@FieldValue','varchar(max)') AS NewField, 
    DateCreated 
FROM FormEntry 

Sin embargo, me gustaría ver si hay un método para tener las columnas dinámicas basadas en el conjunto distinto de valores de atributo "FieldName".

+0

+1 para obtener un bonito formato de código. –

Respuesta

4

Tenga una mirada en this dynamic pivot y más recientemente this one - que básicamente necesita para ser capaz de SELECT DISTINCT FieldName a utilizar esta técnica para crear la consulta de forma dinámica.

Aquí está la respuesta completa para su problema particular (tenga en cuenta que hay una debilidad orden de las columnas cuando se genera la lista de los atributos distintos en saber qué orden deben aparecer las columnas):

DECLARE @template AS varchar(MAX) 
SET @template = 'SELECT 
    FormEntryId 
    ,{@col_list} 
    ,DateCreated 
FROM FormEntry' 

DECLARE @col_template AS varchar(MAX) 
SET @col_template = 'FormXML.value(''/Root[1]/Form[1]/FormData[@FieldName="{FieldName}"][1]/@FieldValue'',''varchar(max)'') AS {FieldName}' 

DECLARE @col_list AS varchar(MAX) 

;WITH FieldNames AS (
    SELECT DISTINCT FieldName 
    FROM FormEntry 
    CROSS APPLY (
     SELECT X.FieldName.value('@FieldName', 'varchar(255)') 
     FROM FormXML.nodes('/Root[1]/Form[1]/FormData') AS X(FieldName) 
    ) AS Y (FieldName) 
) 
SELECT @col_list = COALESCE(@col_list + ',', '') + REPLACE(@col_template, '{FieldName}', FieldName) 
FROM FieldNames 

DECLARE @sql AS varchar(MAX) 
SET @sql = REPLACE(@template, '{@col_list}', @col_list) 

EXEC (@sql) 
+0

¡Guau, esto es muy impresionante! Lo hice funcionar para mi muestra de datos, pero me imagino que hay una serie de prevenciones y consideraciones de ataque de inyección en las que tengo que trabajar, como señaló Steve. ¡Gracias por tu ayuda! –

+0

Sí, puede usar AS QUOTENAME ({FieldName}) para SQL, pero no estoy seguro por dónde empezar a escapar de la parte XQuery: @FieldName = "{FieldName}". –

1

dinámico de pivote ISN' t incorporado en el lenguaje por una buena razón. Sería necesario escanear toda la tabla que contiene posibles nombres de columna antes de conocer la estructura del resultado. Como resultado, la estructura de la tabla de la declaración de dinámica dinámica sería desconocida antes del tiempo de ejecución. Esto crea muchos problemas con respecto al análisis e interpretación del lenguaje.

Si decide implementar un pivote dinámico por su cuenta, tenga cuidado con las oportunidades de inyección de SQL. Asegúrese de aplicar QUOTENAME o un valor equivalente a los valores que planea usar como nombres de columna en su resultado. También considere qué resultado desea si el número de valores distintos en su fuente que se convertirán en nombres de columna excede el número permitido de columnas de un conjunto de resultados.

Cuestiones relacionadas