Est-il possible de PIVOTER sur une instruction LIKE

9

Est-il possible de regrouper par éléments (comme dans COLUMN LIKE='Value%') dans un PIVOTtableau? J'ai une table [DBT]. [Status] qui contient divers statuts (de bases de données, d'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 qu'une seule colonne contenant les valeurs 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 diverses valeurs Test... et Prod...., 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 comment résoudre ma question. (Pour être honnête, je n'ai saisi que 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 que j'ai déjà posées. Les tables [DBT]. [Instance] et [DBT]. [Database] contiennent une colonne avec le [StatusID] qui correspond à la table que nous regardons maintenant.

John aka hot2use
la source

Réponses:

11

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 que vous 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

McNets
la source
7

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 la Status.StatusTypeIDvaleur 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;

DBFiddle complet

Peter Vandivier
la source
Merci pour votre solution, c'est une assez bonne solution. Cependant, je ne suis actuellement pas en mesure de modifier les définitions de table existantes ou d'ajouter à la conception de la base de données.
John aka hot2use
1
Sélectionnez d'abord les données dans une table temporaire, de cette façon vous avez le contrôle sur les données. Déposez le tentable après l'avoir sélectionné pour l'affichage, si vous le souhaitez. Une fois votre requête terminée, vous pouvez l'intégrer dans une procédure stockée qui s'occupe automatiquement de la sélection dans le tentable et de la supprimer une fois que vous avez terminé.
khaoliang