Jonathan Wood
Jonathan Wood

Reputation: 67193

Combining UNION ALL with GROUP BY

I could really use a little help with this query.

I have a Products table.

CREATE TABLE [dbo].[Products](
    [Id] [int] NOT NULL,
    [Title] [nvarchar](50) NOT NULL,
    -- Additional column omitted
    CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED 
    (
        [Id] ASC
    ) ON [PRIMARY]
) ON [PRIMARY]

And a Transactions table.

CREATE TABLE [dbo].[Transactions](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ProductId] [int] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,
    [Quantity] [decimal](9, 3) NOT NULL,
    [TotalAmount] [bigint] NOT NULL,
    -- Additional columns omitted
    CONSTRAINT [PK_Transactions] PRIMARY KEY CLUSTERED 
    (
        [Id] ASC
    ) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[Transactions]  WITH CHECK ADD  CONSTRAINT [FK_Transactions_Products] FOREIGN KEY([ProductId])
REFERENCES [dbo].[Products] ([Id])

And a CCTransactions table.

CREATE TABLE [dbo].[CCTransactions](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ProductId] [int] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,
    [Quantity] [decimal](9, 3) NOT NULL,
    [TotalAmount] [bigint] NOT NULL,
    -- Additional columns omitted
    CONSTRAINT [PK_CCTransactions] PRIMARY KEY CLUSTERED 
    (
        [Id] ASC
    )
) ON [PRIMARY]

ALTER TABLE [dbo].[CCTransactions]  WITH CHECK ADD  CONSTRAINT [FK_CCTransactions_Products] FOREIGN KEY([ProductId])
REFERENCES [dbo].[Products] ([Id])

I need a query that UNION ALLs all the Transactions and CCTransactions that fall within a given date range, and then groups them as Product.Id, Product.Title, SUM(Quantity), SUM(TotalAmount).

Below is what I have so far. It won't compile but I'm including because people usually want to see what you've tried. It tells me:

Msg 8120, Level 16, State 1, Line 5
Column 'Products.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Msg 209, Level 16, State 1, Line 14
Ambiguous column name 'Id'.

I understand the errors, but am not sure the best way to fix them. The query seemed to be working before I added the union.

DECLARE @dtStart DATE = '2016-08-01';
DECLARE @dtEnd DATE = '2016-08-31';

SELECT p.Id, p.Title, SUM(t.Quantity), SUM(t.TotalAmount) AS Amount
FROM Transactions t
INNER JOIN Products p ON t.ProductId = p.Id
WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd
UNION ALL
SELECT p.Id, p.Title, SUM(t.Quantity), SUM(t.TotalAmount) AS Amount
FROM CCTransactions t
INNER JOIN Products p ON t.ProductId = p.Id
WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd
GROUP BY p.Id, p.Title
ORDER BY Title

Upvotes: 2

Views: 6209

Answers (2)

Code Different
Code Different

Reputation: 93161

You need 2 separate GROUP BYs:

SELECT p.Id, p.Title, SUM(t.Quantity), SUM(t.TotalAmount) AS Amount
FROM Transactions t
INNER JOIN Products p ON t.ProductId = p.Id
WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd
GROUP BY p.Id, p.Title

UNION ALL

SELECT p.Id, p.Title, SUM(t.Quantity), SUM(t.TotalAmount) AS Amount
FROM CCTransactions t
INNER JOIN Products p ON t.ProductId = p.Id
WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd
GROUP BY p.Id, p.Title

Or a subquery:

SELECT      X.id, X.Title, SUM(X.Quantity), SUM(X.TotalAmount)
FROM        (
                SELECT p.Id, p.Title, t.Quantity, t.TotalAmount
                FROM Transactions t
                INNER JOIN Products p ON t.ProductId = p.Id
                WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd

                UNION ALL

                SELECT p.Id, p.Title, t.Quantity, t.TotalAmount
                FROM CCTransactions t
                INNER JOIN Products p ON t.ProductId = p.Id
                WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd
            ) X
GROUP BY    X.Id, X.Title

Or you can rewrite the entire query:

;WITH
    AllTransaction AS
    (
        SELECT      ProductID, Timestamp, Quantity, TotalAmount
        FROM        Transaction
        UNION ALL
        SELECT      ProductID, Timestamp, Quantity, TotalAmount
        FROM        CCTransaction
    )

SELECT      p.Id, p.Ttitle, SUM(t.Quantity), SUM(t.TotalAmount)
FROM        AllTransaction      t
INNER JOIN  Products            p   ON t.ProductId = p.Id
WHERE       @dtStart <= t.[Timestamp] AND t.[TimeStamp] < DATEADD(DAY, 1, @dtEnd)
GROUP BY    p.Id, p.Title

A note on CAST(t.[TimeStamp] AS DATE) <= @dtEnd: this condition is not SARGable , meaning index and statistics are of no help. The result of CAST(t.[TimeStamp] AS DATE) is not known until you have applied the function to each and every row. This causes a table scan (i.e. reading the entire table in) while you may only need a few rows. If Transactions and CCTransactions are huge, you will suffer.

Upvotes: 2

forpas
forpas

Reputation: 164099

First use UNION ALL for the 2 tables and join the result to Products:

DECLARE @dtStart DATE = '2016-08-01';
DECLARE @dtEnd DATE = '2016-08-31';

SELECT p.Id, p.Title, SUM(t.Quantity), SUM(t.TotalAmount) AS Amount
FROM (
  SELECT ProductId, [TimeStamp], Quantity, TotalAmount FROM Transactions
  UNION ALL
  SELECT ProductId, [TimeStamp], Quantity, TotalAmount FROM CCTransactions
) t INNER JOIN Products p ON t.ProductId = p.Id
WHERE t.[TimeStamp] >= @dtStart AND CAST(t.[TimeStamp] AS DATE) <= @dtEnd
GROUP BY p.Id, p.Title
ORDER BY Title

Upvotes: 2

Related Questions