É possível fazer PIVOT em uma instrução LIKE

9

É possível agrupar por elementos (como em COLUMN LIKE='Value%') em uma PIVOTtabela? Eu tenho uma tabela [DBT]. [Status] que contém vários status (de bancos de dados, instâncias etc.) e não deseja dinamizar / consultar todos os valores PROD e TEST como valores únicos, mas agrupá-los.

Por exemplo, em vez de ter colunas para os estados Prod, Prod ACC, Prod APP, .. etc. I teria apenas uma coluna que contém os valores para a Name LIKE 'Prod%'e Name LIKE 'Test%'.

O que tenho até agora:

Definição da tabela

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

Valores da tabela

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')

A tabela de status dinâmico

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

Saída até agora

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 <> violino

O dbfiddle até agora.

Questão

Em vez de ter várias linhas para os vários Test... e Prod....valores, eu preferiria agrupá-los, semelhante ao seguinte:

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

Não tenho idéia de como resolver minha pergunta. (Para ser sincero, só compreendi o PIVOT ontem após extensas tentativas e erros).

Esta questão está pouco relacionada à pergunta Como criar somas / contagens de itens agrupados em várias tabelas que eu já fiz. As tabelas [DBT]. [Instância] e [DBT]. [Banco de Dados] contêm uma coluna com o [StatusID] que corresponde à tabela que estamos vendo agora.

John aka hot2use
fonte

Respostas:

11

SUM (CASO

Para um número limitado de nomes, você pode usar uma SUM (solução CASE desta maneira:

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];

PIVÔ

Se houver uma lista extensa de nomes, mas apenas alguns deles devem ser reescritos, você pode manter a solução 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 <> mexer aqui

CONSULTA DINÂMICA

Se você se sentir um pouco preguiçoso e não quiser escrever todos os nomes de colunas, poderá usar uma 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 <> mexer aqui

McNets
fonte
7

Eu acho que é importante separar estritamente as duas tarefas que você está tentando executar em uma etapa aqui.

  1. Classificação
  2. Transformação

Para classificar os dados, meu instinto aqui é recomendar uma tabela de pesquisa para mapear rigorosamente os registros para uma classe pai. por exemplo

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) ;

... onde o registro inicial em StatusType( ID= 1 para o Status.StatusTypeIDpadrão) é um registro de espaço reservado chamado "Desconhecido" ou semelhante.

Quando os dados da pesquisa são semeados e os registros base são atualizados com as chaves corretas, você pode girar para o conteúdo do seu coração.

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
fonte
Obrigado por sua solução, é uma solução muito boa. No entanto, atualmente não consigo modificar as definições de tabela existentes ou adicionar ao design do banco de dados.
John aka hot2use
11
Selecione os dados em uma tabela temporária primeiro, para ter controle sobre os dados. Largue o tentador depois que você selecionou para exibição, se desejar. Depois que sua consulta for concluída, você poderá transformá-la em um procedimento armazenado que cuida automaticamente da seleção do tentador e do descarte após o término.
precisa saber é o seguinte