¿Es posible PIVOTAR en una declaración LIKE?

9

¿Es posible agrupar por elementos (como en COLUMN LIKE='Value%') en una PIVOTtabla? Tengo una tabla [DBT]. [Estado] que contiene varios estados (de bases de datos, instancias, etc.) y no quiero pivotar / consultar todos los valores PROD y TEST como valores únicos, sino agruparlos.

Por ejemplo, lugar de tener columnas para los estados Prod, Prod ACC, Prod APP, .. etc que tendría sólo una columna que contiene los valores de Name LIKE 'Prod%'y Name LIKE 'Test%'.

Lo que tengo hasta ahora:

Definición de tabla

CREATE TABLE [DBT].[Status](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Status] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY],
 CONSTRAINT [IX_Status] UNIQUE NONCLUSTERED 
(
    [Name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]

GO

Tabla de valores

INSERT INTO [DBT].[Status]
(
    -- ID -- this column value is auto-generated
    Name
)
VALUES
('Test ACC'),
('Test APP'),
('Test DBA'),
('Prod ACC'),
('Prod APP'),
('Prod DBA'),
('Prod'),
('Test'),
('Migrated'),
('Offline'),
('Reserved')

La tabla de estado pivotada

SELECT 'Database Status' AS [DB Status], 
[1] AS [Test ACC], [2] AS [Test APP], [3] AS [Test DBA], [4] AS [Prod ACC], [5] AS [Prod APP], [6] AS [Prod DBA], [7] AS [Prod], [8] AS [Test], [9] AS [Migrated], [10] AS [Offline], [11] AS [Reserved] 
FROM 
(
    SELECT ID, Name  FROM [DBT].[Status]
) AS Source
PIVOT
(
    COUNT(Name) FOR ID IN ([1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11])
) AS PivotTable

Salida hasta ahora

DB Status       Test ACC    Test APP    Test DBA    Prod ACC    Prod APP    Prod DBA    Prod        Test        Migrated    Offline     Reserved
--------------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
Database Status 1           1           1           1           1           1           1           1           1           1           1

db <> violín

El dbfiddle hasta ahora.

Pregunta

En lugar de tener varias filas para los diversos Test... y Prod....valores, preferiría agruparlos, de forma similar a lo siguiente:

DB Status       | Test | Prod | Migrated | Offline | Reserved   
--------------- | ---- | ---- | -------- | ------- | --------
Database Status |    4 |    4 |        1 |       1 |        1

No tengo idea de cómo resolver mi pregunta. (Para ser honesto, acabo de comprender PIVOT ayer después de extensas pruebas y errores).

Esta pregunta está poco relacionada con la pregunta Cómo crear sumas / recuentos de elementos agrupados en varias tablas que ya he preguntado. Las tablas [DBT]. [Instancia] y [DBT]. [Base de datos] contienen una columna con el [StatusID] que corresponde a la tabla que estamos viendo ahora.

John aka hot2use
fuente

Respuestas:

11

SUMA (CASO

Para un número limitado de nombres, puede usar una solución SUMA (CASO de esta manera:

SELECT 
    'Database status' as [DB Status],
    SUM(CASE WHEN Name LIKE 'Test%' THEN 1 ELSE 0 END) As Test,
    SUM(CASE WHEN Name LIKE 'Prod%' THEN 1 ELSE 0 END) AS Prod,
    SUM(CASE WHEN Name = 'Migrated' THEN 1 ELSE 0 END) AS Migrated,
    SUM(CASE WHEN Name = 'Offline' THEN 1 ELSE 0 END) AS Offline,
    SUM(CASE WHEN Name = 'Reserved' THEN 1 ELSE 0 END) AS Reserved
FROM 
    [Status];

PIVOTE

Si hay una lista extensa de nombres pero solo algunos de ellos deben reescribirse, puede mantener la solución PIVOT:

SELECT 'Database Status' AS [DB Status],
[Test], [Prod], [Migrated], [Offline], [Reserved]
FROM
(
    SELECT 
        ID, 
        CASE
            WHEN Name LIKE 'Test%' THEN 'Test'
            WHEN Name LIKE 'Prod%' THEN 'Prod'
            ELSE Name
        END AS Name
    FROM 
        [Status]
) AS Source
PIVOT
(
    COUNT(ID) FOR Name IN ([Test], [Prod], [Migrated], [Offline], [Reserved])
) AS PivotTable;

db <> violín aquí

CONSULTA DINÁMICA

Si se siente un poco vago y no quiere escribir todos los nombres de columna, puede usar una consulta dinámica:

DECLARE @cols nvarchar(max);

SET @cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(CASE WHEN Name LIKE 'Test%' THEN 'Test'
                                                    WHEN Name LIKE 'Prod%' THEN 'Prod'
                                                    ELSE Name END)
                   FROM [Status]
                   FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '');

DECLARE @cmd nvarchar(max);

SET @cmd = 
'SELECT ''Database Status'' AS [DB Status],' + @cols + ' FROM
    (SELECT 
        ID, 
        CASE
            WHEN Name LIKE ''Test%'' THEN ''Test''
            WHEN Name LIKE ''Prod%'' THEN ''Prod''
            ELSE Name
        END AS Name
    FROM 
        [Status]
) AS Source
PIVOT
(
    COUNT(ID) FOR Name IN (' + @cols + ')
) PVT'

EXEC(@cmd);

db <> violín aquí

McNets
fuente
7

Creo que es importante separar estrictamente las dos tareas que está intentando realizar en un solo paso aquí.

  1. Clasificación
  2. Transformación

Para clasificar los datos, mi instinto aquí es recomendar una tabla de búsqueda para asignar rigurosamente los registros a una clase principal. p.ej

CREATE TABLE StatusType (
  ID     INT         IDENTITY PRIMARY KEY,
  [Name] VARCHAR(10) NOT NULL UNIQUE
);
GO
ALTER TABLE [Status] 
  ADD StatusTypeID INT NOT NULL 
    DEFAULT 1
    FOREIGN KEY REFERENCES StatusType (ID) ;

... donde el registro semilla en StatusType( ID= 1 para el Status.StatusTypeIDvalor predeterminado) es un registro de marcador de posición llamado "Desconocido" o similar.

Cuando los datos de búsqueda se siembran y los registros base se actualizan con las claves correctas, puede pasar al contenido de su corazón.

select 'Database Status' AS [DB Status],
    [Test], [Prod], [Migrated], [Offline], [Reserved]
from (
    select s.ID,
           st.Name as StatusTypeName
    from status s
    join statusType st on st.ID = s.StatusTypeID
) as Source
pivot (
    count(ID) for StatusTypeName in ([Test],[Prod],[Migrated],[Offline],[Reserved],[Unknown])
) as pvt;

Dbfiddle completo

Peter Vandivier
fuente
Gracias por su solución, es una solución bastante buena. Sin embargo, actualmente no puedo modificar las definiciones de tabla existentes o agregarlas al diseño de la base de datos.
John aka hot2use
1
Seleccione los datos en una tabla temporal primero, de esa manera tendrá control sobre los datos. Suelta el tentador después de haberlo seleccionado para mostrarlo, si quieres. Una vez que finalice su consulta, puede incluirla en un procedimiento almacenado que automáticamente se encarga de seleccionar en el tentador y soltarlo una vez que haya terminado.
khaoliang