How to get a difference between two query result sets - sql

I have two queries. The output for the first query is the total sales quantity for all Brands and the output for the second query is the total sales quantity only for 'New' Brands.
I need to create only one query (By merging below two queries: Query1 & Query2) where we can see the total sales of 'New' brands per Region, total sales of All brands per region and a new column named difference (Total sales quantity of All brands- Total sales quantity of New brands) side by side.
Expected Output :
InvoiceDate
Brand
Region
Quantity for 'New' Brand
Quantity for All Brand
Difference
2021/10/01
New
A
40
100
60
2021/10/01
New
B
10
90
80
2021/10/01
New
C
50
150
100
2021/10/01
New
D
30
200
170
These are my queries:
Query1:
SELECT InvoiceDate, Brand, Region, Quantity From TotalSales // For All Brands
Query2:
SELECT InvoiceDate, Brand, Region, Quantity From TotalSales where Brand='New' // For New Brands

There are a couple of ways of doing this...
First - I don't think you want the "Brand" column in your result. That doesn't make must sense. Also, I think you are going to want a summation for the AllBrands total...
Use subqueries
select allBrands.InvoiceDate, allBrands.Region, newBrands.Quantity as NewQuantity, allBrands.Quantity as allQuantity, allBrands.Quantity-newBrands.Quantity as Difference
FROM
(SELECT InvoiceDate, Region, SUM(Quantity) as Quantity From TotalSales GROUP BY InvoiceDate, Region) as allBrands
LEFT OUTER JOIN (SELECT InvoiceDate, Region, Quantity From TotalSales where Brand='New') as NewBrands ON NewBrands.InvoiceDate = allBrands.InvoiceDate AND NewBrands.Region = AllBrands.Region
or 2. use temp tables
SELECT InvoiceDate, Region, SUM(Quantity) as Quantity INTO #allSales From TotalSales GROUP BY InvoiceDate, Region;
SELECT InvoiceDate, Region, Quantity INTO #newSales From TotalSales where Brand='New';
select allBrands.InvoiceDate, allBrands.Region, newBrands.Quantity as NewQuantity, allBrands.Quantity as allQuantity, allBrands.Quantity-newBrands.Quantity as Difference
FROM #allBrands allBrands
LEFT OUTER JOIN #newBrands newBrands ON NewBrands.InvoiceDate = allBrands.InvoiceDate AND NewBrands.Region = AllBrands.Region;

You want to get the quantity for brand = 'new' and the total quantity for all brands and compare the two.
One way to achieve this is conditional aggregation:
select
invoicedate,
'New' as brand,
region,
sum(case when brand = 'New' then quantity else 0 end) as qty_new,
sum(quantity) as qty_all,
sum(quantity) - sum(case when brand = 'New' then quantity else 0 end) as diff
from totalsales
group by invoicedate, region
having sum(case when brand = 'New' then quantity else 0 end) > 0
order by invoicedate, region;
Another is a join
with qnew as
(
select invoicedate, brand, region, quantity
from totalsales
where brand = 'New'
)
, qall as
(
select invoicedate, region, sum(quantity) as total
from totalsales
group by invoicedate, region
)
select
qnew.*, qall.total, qall.total- qnew.quantity as diff
from qnew
join qall on qall.invoicedate = qnew.invoicedate
and qall.brand = qnew.brand
and qall.region = qnew.region
order by qnew.invoicedate, qnew.brand, qnew.region;

You can use simple conditional aggregation (SUM) on the data such as this:
DECLARE #TotalSales TABLE (InvoiceDate DATE, Brand NVARCHAR(16), Region NCHAR(1), Quantity INT)
INSERT INTO
#TotalSales(
InvoiceDate,
Brand,
Region,
Quantity
)
VALUES ('10/1/2021', 'New', 'A', 20),
('10/1/2021', 'New', 'A', 20),
('10/1/2021', 'Old', 'A', 30),
('10/1/2021', 'Old', 'A', 30),
('10/1/2021', 'New', 'B', 10),
('10/1/2021', 'Old', 'B', 30),
('10/1/2021', 'Old', 'B', 50),
('10/1/2021', 'New', 'C', 50),
('10/1/2021', 'Old', 'C', 100),
('10/1/2021', 'New', 'D', 10),
('10/1/2021', 'New', 'D', 10),
('10/1/2021', 'New', 'D', 10),
('10/1/2021', 'Old', 'D', 100),
('10/1/2021', 'Old', 'D', 70),
('11/1/2021', 'Old', 'A', 50)
;WITH Data AS (
SELECT
ts.InvoiceDate,
ts.Region,
SUM(ts.Quantity) AS QuantityAll,
SUM(CASE WHEN ts.Brand = 'New' THEN ts.Quantity ELSE 0 END) AS QuantityNew
FROM
#TotalSales ts
GROUP BY
ts.InvoiceDate,
ts.Region
)
SELECT
d.InvoiceDate,
d.Region,
d.QuantityAll,
d.QuantityNew,
d.QuantityAll - d.QuantityNew AS TheDifference
FROM
Data d
ORDER BY
d.InvoiceDate,
d.Region
I used a CTE so that we don't have to repeat the conditional SUM(CASE WHEN... for subtracting between QuantityNew and QuantityAll.
Output is:
InvoiceDate Region QuantityAll QuantityNew TheDifference
2021-10-01 A 100 40 60
2021-10-01 B 90 10 80
2021-10-01 C 150 50 100
2021-10-01 D 200 30 170
2021-11-01 A 50 0 50

Related

Get userwise balance and first transaction date of users in SQL

I have created a Transaction table with columns card_id, amount, created_at. There may be more than 1 row of one user so I want to return the value card_id, sum(amount), first created_at date of all users.
CREATE TABLE Transactions(card_id int, amount money, created_at date)
INSERT INTO Transactions(card_id, amount, created_at)
SELECT 1, 500, '2016-01-01' union all
SELECT 1, 100, '2016-01-01' union all
SELECT 1, 100, '2016-01-01' union all
SELECT 1, 200, '2016-01-02' union all
SELECT 1, 300, '2016-01-03' union all
SELECT 2, 100, '2016-01-04' union all
SELECT 2, 200, '2016-01-05' union all
SELECT 3, 700, '2016-01-06' union all
SELECT 1, 100, '2016-01-07' union all
SELECT 2, 100, '2016-01-07' union all
SELECT 3, 100, '2016-01-07'
I have created function for that but one of my client says I need query not function. Can anyone here suggest what query to use?
CREATE FUNCTION [dbo].[card_id_data]()
RETURNS #t TABLE
(
card_id text,
amount money,
dateOfFirstTransaction date
)
AS
BEGIN
INSERT INTO #t(card_id)
SELECT DISTINCT(card_id) FROM Transactions;
UPDATE #t
SET dateOfFirstTransaction = b.createdat
FROM
(SELECT DISTINCT(card_id) cardid,
MIN(created_at) createdat
FROM Transactions
WHERE amount < 0
GROUP BY card_id) b
WHERE card_id = b.cardid;
UPDATE #t
SET amount = T.AMOUNT
FROM
(SELECT
card_id AS cardid, SUM(MIN(AMOUNT)) AMOUNT, created_at
FROM Transactions
WHERE amount < 0
GROUP BY card_id, created_at) T
WHERE card_id = cardid
AND dateOfFirstTransaction = created_at;
RETURN
END
I want a result as shown in this screenshot:
You can use DENSE_RANK for this. It will number the rows, taking into account tied places (same dates)
SELECT
t.card_id,
SumAmount = SUM(amount),
FirstDate = MIN(t.created_at)
FROM (
SELECT *,
rn = DENSE_RANK() OVER (PARTITION BY t.card_id ORDER BY t.created_at)
FROM dbo.Transactions t
) t
WHERE t.rn = 1
GROUP BY t.card_id;
If the dates are actually dates and times, and you want to sum the whole day, change t.created_at to CAST(t.created_at AS date)
Try this:
/*
CREATE TABLE dbo.Transactions
(
card_id INT,
amount MONEY,
created_at DATE
);
INSERT INTO dbo.Transactions (card_id, amount, created_at)
VALUES (1, 500, '2016-01-01'),
(1, 100, '2016-01-01'),
(1, 100, '2016-01-01'),
(1, 200, '2016-01-02'),
(1, 300, '2016-01-03'),
(2, 100, '2016-01-04'),
(2, 200, '2016-01-05'),
(3, 700, '2016-01-06'),
(1, 100, '2016-01-07'),
(2, 100, '2016-01-07'),
(3, 100, '2016-01-07');
*/
WITH FirstDatePerCard AS
(
SELECT
card_id,
FirstDate = MIN(created_at)
FROM
dbo.Transactions
GROUP BY
card_id
)
SELECT DISTINCT
t.card_id,
SumAmount = SUM(amount) OVER (PARTITION BY t.card_id),
FirstDate = f.FirstDate
FROM
FirstDatePerCard f
INNER JOIN
dbo.Transactions t ON f.card_id = t.card_id AND f.FirstDate = t.created_at
You'll get an output something like this:
card_id SumAmount FirstDate
--------------------------------
1 700.00 2016-01-01
2 100.00 2016-01-04
3 700.00 2016-01-06
Is that what you're looking for??
UPDATE: OK, so you want to sum the amount only for the first_date, for every card_id - is that correct? (wasn't clear from the original question)
Updated my solution accordingly

I would like to display current month, year to date and previous year to date on ssrs

Data Presentation
Would like to calculate year to date and previous year to date current month on sql view so that to simply data presentation on ssrs to make it run faster. Is there a way to write a view which can perform this ?
Ignoring the fact that I think you have some errors in your Previous YTD summary numbers..
I recreated the data as per your example
CREATE TABLE #t (TransDate date, Customer varchar(30), Amount float)
INSERT INTO #t VALUES
('2020-09-21', 'Customer 1', 200),
('2020-09-22', 'Customer 2', 300),
('2020-08-03', 'Customer 2', 450),
('2020-08-04', 'Customer 1', 1200),
('2019-09-14', 'Customer 1', 859),
('2019-02-05', 'Customer 2', 230),
('2019-07-26', 'Customer 2', 910),
('2019-11-17', 'Customer 1', 820)
Then the following statement will produce what you need. It is NOT the most elegant way of doing this but it will convert to a view easily and was all I could come up with in the time I had.
SELECT
m.Customer
, m.MTD as [Current Month]
, y.YTD as [Current YTD]
, p.YTD as [Previous YTD]
FROM (
SELECT Customer, Yr = Year(TransDate), Mn = MONTH(TransDate), MTD = SUM(Amount) FROM #t t WHERE MONTH(TransDate) = MONTH(GetDate()) and YEAR(TransDate) = YEAR(GetDate())
GROUP by Customer, YEAR(TransDate), MONTH(TransDate)
) m
JOIN (SELECT Customer, Yr = YEAR(TransDate), YTD = SUM(Amount) FROM #t t GROUP by Customer, YEAR(TransDate)) y on m.Customer =y.Customer and m.Yr = y.Yr
JOIN (SELECT Customer, Yr = YEAR(TransDate), YTD = SUM(Amount) FROM #t t GROUP by Customer, YEAR(TransDate)) p on y.Customer =p.Customer and y.Yr = p.Yr + 1
This gives the following results (which don;t match your example but I think your sample is incorrect)

Conditional sum and count in Teradata (SQL help)

My data is setup in the following way:
Person Account Revenue Region
A W 100 AU
A W 200 AU
A W 300 AU
B X 200 AU
B X 50 CH
B X 50 CH
Here is code for the sample data:
IF OBJECT_ID('tempdb..#StackTest') IS NOT NULL
DROP TABLE #StackTest;
CREATE TABLE #StackTest
(Person varchar(1)
, Account varchar(1)
, Revenue int
, Region varchar(2));
INSERT INTO #StackTest
(Person
, Account
, Revenue
, Region)
VALUES
('A', 'W', 100, 'AU'),
('A', 'W', 200, 'AU'),
('A', 'W', 300, 'AU'),
('B', 'X', 200, 'AU'),
('B', 'X', 50, 'CH'),
('B', 'X', 50, 'CH');
I need to write a SQL query that sums revenue for only those accounts when total sum of an account Q exceeds Y. Similarly, I also need to count only those accounts when total sum of an account Q exceeds Y. So if my sum threshold for region AU is 500 and for region CH is 200, then I would want the following output
Output # of accounts exceeding threshold sum Revenue from these accounts
A 1 600
B 0 0
However, my current query is checking each line item separately and not at the account level.
What should I do?
In standard SQL, you would use two levels of aggregation. I suspect that the query is something like:
select person,
sum(case when region = 'AU' and revenue > 500 then 1
when region = 'CH' and revenue > 200 then 1
else 0
end) as numAccounts,
sum(case when region = 'AU' and revenue > 500 then revenue
when region = 'CH' and revenue > 200 then revenue
else 0
end) as reveue,
from (select person, region, sum(revenue) as revenue
from t
group by person, region
) t
group by person;
The following query will aggregate by person/region, and then apply regional thresholds from separate table in order to generate results.
Updated to account for separate regional thresholds
IF OBJECT_ID('tempdb..#Thresholds') IS NOT NULL
DROP TABLE #Thresholds
CREATE TABLE #Thresholds (Region VARCHAR(2), Revenue INT)
INSERT #Thresholds VALUES ('AU', 500), ('CH', 200)
--DECLARE #Threshold INT = 500
SELECT
T.Person,
SUM(CASE WHEN T.[Revenue] >= Thresholds.Revenue THEN T.[Count] ELSE 0 END) AS [# of accounts exceeding threshold sum],
SUM(CASE WHEN T.[Revenue] >= Thresholds.Revenue THEN T.[Revenue] ELSE 0 END) AS [Revenue from these accounts]
FROM (
SELECT
Person,
Region, -- Add region to apply thresholds by region
COUNT(DISTINCT Account) AS [Count],
SUM(Revenue) AS [Revenue]
FROM #StackTest
GROUP BY Person, Region
) T
INNER JOIN #Thresholds Thresholds
ON Thresholds.Region = T.Region
GROUP BY Person
ORDER BY Person

SQL Query help: pick newest record and join to another table

I've been trying and trying without success to write a query in SQL Server 2012 that works here.
I have tblProducts that looks like this and has about 100000 rows:
SKU, Title, CategoryID
155, 'Product a', 5
176, 'Product b', 5
630, 'Product 1', 10
and tblPrices which looks like this and has about a million rows, each recording the price and stock of the item at a certain time:
SKU, Price, StockCount, TimeStamp (smalldatetime)
155, 10, 5, 2012-12-31 23:40:00
155, 9, 6, 2012-12-30 23:40:00
155, 7, 6, 2012-12-29 21:40:00
176, 0.50, 0, 2012-12-31 23:40:00
Basically, I want to get a list of the SKUs which are in category 5 and their current price is > 3.
So far, I've got:
SELECT *
FROM tblPrices
WHERE Timestamp IN (
SELECT MAX(TimeStamp)
FROM tblPrices
GROUP BY SKU
which gives me the current/newest price of each SKU in tblPrices:
SKU, Price, StockCount, TimeStamp
155, 10, 5, 2012-12-31 23:40:00
176, 0.50, 0, 2012-12-31 23:40:00
but what I really need is to filter this table by price, join it to tblProducts and then filter by category. However, no matter how I try this, it returns several rows for each SKU or ignores the price condition. The below returns multiple SKUs (thus missing the point of newest price must be > 3) and also takes about 2 minutes to execute and return 810 rows, about 1 in 4 of which are unique:
SELECT tblProducts.SKU , CategoryID
FROM tblProducts,
(SELECT EAN, Price
FROM tblPrices
WHERE Timestamp IN (
SELECT MAX(TimeStamp)
FROM tblPrices
GROUP BY SKU
)) a
WHERE tblProducts.EAN = a.EAN AND tblProducts.CategoryID=5 AND a.Price > 3
I would be really grateful for any help, as I'm getting nowhere on my own.
Using a subquery in the WHERE clause
SELECT SKU
FROM tblProducts
WHERE CategoryId = 5 AND SKU IN (
SELECT SKU
FROM tblPrices
WHERE Price > 3 AND Timestamp IN (
SELECT MAX(TimeStamp)
FROM tblPrices
GROUP BY SKU
)
)
You can use ROW_NUMBER in a CTE with PARTITION BY SKU and ORDER BY TimeStamp DESC to get only a single row for each SKU with CATEGORYID=5:
WITH CTE AS
(
SELECT
PR.SKU, PR.Price, PR.StockCount, PR.TimeStamp, P.Title, P.CategoryID,
ROW_NUMBER() OVER (PARTITION BY PR.SKU ORDER BY PR.TimeStamp DESC) AS RN
FROM
tblProducts P
INNER JOIN tblPrices PR ON P.SKU=PR.SKU
WHERE
p.CategoryID=5
AND PR.Price > 3
)
SELECT
SKU, Price, StockCount, TimeStamp, Title, CategoryID
FROM
CTE
WHERE
RN = 1
The CTE is like a subquery and the PARTITION BY is similar to a GROUP BY.
Same idea as Voithos with a correlated subquery.
SELECT p.SKU, p.Price
FROM tblPrices p
Join tblProducts d
On d.sku = p.sku
WHERE d.CategoryId = 5
And p.Price > 3
AND p.Timestamp =
(Select Max(TimeStamp)
From tblPrices
Where Sku = p.sku)

Multi-aggregate, Multi-Filter, Single Table SQL

The following simulated table contains order details where cust_nbr represents the order number. I'm trying to find where if an order contains an item_nbr 90000, I need to know if the price for 90000 is greater than the sum of the other items plus tax. I have hundreds of thousands of records in this table. I am using Teradata.
CREATE TABLE Line_Item_Details_Tbl (
cust_nbr INT,
trn_dt DATE,
str_typ VARCHAR(6),
trn_nbr INT,
item_nbr INT,
price DECIMAL(6,2),
tax DECIMAL(6,2)
);
Sample data:
INSERT INTO Line_Item_Details_Tbl VALUES
(5551, '12/22/2011', 'store', 215, 12345, 10.00, 1.25);
INSERT INTO Line_Item_Details_Tbl VALUES
(5551, '12/22/2011', 'store', 215, 65715, 6.25, 0.75);
INSERT INTO Line_Item_Details_Tbl VALUES
(5551, '12/22/2011', 'store', 215, 90000, 40.00, 0);
INSERT INTO Line_Item_Details_Tbl VALUES
(6875, '12/10/2011', 'online', 856, 72345, 8.50, 1.00);
INSERT INTO Line_Item_Details_Tbl VALUES
(6875, '12/10/2011', 'online', 856, 65715, 6.25, 0.75);
INSERT INTO Line_Item_Details_Tbl VALUES
(3500, '12/12/2011', 'store', 402, 54123, 45.00, 4.00);
INSERT INTO Line_Item_Details_Tbl VALUES
(3500, '12/12/2011', 'store', 402, 90000, 20.00, 0);
INSERT INTO Line_Item_Details_Tbl VALUES
The query should do the following:
Select cust_nbr, trn_dt, trn_nbr, sum(price + tax) as purchase
For a cust_nbr with str_typ = 'store' AND contains an item_nbr = 90000,
aggregate price + tax for all items related to cust_nbr except item_nbr 90000
So, preliminary result should be:
cust_nbr : trn_dt : trn_nbr : purchase
5551 12/22/2011 215 $18.25
3500 12/12/2011 402 $49.00
Then, for each record in the preliminary results, I need to subtract the price of item_nbr 90000 from the purchase and return results only if the purchase is less than
the price of item_nbr 90000 as net_cb
So, my ending result should be:
cust_nbr trn_dt trn_nbr net_cb
5551 12/22/2011 215 ($21.75)
Use a sub-query to identify the transaction you want, then use CASE to determin which records contribute to you aggregates or not.
SELECT
transactions.cust_nbr,
transactions.trn_dt,
transactions.trn_nbr,
sum(price + tax) AS total,
sum(CASE WHEN item_nbr = 9000 THEN 0 ELSE price + tax END) AS total_less_9000
FROM
(
SELECT
cust_nbr, trn_dt, trn_nbr
FROM
yourTable
WHERE
str_typ = 'store'
AND item_nbr = 90000
GROUP BY
cust_nbr, trn_dt, trn_nbr
)
AS transactions
INNER JOIN
yourTable
ON transactions.cust_nbr = yourTable.cust_nbr
AND transactions.trn_dt = yourTable.trn_dt
AND transactions.trn_nbr = yourTable.trn_nbr
GROUP BY
transactions.cust_nbr, transactions.trn_dt, transactions.trn_nbr
Or simply use the HAVING clause to determine which transactions to include.
SELECT
cust_nbr,
trn_dt,
trn_nbr,
sum(price + tax) AS total,
sum(CASE WHEN item_nbr = 9000 THEN 0 ELSE price + tax END) AS total_less_9000
FROM
yourTable
GROUP BY
cust_nbr,
trn_dt,
trn_nbr
HAVING
MAX(CASE WHEN item_nbr = 9000 THEN 1 ELSE 0 END) = 1
Or...
HAVING
EXISTS (SELECT * FROM yourTable AS lookup
WHERE cust_nbr = yourTable.cust_nbr
AND trn_dt = yourTable.trn_dt
AND trn_nbr = yourTable.trn_nbr
AND item_nbr = 9000
)
I've tested in on SQL Server 2005 so please do not downvote if it doesn't work at all, just let me know and I will delete my answer :-). I'm just trying to help.
Treat this as your sample data (CTE in SQL Server 2005):
;with ord_det (cust_nbr, trn_dt, str_typ, trn_nbr, item_nbr, price, tax) as (
select 5551, convert(datetime, '12/22/2011', 101), 'store', 215, 12345, 10.00, 1.25 union all
select 5551, convert(datetime, '12/22/2011', 101), 'store', 215, 65715, 6.25, 0.75 union all
select 5551, convert(datetime, '12/22/2011', 101), 'store', 215, 90000, 40.00, null union all
select 6875, convert(datetime, '12/10/2011', 101), 'online', 856, 72345, 8.50, 1.00 union all
select 6875, convert(datetime, '12/10/2011', 101), 'online', 856, 65715, 6.25, 0.75 union all
select 3500, convert(datetime, '12/12/2011', 101), 'store', 402, 54123, 45.00, 4.00 union all
select 3500, convert(datetime, '12/12/2011', 101), 'store', 402, 90000, 20.00, null
)
Final query (I assumed that your table name is ord_det, if it's not just use proper name):
select t.cust_nbr, t.trn_dt, t.trn_nbr, price - purchase as net_cb from (
select cust_nbr, trn_dt, trn_nbr, sum(price + coalesce(tax, 0)) as purchase
from ord_det o
where item_nbr <> 90000 and str_typ = 'store'
group by cust_nbr, trn_dt, trn_nbr
) t
inner join (
select cust_nbr, trn_dt, trn_nbr, price + coalesce(tax, 0) as price
from ord_det o
where item_nbr = 90000 and str_typ = 'store'
) t1 on t.cust_nbr = t1.cust_nbr
where purchase < price
Result:
cust_nbr trn_dt trn_nbr net_cb
5551 2011-12-22 00:00:00.000 215 21.75