SQL SERVER T-SQL Calculate SubTotal and Total by group - sql

I am trying to add subtotal by group and total to a table. I've recreated the data using the following sample.
DECLARE #Sales TABLE(
CustomerName VARCHAR(20),
LegalID VARCHAR(20),
Employee VARCHAR(20),
DocDate DATE,
DocTotal Int,
DueTotal Int
)
INSERT INTO #Sales SELECT 'Jhon Titor','12345', 'Employee1','2015-09-01',1000,200
INSERT INTO #Sales SELECT 'Jhon Titor','12345', 'Employee1','2015-08-20',500,100
INSERT INTO #Sales SELECT 'Jhon Titor','12345', 'Employee1','2015-08-18',200,50
INSERT INTO #Sales SELECT 'Deli Armstrong','2345', 'Employee1','2015-09-17',2300,700
INSERT INTO #Sales SELECT 'Deli Armstrong','2345', 'Employee1','2015-09-11',5000,1000
INSERT INTO #Sales SELECT 'Ali Mezzu','6789', 'Employee1','2015-09-07',300,200
Selecting #Sales
I need to add the customer subtotal just below customer occurrences and total in the end row of table like this:
what I've tried so far:
select
case
when GROUPING(CustomerName) = 1 and
GROUPING(Employee) = 1 and
GROUPING(DocDate) = 1 and
GROUPING(LegalID) = 0 then 'Total ' + CustomerName
when GROUPING(CustomerName) = 1 and
GROUPING(Employee) = 1 and
GROUPING(DocDate) =1 and
GROUPING(LegalID) = 1 then 'Total'
else CustomerName end as CustomerName,
LegalID, Employee,DocDate,
sum(DocTotal) as DocTotal,
sum(DueTotal) as DueTotal
From #Sales
group by LegalID, CustomerName,Employee,DocDate with rollup
But I am getting subtotal as null where it should say Total Jhon Titor as I set it static in the query, also it is repeated for every not aggregated column (3),
How can I add subtotal and total to the table presented above?
I am open to use a query without ROLLUP operator. I think it is possible using unions but don't know how to start.
Thanks for considering my question.

I think this is what you want:
select (case when GROUPING(CustomerName) = 0 and
GROUPING(Employee) = 1 and
GROUPING(DocDate) = 1 and
GROUPING(LegalID) = 1
then 'Total ' + CustomerName
when GROUPING(CustomerName) = 1 and
GROUPING(Employee) = 1 and
GROUPING(DocDate) =1 and
GROUPING(LegalID) = 1 then 'Total'
else CustomerName
end) as CustomerName,
LegalID, Employee,DocDate,
sum(DocTotal) as DocTotal,
sum(DueTotal) as DueTotal
From #Sales
group by grouping sets((LegalID, CustomerName ,Employee, DocDate),
(CustomerName),
()
);

You can use the following query:
SELECT CustomerName, LegalID, Employee, DocDate, DocTotal, DueTotal
FROM (
SELECT CustomerName AS cName, CustomerName,
LegalID, Employee, DocDate, DocTotal, DueTotal,
1 AS ord
FROM Sales
UNION ALL
SELECT CustomerName AS cName, CONCAT('Total ', CustomerName),
NULL, NULL, NULL,
SUM(DocTotal), SUM(DueTotal), 2 AS ord
FROM Sales
GROUP BY CustomerName
UNION ALL
SELECT 'ZZZZ' AS cName, 'Total', NULL, NULL, NULL,
SUM(DocTotal), SUM(DueTotal), 3 AS ord
FROM Sales ) AS t
ORDER BY cName, ord
Demo here

Related

SQL Server : how to group only part of the syntax

I have a problem creating a SQL Server query.
In summary, the query should get columns that are sum and count, grouped by customerID, and another column that is a case when by a column that is not used as a grouper column.
My problem is to group only part of the syntax, while the case when column does not need to be grouped.
A sample data, Test:
customerID, 1,2,3,4...
InvoiceID, 1234551, 1234552...
ProductID, A, B, C...
Date, Datetime
Income, int
customerID
InvoiceID
ProductID
Date
Income
1
1234551
A
01/01/2015
300
2
1234552
B
02/01/2016
300
I have a solution, but I am sure there is a more simple solution.
SELECT DISTINCT
Test.CustomerId,
ISNULL(TBL.Income_2015, 0) AS Income_2015,
ISNULL(TBL_2.Income_2016, 0) AS Income_2016,
CASE
WHEN Test.ProductID = 'A'
THEN 'TRUE'
ELSE 'FALSE'
END AS 'purchase_product_A',
TBL_3.Invoices
FROM
Test
LEFT OUTER JOIN
(SELECT CustomerId, SUM(Income) AS Income_2015
FROM Test
WHERE YEAR(Date) = 2015
GROUP BY CustomerId) TBL ON Test.customerID = TBL.customerID
LEFT OUTER JOIN
(SELECT CustomerId, SUM(Income) AS Income_2016
FROM Test
WHERE YEAR(Date) = 2016
GROUP BY CustomerId) TBL_2 ON Test.customerID = TBL_2.customerID
LEFT OUTER JOIN
(SELECT CustomerId, COUNT(InvoiceID) AS Invoices
FROM Test
GROUP BY CustomerId) TBL_3 ON Test.customerID = TBL_3.customerID
To produce:
customerID, 1,2,3...
Income_2015, int
Income_2016, int
Invoices, int
Purchase_product_A, boolean
customerID
Income_2015
Income_2016
Invoices
Purchase_product_A
1
300
300
2
TRUE
10
0
400
1
FALSE
Thanks!
Nir
You may use conditional aggregation with a single pass query:
SELECT
CustomerId,
SUM(CASE WHEN YEAR(Date) = 2015 THEN Income ELSE 0 END) AS Income_2015,
SUM(CASE WHEN YEAR(Date) = 2016 THEN Income ELSE 0 END) AS Income_2016,
COUNT(InvoiceID) AS Invoices,
CASE WHEN COUNT(CASE WHEN ProductID = 'A' THEN 1 END) > 0
THEN 'TRUE' ELSE 'FALSE' END AS [Purchase_product_A]
FROM Test
GROUP BY
CustomerId;

Combine values from multiple rows into one row with each value in it's own column

I have a number of sites were customers can register and I want to combine in one row all the customer IDs associated with an email address across all sites. I am working in SQL Sever.
Example source data:
What I would like to achieve in a new table:
My first thought was something like this, with email as primary key but it doesn't work. Could I use a CASE expression?
SELECT c.[Email],
(SELECT c.[CustomerID] FROM Customer c WHERE c.[BrandID] = '1859') AS [Brand1],
(SELECT c.[CustomerID] FROM Customer c WHERE c.[BrandID] = '1594') AS [Brand2],
(SELECT c.[CustomerID] FROM Customer c WHERE c.[BrandID] = '4578') AS [Brand3]
FROM Customer c
Just use conditional aggregation:
SELECT c.Email,
MAX(CASE WHEN BrandID = 1859 THEN c.[CustomerID] END) as Brand1,
MAX(CASE WHEN BrandID = 1594 THEN c.[CustomerID] END) as Brand2,
MAX(CASE WHEN BrandID = 4578 THEN c.[CustomerID] END) as Brand3
FROM Customer c
GROUP BY c.Email;
BrandID looks like a number so I removed the single quotes around the comparison value. Of course, use single quotes if it is really a string.
You can try case like this:
SELECT c.Email,
SUM(CASE WHEN BrandID = 1859 THEN CustomerID ELSE 0 END) as Brand1,
SUM(CASE WHEN BrandID = 1594 THEN CustomerID ELSE 0 END) as Brand2,
SUM(CASE WHEN BrandID = 4578 THEN CustomerID ELSE 0 END) as Brand3
FROM Customer c
GROUP BY c.Email;
You may use pivot for same.
; with cte as (
select row_number() over (partition by email order by brandid) as Slno,
Email, brandid from table)
, ct as (
select brandid, 'brand' + cast(slno as varchar(5)) as Col, email from cte )
select * from (
select brandid, col, email from ct ) as d
pivot (
max(brandid) for col in ( [brand1], [brand2], [brand3] )
) as p

SQL Server : find dates between two separate row entries

I am trying to pull a list of AccountNumbers that have 2 specific charges (by code) that are at least 2 days apart. These are the columns of my table:
AccountNumber
ServiceDate
Code
Example: if there is AccountNumber for Code = 33967 on ServiceDate 12/11/2018 and an AccountNumber for Code = 33968 on ServiceDate 12/15/2018, the AccountNumber will be output to the results window because these two instances show up on DIFFERENT ServiceDates and are at least 2 days apart.
Example 2: if there is an AccountNumber for Code = 33967 on ServiceDate 12/11/2018 and an AccountNumber for Code = 33968 on ServiceDate 12/11/2018, the AccountNumber will NOT be output to the results window because these two instances show up on the same ServiceDate.
Example 3: if there is an AccountNumber for Code = 33967 on ServiceDate 12/11/2018 and an AccountNumber for Code = 33968 on ServiceDate 12/12/2018, the AccountNumber will NOT be output to the results window because there are no dates between the two ServiceDate's. However if it were 12/11 and 12/13 it would be acceptable because there is a day in-between.
I am only concerned about Code 33967 and 33968, all other codes should not be considered. Right now, I am able to pull all Accounts with both these codes on file but cannot figure out how to go further. Any ideas?
My code is as follows:
SELECT AccountNumber, ServiceDate
FROM dbo.table
WHERE Code = '33968'
INTERSECT
SELECT AccountNumber, ServiceDate
FROM dbo.table
WHERE Code = '33967'
here you go with some sample data. Feel free to add some more rows and test it up
create table #Temp_table
(
AccountNumber int null
, ServiceDate date null
,Code int null
)
insert into #Temp_table values
(1,'12/11/2018',33967)
,(2,'12/15/2018',33968)
,(3,'12/11/2018',33967)
,(4,'12/12/2018',33968)
,(5,'12/17/2018',33968)
,(6,'12/16/2018',33967)
;with CTE_MinDate as (
select --Code ,
MinServiceDate = min(Servicedate)
from #Temp_table
--group by Code
)
--select * from CTE_MinDate
select *
from (
select *
,Days_Diff = datediff(day,MinServiceDate, Servicedate)
from (
select a.*
,MinServiceDate = (select MinServiceDate from CTE_MinDate)
from #Temp_table a
where a.Code in ( 33967,33968)
) a
) b where Days_Diff >= 2
Does this do what you want?
SELECT DISTINCT t.AccountNumber
FROM dbo.table t
WHERE t.Code = '33968' AND
EXISTS (SELECT 1
FROM dbo.table t2
WHERE t2.AccountNumber = t.AccountNumber AND
t2.Code = '33967' AND
t2.ServiceDate <> t.ServiceDate
);
If it's only the Account Number you want then how about:
SELECT AccountNumber
FROM dbo.[Table]
GROUP BY AccountNumber
HAVING COUNT(CASE CODE WHEN 33968 THEN 1 END) > 0
AND COUNT(CASE CODE WHEN 33967 THEN 1 END) > 0;

How to consolidate two sql server tables using Group By clause?

I have two tables TableA and TableB as follows:
TableA:
ItemID Qty Rate
-------- ----- --------
1 10 100.00
2 20 150.00
TableB:
ItemID Qty Rate
-------- ----- -------
1 5 150.00
3 10 200.00
3 20 400.00
Now I want to consolidate these two tables. My desired result needs to be as follows:
Result TableA:
ItemID Qty Rate
-------- ----- -------
1 15 150.00
2 20 150.00
3 30 400.00
I tried the following Insert Select statement, But it does not give the desired result.
INSERT INTO TableA
(
ItemID,
Qty,
Rate
)
SELECT
ItemID,
SUM(Qty),
MAX(Rate)
FROM
TableB
GROUP BY
ItemID
But it gives the result as follows:
ItemID Qty Rate
-------- ----- --------
1 10 100.00
2 20 150.00
1 5 150.00
3 30 400.00
How to achieve my desired result?
I tried like this:
MERGE PUR_PODetail AS Target
USING (
SELECT
#POID,
ItemID,
SUM(POQuantity),
MAX(UnitRate),
1,
CASE WHEN D1 = '' THEN NULL ELSE D1 END D1,
CASE WHEN D2 = '' THEN NULL ELSE D2 END D2,
CASE WHEN D3 = '' THEN NULL ELSE D3 END D3,
CASE WHEN RandomDimension = '' THEN NULL ELSE RandomDimension END RandomDimension,
0
FROM
#Detail
GROUP BY
ItemID, D1, D2, D3, RandomDimension
) AS Source ON (Target.ItemID = Source.ItemID) AND
(ISNULL(Target.D1, -999) = ISNULL(Source.D1, -999)) AND
(ISNULL(Target.D2, -999) = ISNULL(Source.D2, -999)) AND
(ISNULL(Target.D3, -999) = ISNULL(Source.D3, -999)) AND
(ISNULL(Target.RandomDimension, -999) = ISNULL(Source.RandomDimension, -999))
WHEN MATCHED
THEN UPDATE SET
Target.POQuantity = Target.POQuantity + Source.POQuantity,
Target.UnitRate = MAX(Source.UnitRate)
WHEN NOT MATCHED
INSERT
(
POID,
ItemID,
POQuantity,
UnitRate,
ItemStatusID,
D1,
D2,
D3,
RandomDimension,
EDInclusive_f
)
VALUES
(
#POID,
Source.ItemID,
Source.POQuantity,
Source.UnitRate,
1,
CASE WHEN Source.D1 = '' THEN NULL ELSE Source.D1 END D1,
CASE WHEN Source.D2 = '' THEN NULL ELSE Source.D2 END D2,
CASE WHEN Source.D3 = '' THEN NULL ELSE Source.D3 END D3,
CASE WHEN Source.RandomDimension = '' THEN NULL ELSE Source.RandomDimension END RandomDimension,
0
)
But it gives the following error.
Please correct the error. I dont know what would be wrong here.
Msg 102, Level 15, State 1, Procedure PUR_PurchaseOrder_IU, Line 936
Incorrect syntax near 'MERGE'.
Msg 156, Level 15, State 1, Procedure PUR_PurchaseOrder_IU, Line 953
Incorrect syntax near the keyword 'AS'.
But when I remove these merge statement from my stored procedure, it is executing...
You can't just use the INSERT statement for this, you have to either INSERTor UPDATEdepending on the ItemID already being present in your target table.
SQL Server 2005
UPDATE #TableA
SET Qty = a.Qty + b.Qty
, Rate = CASE WHEN a.Rate < b.Rate
THEN b.Rate
ELSE a.Rate
END
FROM #TableA a
INNER JOIN (
SELECT ItemID
, Qty = SUM(Qty)
, Rate = MAX(Rate)
FROM #TableB
GROUP BY
ItemID
) b ON a.ItemID = b.ItemID
INSERT INTO #TableA
SELECT ItemID, Qty, Rate
FROM ( SELECT ItemID
, Qty = SUM(Qty)
, Rate = MAX(Rate)
FROM #TableB b
WHERE NOT EXISTS (SELECT * FROM #TableA a WHERE a.ItemID = b.ItemID)
GROUP BY
ItemID
) b
SQL Server 2008 provides the MERGE statement for this.
Performs insert, update, or delete operations on a target table based on the results of a join with a source table. For example, you
can synchronize two tables by inserting, updating, or deleting rows in
one table based on differences found in the other table.
SQL Server 2008
MERGE #TableA AS Target
USING (
SELECT ItemID
, Qty = SUM(Qty)
, Rate = MAX(Rate)
FROM #TableB
GROUP BY
ItemID
) AS source (ItemID, Qty, Rate) ON (target.ItemID = source.ItemID)
WHEN MATCHED THEN
UPDATE SET target.Qty = target.Qty + source.Qty
, target.Rate = CASE WHEN target.Rate < source.Rate
THEN source.Rate
ELSE target.Rate
END
WHEN NOT MATCHED THEN
INSERT (ItemID, Qty, Rate)
VALUES (source.ItemID, source.Qty, source.Rate);
Try this one:
MERGE TableA T
USING
(
SELECT ItemId, SUM(Qty) Qty, MAX(Rate) Rate
FROM
(
SELECT ItemId, Qty, Rate from TableA
UNION ALL
SELECT ItemId, Qty, Rate from TableB
) S
ON T.ItemId = S.ItemId
WHEN MATCHED THEN UPDATE SET
Qty = S.Qty,
Rate= S.Rate
WHEN NOT MATCHED THEN
INSERT(ItemId, Qty, Rate)
VALUES(S.ItemId, S.Qty, S.Rate);
declare #t table (ItemID int,Qty int,Rate int)
insert into #t values (1,10,100),(2,20,150)
Select * from #t
declare #t1 table (ItemID int,Qty int,Rate int)
insert into #t1 values (1,5,150),(3,10,200),(3,20,400)
Select * from #t1
insert into #t
Select ItemID,sum(Qty) Qty,max(Rate) Rate from #t1 where ItemID not in (Select ItemID from #t)
group by ItemID
Select * from #t

Sum different row in column based on second column value

I have an Orders table (simplified)
OrderId,
SalesPersonId,
SaleAmount,
CurrencyId,
...
I am attempting to create a report on this table, I'm hoping for something like:
SalesPersonId TotalCAD TotalUSD
1 12,345.00 6,789.00
2 7,890.00 1,234.00
I'd prefer not to do a self join (perhaps I'm optimizing prematurely, but this seems inefficient) IE:
SELECT SalesPersonId, SUM(OrdersCAD.SaleAmount), SUM(OrderUSD.SaleAmount)
FROM Orders
LEFT JOIN Orders AS OrdersCAD ON Orders.OrderID AND Orders.CurrencyID = 1
LEFT JOIN Orders AS OrdersUSD ON Orders.OrderID AND Orders.CurrencyID = 2
But I cannot think of another way to do this, any ideas?
Use a CASE block:
SELECT
SalesPersonId,
SUM(
CASE CurrencyID
WHEN 1 THEN SaleAmount
ELSE 0
END
) AS TotalCAD,
SUM(
CASE CurrencyID
WHEN 2 THEN SaleAmount
ELSE 0
END
) AS TotalUSD
FROM Orders
GROUP BY SalesPersonId
Try This:
SELECT SalesPersonId,
SUM(CASE WHEN CurrencyID = 1 THEN SaleAmount ELSE 0 END) as CAD,
SUM(CASE WHEN CurrencyID = 2 THEN SaleAmount ELSE 0 END) as USD
FROM ORDERS
Consider trying out a scalar-valued function (SQL Server 2000 or later).
CREATE FUNCTION dbo.GetOrdersSumByCurrency
(
#SalesPersonID INT, #CurrencyID INT
)
RETURNS DECIMAL(10, 2)
AS
BEGIN
DECLARE #Sum DECIMAL(10, 2)
SELECT #Sum = ISNULL(SUM(SalesAmount), 0) FROM dbo.Orders
WHERE SalespersonID=#SalesPersonID AND CurrencyID = #CurrencyID
RETURN #Sum
END
Then execute SQL such as this to get the results (assumes you also have a separate salespersons table, or otherwise use instead SELECT DISTINCT SalesPersonId.... FROM Orders) :
SELECT SalesPersonId,
dbo.GetOrdersSumByCurrency(SalesPersonId, 1) AS SumUSD, dbo.GetOrdersSumByCurrency(SalesPersonId, 2) AS SumCAD
FROM SalesPersons
Be sure to run query plans to see if it performs as you need compared against the other possibilities suggested here, especially if you are processing a large quantity of data.