web-dev-qa-db-fra.com

Est-il possible de PIVOTER sur une instruction LIKE

Est-il possible de regrouper par éléments (comme dans COLUMN LIKE='Value%') dans une table PIVOT? J'ai une table [DBT]. [Status] qui contient différents statuts (de bases de données, instances, etc.) et je ne veux pas faire pivoter/interroger toutes les valeurs PROD et TEST en tant que valeurs uniques, mais les regrouper.

Par exemple. Au lieu d'avoir des colonnes pour les statuts Prod, Prod ACC, Prod APP, .. etc. Je n'aurais qu'une seule colonne contenant les valeurs de Name LIKE 'Prod%' et Name LIKE 'Test%'.

Ce que j'ai jusqu'à présent:

Définition de table

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

Valeurs de table

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

Le tableau de statut pivoté

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

Sortie jusqu'à présent

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

Le dbfiddle jusqu'ici.

Question

Au lieu d'avoir plusieurs lignes pour les divers Test... et Prod.... valeurs, je préférerais les regrouper, comme suit:

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

Je n'ai aucune idée de la façon de résoudre ma question. (Pour être honnête, je viens de saisir PIVOT hier après de nombreux essais et erreurs).

Cette question est vaguement liée à la question Comment créer des sommes/comptes d'éléments groupés sur plusieurs tables I ont déjà demandé. Les tables [DBT]. [Instance] et [DBT]. [Base de données] contiennent une colonne avec le [StatusID] qui correspond à la table que nous regardons maintenant.

9
John aka hot2use

SUM (CASE

Pour un nombre limité de noms, vous pouvez utiliser une solution SUM (CASE de cette manière:

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

[~ # ~] pivot [~ # ~]

S'il existe une longue liste de noms mais que seuls quelques-uns doivent être réécrits, vous pouvez maintenir la solution 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 <> violon ici

REQUÊTE DYNAMIQUE

Si vous vous sentez un peu paresseux et ne voulez pas écrire tous les noms de colonnes, vous pouvez utiliser une requête dynamique:

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 <> violon ici

11
McNets

Je pense qu'il est important de séparer strictement les deux tâches que vous essayez d'effectuer en une seule étape ici.

  1. Classification
  2. Transformation

Pour classer les données, mon instinct ici est de recommander une table de recherche pour mapper rigoureusement les enregistrements à une classe parente. par exemple.

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

... où l'enregistrement de départ dans StatusType (ID = 1 pour le Status.StatusTypeID par défaut) est un enregistrement d'espace réservé nommé "Inconnu" ou similaire.

Lorsque les données de recherche sont prédéfinies et que les enregistrements de base sont mis à jour avec les bonnes clés, vous pouvez pivoter vers le contenu de votre cœur.

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;

Full dbfiddle

7
Peter Vandivier