2

My current project/issue is we have a list of permit types. With these types they are needing to be grouped by an overall category. Once grouped out the end goal is to count how many within specified date range along with a sum total of the estimated value. The initial group category element doesn't exist.

I'm trying to learn how to use nested select statements, and thought I might be able to do that for each grouped category. Although I can technically get it to work, all the resulted data displayed is on one single row. I figured I'm probably going to need to create a temporary table, but I don't fully understand how it works, and how I would apply it to my query.

declare @CATEGORY1 nvarchar(40)
declare @CATEGORY2 nvarchar(40)
declare @CATEGORY3 nvarchar(40)
declare @CATEGORY4 nvarchar(40)
declare @CATEGORY5 nvarchar(40)
declare @CATEGORY6 nvarchar(40)

Set @CATEGORY1 = 'Commercial - New'
Set @CATEGORY2 = 'Commercial - Modifications'
Set @CATEGORY3 = 'Multi-Family - New'
Set @CATEGORY4 = 'Multi-Family - Modifications'
Set @CATEGORY5 = 'Residential - New'
Set @CATEGORY6 = 'Residential - Modifications'

select 
@CATEGORY4 as 'Group Category'

, query1.[Count]
, query1.[SUM]

,@CATEGORY5 as 'Group Category'
, query2.[Count]
, query2.[SUM]

from 
(
Select
    Count(DISTINCT P1.PermitNum) as 'Count'
    , Sum(P1.EstimatedValue) as 'SUM'
FROM
    Permit P1
where P1.PermitTypeMasterID in (57,60,59)
and
P1.CreatedDate >= '2025-01-01' 
) as query1 , 

(
Select
    Count(DISTINCT P2.PermitNum) as 'Count'
    , Sum(P2.EstimatedValue) as 'SUM'
FROM
    Permit P2
where P2.PermitTypeMasterID in (1,46,78,79)
and
P2.CreatedDate >= '2025-01-01' 
) as query2 ;

The results of the above looks like this:

Group Category Count SUM Group Category Count SUM
Multi-Family - Modifications 15 8360000 Residential - New 72 32360475

What my end goal is something like this:

Group Category Count SUM
Multi-Family - Modifications 15 8360000
Residential - New 72 32360475

3 Answers 3

3

You are on the right track with using subqueries to group and aggregate your data but the issue is that you are selecting both subqueries side by side which gives you one wide row with multiple columns instead of multiple rows (one per group).

What you want is rows per category, which you can achieve using UNION ALL.

SELECT 
    'Multi-Family - Modifications' AS [Group Category],
    COUNT(DISTINCT P1.PermitNum) AS [Count],
    SUM(P1.EstimatedValue) AS [SUM]
FROM Permit P1
WHERE P1.PermitTypeMasterID IN (57, 60, 59)
  AND P1.CreatedDate >= '2025-01-01'

UNION ALL

SELECT 
    'Residential - New' AS [Group Category],
    COUNT(DISTINCT P2.PermitNum) AS [Count],
    SUM(P2.EstimatedValue) AS [SUM]
FROM Permit P2
WHERE P2.PermitTypeMasterID IN (1, 46, 78, 79)
  AND P2.CreatedDate >= '2025-01-01'
Sign up to request clarification or add additional context in comments.

1 Comment

@Josh.STCH Instead of repeating logic for every category you can define them once using a CTE too
2

You would greatly benefit from using a lookup table.

This allows you to reduce a bulky query full of hardcoded conditions to a simple and maintainable JOIN.

For example, create a table like this:

CREATE TABLE PermitCategoryLookup (
    PermitTypeMasterID INT PRIMARY KEY,
    GroupCategory VARCHAR(50)
);

...and then save your lookup data there...

INSERT INTO PermitCategoryLookup (PermitTypeMasterID, GroupCategory) VALUES
(57, 'Commercial - New'),
(60, 'Commercial - New'),
(59, 'Commercial - New'),

(1, 'Commercial - Modifications'),
(46, 'Commercial - Modifications'),
(78, 'Commercial - Modifications'),
(79, 'Commercial - Modifications'),

(100, 'Multi-Family - New'),
(101, 'Multi-Family - New'),

(102, 'Multi-Family - Modifications'),
(103, 'Multi-Family - Modifications'),

(200, 'Residential - New'),
(201, 'Residential - New'),

(202, 'Residential - Modifications'),
(203, 'Residential - Modifications');

After that, the final query becomes straightforward:

SELECT
    pcl.GroupCategory,
    COUNT(DISTINCT p.PermitNum) AS Count,
    SUM(p.EstimatedValue) AS SUM
FROM Permit p
JOIN PermitCategoryLookup pcl ON p.PermitTypeMasterID = pcl.PermitTypeMasterID
WHERE p.CreatedDate >= '2025-01-01'
GROUP BY pcl.GroupCategory;

If the categorization changes later, you can simply update the lookup data - potentially even via a UI - without touching the query or source code.

Without a lookup table, every change requires editing the query itself, which quickly becomes messy, error-prone and hard to maintain due to long CASE expressions or chains of UNION ALL.

See this db<>fiddle with sample data. It shows how much cleaner this approach is compared to CASE or UNION ALLlogic.

1 Comment

Thank you all for your comments. With SQL, and many other programs, there is always multiple ways. So will definitely keep an open mind with all the information provided as it was all greatly appreciated. I initially thought about the CASE use, but I wasn't able to think through it enough to work so that information is helpful. The Union All I have not used, but want to look more into. The use of creating a new table for the sake of looking up values I think is what I was pursuing logically so it caught my eye, and easiest for my mind to interpret. Again, thank you all for your insight.
0

The union all approach is a nice and quick way to get your results with the least modifications to your presented query. However, if you have to do it on 6 categories, and have more complex computations to do on your data (but want the same computations for all categories), you'll risk copy-pasting errors and should probably convert your PermitTypeMasterID to a category then group by those categories.

In addition to the code mutualization (same WHERE filter, same SUM…) you'll get the benefit of an "other" category to show PermitTypeMasterID values that you would have forgotten; and the possibility to order (by decrementing SUM for example

Your query would then look like:

Select
    Case
        When P.PermitTypeMasterID in (57,60,59) Then 'Multi-Family - Modifications'
        When P.PermitTypeMasterID in (1, 46, 78, 79) Then 'Residential - New'
        …
        Else 'Others'
    End AS [Group Category],
    Count(DISTINCT P.PermitNum) as 'Count'
    , Sum(P.EstimatedValue) as 'SUM'
FROM
    Permit P
WHERE
P.CreatedDate >= '2025-01-01' 
GROUP BY <first column of result>;

Alas, SQL Server doesn't allow this GROUP BY <first column of result>, so you'll have to either:

  1. repeat the Case in the GROUP BY
  2. first add the category to each row (thanks to a subquery or Common Table Expression
  3. or use a reference table

In either case you'll get the expected:

Group Category Count SUM
Multi-Family - Modifications 2 8360000
Residential - New 5 32360475

You'll find the 3 possibilities under this db<>fiddle, and here I'll detail 2. and 3.:

2. With a CTE

A CTE (Common Table Expression) is like an evanescent table that lasts (and is visible) only for your query. Like the temporary tables your spoke of, but with even less side effects.

The simplest would be:

With PermitAndCategory As (Select *, Case When … End AS [Group Category] FROM Permit P)
Select [Group Category], Count(DISTINCT P.PermitNum) as 'Count', Sum(P.EstimatedValue) as 'SUM'
From PermitAndCategory P
WHERE P.CreatedDate >= '2025-01-01'
GROUP BY [Group Category];

However, like a temporary table, it has to store its results server-side, so unless you have a small dataset we'd better:

  • not use * but select only the columns that will be used in the main, final Select
  • filter as soon as possible
  • group as soon as possible (at least we can pre-GROUP BY PermitTypeMasterID)

to alleviate the database server's memory use, and its final GROUP BY (a CTE is not indexed, so the less it has rows, the quicker the final query will be):

With PermitAndCategory As
(
    Select
        Case
            When P.PermitTypeMasterID in (57,60,59) Then 'Multi-Family - Modifications'
            When P.PermitTypeMasterID in (1, 46, 78, 79) Then 'Residential - New'
            -- …
            Else 'Others'
        End AS [Group Category],
        Count(DISTINCT P.PermitNum) as 'Count'
        , Sum(P.EstimatedValue) as 'SUM'
    FROM Permit P
    WHERE P.CreatedDate >= '2025-01-01'
    GROUP BY P.PermitTypeMasterID
)
Select [Group Category], Sum([Count]) as [Count], Sum([SUM]) as [SUM]
From PermitAndCategory
Group By [Group Category];
3. Use a reference table

In addition to ease grouping (you just have to group by [Group Category] after having joined to the reference table mapping each PermitTypeMasterID to its [Group Category]), you will ease your maintenance when adding new values of PermitTypeMasterID: instead of 2 medias (one insert to Permit, one text script to deploy), you'll have one (inserts to 2 tables of the same database).

create table MasterCategory
(
    PermitTypeMasterID int,
    [Group Category] varchar(99)
    constraint PermitTypeMasterIDUnique Unique(PermitTypeMasterID)
);
insert into MasterCategory values
    (57, 'Multi-Family - Modifications'),
    (59, 'Multi-Family - Modifications'),
    (60, 'Multi-Family - Modifications'),
    (1, 'Residential - New'),
    (46, 'Residential - New'),
    (78, 'Residential - New'),
    (79, 'Residential - New'),
    …;

Select
    MC.[Group Category],
    Count(DISTINCT P.PermitNum) as 'Count'
    , Sum(P.EstimatedValue) as 'SUM'
FROM Permit P Left Join MasterCategory MC On MC.PermitTypeMasterID = P.PermitTypeMasterID -- Left Join, in case we have forgotten to insert a value to MasterCategory.
WHERE P.CreatedDate >= '2025-01-01'
GROUP BY MC.[Group Category]
;

(and in fact you could even have a reference table for categories, whose key would be an integer referenced from MasterCategory, to avoid typing errors in MasterCategory)

I would recommend this solution that puts all your data and metadata in the same database, not relying on your SQL script that is maintained apart and could get lost independently of the database.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.