Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to "catch all other values" FOR a column in a SQL PIVOT?

I have a SQL query where I’m calculating the number of players for a game based on UserType (only 1 counts as an actual player for the purposes of my calculation), and listing this info out for all games on a certain date:

SELECT
    gm.[Id]
    ,SUM(CASE WHEN mbr.[UserType] = 1 THEN 1 ELSE 0 END) AS NbrPlayers
FROM
    dbo.[Game] gm
    LEFT JOIN dbo.[GameMembership] mbr ON gm.[Id] = mbr.[GameId]
WHERE
    CAST(gm.[DateCreated] AS DATE) = CAST('2023-04-30' AS DATE)
GROUP BY
    gm.[Id]

This gives the result, for my sample data:

Id                                      NbrPlayers
==================================================
FFA8C1D6-CEB6-4823-B436-18F0417EEBD0    2
E14B2558-F8B4-4660-967C-2EBC452BE881    0
208B46F8-B39D-4BC8-A907-57A07CD0A380    1
76275D64-AFD9-425C-8BD5-9FA770C24AA5    1
EA092967-680B-4EA6-A137-CD9834C51323    2
6C346BA1-1D3D-410A-A1A3-CF0B2F840C0E    1
EC68A93E-996F-4D4E-B443-DE63BD9B9F59    1
06C9A62D-9790-4080-8863-F7F2D0432F67    1
428D6C3F-57AF-4F6C-AE7F-F8C5D10AA6A5    2
DF027EE0-FAA3-4DA2-844A-F94AB6D56C2C    1
C82E60DF-7193-4099-B273-FE497B3002E5    1

However, what I then want to do is turn this into a single row of output, with columns for each distinct NbrPlayers value. As I know that I have 0, 1, and 2 in this output, I can do this with PIVOT like so:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

SELECT
    [0] AS [0pGames], [1] AS [1pGames], [2] AS [2pGames]
FROM (
    SELECT
        gm.[Id]
        ,SUM(CASE WHEN mbr.[UserType] = 1 THEN 1 ELSE 0 END) AS NbrPlayers
    FROM
        dbo.[Game] gm
        LEFT JOIN dbo.[GameMembership] mbr ON gm.[Id] = mbr.[GameId]
    WHERE
        CAST(gm.[DateCreated] AS DATE) = CAST('2023-04-30' AS DATE)
    GROUP BY
        gm.[Id]
) x
PIVOT (
    COUNT([Id])
    FOR [NbrPlayers] IN ([0], [1], [2])
) pvt

Which results in:

0pGames    1pGames    2pGames
=============================
1          7          3

This problem is, what happens when I get other NbrPlayers values, and I don’t necessarily know in advance what they might be? What’s more, I actually don’t want a separate column for each one of those other values; I want one column that sums up "everything else". Say I had a couple of entries for NbrPlayers being 3, and a couple where NbrPlayers was 7. I’d want an output like:

0pGames    1pGames    2pGames    AllOtherGames
==============================================
1          7          3          4

As far as I can tell, PIVOT doesn’t have this "catch-all" functionality that can be specified in the FOR clause, so how can I achieve this?

>Solution :

You can do the pivot manually, in other words use conditional aggregation

SELECT
  [0pGames] = COUNT(CASE WHEN NbrPlayers = 0 THEN 1 END),
  [1pGames] = COUNT(CASE WHEN NbrPlayers = 1 THEN 1 END),
  [2pGames] = COUNT(CASE WHEN NbrPlayers = 2 THEN 1 END),
  AllOtherGames = COUNT(CASE WHEN NbrPlayers NOT IN (0, 1, 2) THEN 1 END)
FROM (
    SELECT
      gm.Id,
      COUNT(CASE WHEN mbr.UserType = 1 THEN 1 END) AS NbrPlayers
    FROM dbo.Game gm
    LEFT JOIN dbo.GameMembership mbr ON gm.Id = mbr.GameId
    WHERE gm.DateCreated >= '20230430'
      AND gm.DateCreated < '20230501' 
    GROUP BY
      gm.Id
) gm;

Note the use of a date range, rather than casting to date which is inefficient.

Also, your main query seems questionable. You are using a left-join, but then filtering in the COUNT. You could equally filter inside the left-join, then just count the number of matches.

SELECT
  [0pGames] = COUNT(CASE WHEN NbrPlayers = 0 THEN 1 END),
  [1pGames] = COUNT(CASE WHEN NbrPlayers = 1 THEN 1 END),
  [2pGames] = COUNT(CASE WHEN NbrPlayers = 2 THEN 1 END),
  AllOtherGames = COUNT(CASE WHEN NbrPlayers NOT IN (0, 1, 2) THEN 1 END)
FROM (
    SELECT
      gm.Id,
      COUNT(mbr.GameId) AS NbrPlayers
    FROM dbo.Game gm
    LEFT JOIN dbo.GameMembership mbr ON gm.Id = mbr.GameId
        AND mbr.UserType = 1
    WHERE gm.DateCreated >= '20230430'
      AND gm.DateCreated < '20230501' 
    GROUP BY
      gm.Id
) gm;
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading