Combine multiple SQL queries into single result - sql-server

I'm trying to get the result of 2 queries into a single result set. I'm using SQL Server 2019 Express.
Here is the data I'm working with:
Table Sales
SaleDate
SaleAmt
CustomerID
11/1/2021
500
123
11/1/2021
100
234
11/1/2021
300
345
11/2/2021
500
456
11/2/2021
100
567
11/2/2021
200
678
Table Customers
CustomerID
CustomerName
123
Jon Doe
234
Jane Doe
456
Bob Doe
678
Jim Doe
Query #1:
select sales.saledate, sum(sales.saleamt) as 'Total Sales from All'
from Sales
group by sales.saledate
Query #2:
select sales.saledate, sum(sales.saleamt) as 'Total Sales from Customers'
from Sales
where sales.customerid in (select customerid from customers)
group by sales.saledate
This is my desired result:
SaleDate
Total Sales from All
Total Sales from Customers
11/1/2021
900
600
11/2/2021
800
700

you can use join on the date of the sale
select s1.saledate, All_Total AS 'Total Sales from All', CustomersTotal as 'Total Sales from Customers'
from (
select sales.saledate, sum(sales.saleamt) as All_Total
from Sales
group by sales.saledate
) s1
inner join
(
select sales.saledate, sum(sales.saleamt) as CustomersTotal
from Sales
where sales.customerid in (select customerid from customers)
group by sales.saledate
) s2 on s1.saledate = s2.saledate

you can combine it in one single query using case expression.
select s.saledate,
sum(s.saleamt) as [Total Sales from All],
sum(case when exists
(
select *
from customers c
where c.customerid = s.customerid
)
then s.salesamt
end) as [Total Sales from Customers]
from Sales s
group by s.saledate

You can use a LEFT JOIN with conditional aggregation
select
s.saledate,
sum(s.saleamt) as [Total Sales from All],
sum(case when c.customerid is not null then s.saleamt end) as [Total Sales from Customers]
from Sales s
left join customers c on s.customerid = c.customerid
group by s.saledate;

Related

Update gaps in sequential table

I have a table that contains employee bank data
Employee |Bank |Date |Delta
---------------------------------------------------
Smith |Vacation |2023-01-01 |15.0
Smith |Vacation |2023-01-02 |Null
Smith |Vacation |2023-01-03 |Null
Smith |Vacation |2023-01-04 |7.5
I would like to write a statement so that I can update 2023-01-02 and 2023-01-03 with the Delta value from January 1. Essentially, I want to use the value from the most recent row that isn't > than the date on the row.
Once complete, I want the table to look like this:
Employee |Bank |Date |Delta
---------------------------------------------------
Smith |Vacation |2023-01-01 |15.0
Smith |Vacation |2023-01-02 |15.0
Smith |Vacation |2023-01-03 |15.0
Smith |Vacation |2023-01-04 |7.5
The source table has a unique index consisting of Employee, Bank and Date descending. There could be up to 2 billion rows in the table.
I currently update the table with the following, but I am wondering if there is a more efficient way to do so?
WITH cte_date
AS (SELECT dd.date_key,
db.balance_key,
feb.employee_key
FROM shared.dim_date dd
CROSS JOIN
(
SELECT DISTINCT
employee_key
FROM wfms.fact_employee_balance
) feb
CROSS JOIN wfms.dim_balance db
WHERE dd.date BETWEEN DATEFROMPARTS(DATEPART(YY, GETDATE()) - 2, 12, 31) AND GETDATE())
SELECT dd.*,
t.delta
INTO wfms.test2
FROM cte_date dd
LEFT JOIN wfms.test1 t ON dd.balance_key = t.balance_key
AND dd.employee_key = t.employee_key
AND t.date_key = (SELECT TOP 1 tt1.date_key
FROM wfms.test1 tt1
WHERE tt1.balance_key = t.balance_key
AND tt1.employee_key = t.employee_key
AND tt1.date_key < dd.date_key);
Just for fun, I wanted to test an idea.
For the moment, lets assume the gaps are not too wide ... In this example 7 days.
On a relative to batch, the lag() over() approach was 22% while the Cross Apply was 78%.
Again, Just for fun
Select Employee
,Bank
,Date
,Delta = coalesce(A.Delta
,lag(Delta,1) over (partition by Employee,Bank order by date)
,lag(Delta,2) over (partition by Employee,Bank order by date)
,lag(Delta,3) over (partition by Employee,Bank order by date)
,lag(Delta,4) over (partition by Employee,Bank order by date)
,lag(Delta,5) over (partition by Employee,Bank order by date)
,lag(Delta,6) over (partition by Employee,Bank order by date)
,lag(Delta,7) over (partition by Employee,Bank order by date)
)
From YourTable A
Versus
Select Employee
,Bank
,Date
,Delta = coalesce(A.Delta,B.Delta)
From YourTable A
Cross Apply ( Select top 1 Delta
From YourTable
Where Employee=A.Employee
and A.Bank = Bank
and Delta is not null
and A.Date>=Date
Order By Date desc
) B
Update
Same results with 20 days
Here is another way. Using sum() with window function to find the group "Grp" of rows (1 row with not null with subsequent rows of null). Finally max(Delta) of the Grp to return the not null value.
select Employee, Bank, [Date], max (max(Delta))
over (partition by Employee, Bank, Grp)
from
(
select *, Grp = sum (case when Delta is not null then 1 else 0 end)
over (partition by Employee,Bank
order by [Date])
from YourTable
) t
group by Employee, Bank, [Date], Grp

SQL Server - Group last paid price by customer and product code

I am trying to write a query that will do the following.
I have a table with separate sales order lines on it. Each line details the customer, product sold, the price sold at, and the date of the sale.
I am trying to establish for each product code, what the last price we sold it for was for each separate customer.
For example using my input below I would expect Product code ABC to return '10' for Brian, '20' for Gary, and '50 for Sam.
Below is complete set of results I would expect for all product codes.
Input
Order No Customer Product Code Price Date
-----------------------------------------------------------
1 Brian ABC 10 12/04/2018
2 Brian ABC 14 01/04/2018
3 Gary ABC 20 12/04/2018
4 Gary ABC 35 12/04/2017
5 Sam ABD 40 06/08/2017
6 Sam ABC 50 20/08/2017
7 Adam ABE 20 15/06/2016
8 Adam ABE 30 17/03/2017
Output
Order No Customer Product Code Price Date 1 Brian ABC 10 12/04/2018 3 Gary ABC 20 12/04/2018 6 Sam ABC 50 20/08/2017 5 Sam ABD 40 06/08/2017 8 Adam ABE 30 17/03/2017
You can you Row_number() with partition BY [product code], [customer] for this.
Following query should work for you scenario
SELECT *
FROM (SELECT [order no],
[customer],
[product code],
[price],
[date],
Row_number()
OVER(
partition BY [product code], [customer]
ORDER BY [date] DESC) AS RN
FROM [table]) T
WHERE T.rn = 1
This is all about the row_number():
Allows you to partition (Similar to group) and provide an order
select top 1 with ties *
from table
order by row_number() over (partition by [Product Code],Customer order by date desc)
I think this is what you are looking for:
select a.* from #temp a join (
select customer, [product code], max(Date) maxdate
from #temp
group by customer, [product code])b
on a.customer=b.customer and a.date=b.maxdate and a.[product code]=b.[product code]

Get all funds which has at least minimum data points

I have two tables
1) Fund details
ID Symbol
-------------------
1 ABC
2 XYZ
2) Fund Price data
Fund_id date Price
-------------------------------------------
1 2014-07-01 00:00:00.000 25.25
1 2014-07-02 00:00:00.000 25.45
......
2 2014-07-01 00:00:00.000 75.25
2 2014-07-02 00:00:00.000 75.42
.......
Now what I want to achieve is:
Here I am fetching the monthly data of a particular Fund as below:
SELECT YEAR(date) [Year], MONTH(date) [Month],
DATENAME(MONTH,date) [Month Name], COUNT(1) [Sales Count], F.Symbol
FROM FundData FD inner join FundDetails F on F.ID = FD.Fund_ID
where F.Symbol = 'ABC'
GROUP BY YEAR(date), MONTH(date), DATENAME(MONTH, date), F.Symbol
Output:
Year Month Month Name Sales Count Symbol
-------------------------------------------
2014 4 April 21 ABC
2014 5 May 21 ABC
2014 6 June 21 ABC
2014 7 July 3 ABC
.......
Total Rows: 301
So here this is only for only particular fund which has returned 301 rows.
Now I want to get all the funds from the Fund details table which has rows less than given count ex 216 which I will pass as a parameter
Use Following query:
Declare #YourParameter int = 10
SELECT YEAR(date) [Year],
MONTH(date) [Month],
DATENAME(MONTH,date) [Month Name],
COUNT(1) [Sales Count],
F.Symbol
FROM FundData FD
INNER JOIN FundDetails F on FD.ID = F.Fund_ID
Where FD.ID IN (SELECT z.Fund_ID
FROM FundDetails z
WHERE z.Fund_ID=FD.ID
GROUP BY z.Fund_ID, YEAR(z.date), MONTH(z.date)
HAVING COUNT(*) <= #YourParameter
)
GROUP BY YEAR(date), MONTH(date), DATENAME(MONTH, date), F.Symbol
I have fixed it:
Declare #YourParameter int = 110
WITH CTE AS
(
SELECT YEAR(date) [Year], MONTH(date) [Month],
DATENAME(MONTH,date) [Month Name], COUNT(1) [Sales Count], F.Symbol
FROM FundData FD inner join FundDetails F on F.ID = FD.Fund_ID
where F.ID
IN (SELECT z.ID FROM FundDetails z)
GROUP BY F.Symbol, YEAR(date), MONTH(date), DATENAME(MONTH, date)
)
SELECT Symbol, COUNT(*) as cnt FROM CTE
GROUP BY Symbol
having COUNT(*) >= #YourParameter

SQL Server getting values from 4 tables into ONE

I have 5 Tables :
1- UserAccount : Has AccountNumber field
2- Total Amount : Has AccountNumber field
3- Total Withdrawal : Has AccountNumber field
4- Total Profit : Has AccountNumber field
5- Balance : Has AccountNumber field
I need to get the values of [Total Amount], [Total Withdrawal], [Total Profit] & [Balance] for each user in ONE table to be like:
AccountNumber Balance Total Amount Total Withdrawal Total Profit
----------------------------------------------------------------------------------
201 450 600 150 250
222 600 800 200 150
I used this query but the results isn't correct:
SELECT DISTINCT
dbo.useraccount.AccountNumber,
dbo.useraccount.FirstName,
dbo.useraccount.SecondName,
dbo.Balance.Balance,
sum(dbo.[Total Amount].DepositTotal) AS [Total Deposit],
Sum(dbo.[Total Profit].ProfitTotal) AS [Total Profit],
Sum(dbo.[Total Withdrawal].WithdrawalTotal) AS [Total Withdrawal]
FROM
dbo.useraccount ,
dbo.Balance ,
dbo.[Total Amount] ,
dbo.[Total Profit] ,
dbo.[Total Withdrawal]
WHERE
dbo.useraccount.AccountNumber = dbo.Balance.AccountNumber AND
dbo.useraccount.AccountNumber = dbo.[Total Amount].AccountNumber AND
dbo.useraccount.AccountNumber = dbo.[Total Profit].AccountNumber AND
dbo.useraccount.AccountNumber = dbo.[Total Withdrawal].AccountNumber
GROUP BY
dbo.useraccount.AccountNumber,
dbo.useraccount.FirstName,
dbo.useraccount.SecondName,
dbo.Balance.Balance
Any help please?
The results i am getting is something like this:
AccountNumber Balance Total Amount Total Withdrawal Total Profit
-----------------------------------------------------------------------
201 5316.52 291060.00 86328.20 51150.00
220 35000.00 2086400.00 648000.00 532800.00
221 12000.00 192000.00 44548.00 44548.00
222 8500.00 76500.00 12003.75 12003.75
224 4000.00 484000.00 120780.00 120780.00
226 2393.50 48000.00 5206.50 10736.00
When i run this query for [Total Profit] i get correct results:
SELECT DISTINCT
dbo.[Total Profit].AccountNumber,
Sum(dbo.[Total Profit].ProfitTotal) As [Total Deposit]
FROM
dbo.[Total Profit]
GROUP BY
dbo.[Total Profit].AccountNumber
Well, given that you haven't posted your tables definition, you can do this to avoid the duplication of rows:
SELECT UA.AccountNumber,
UA.FirstName,
UA.SecondName,
B.Balance,
TA.DepositTotal [Total Deposit],
TP.ProfitTotal [Total Profit],
TW.WithdrawalTotal [Total Withdrawal]
FROM dbo.useraccount UA
LEFT JOIN ( SELECT AccountNumber, SUM(Balance) Balance
FROM dbo.Balance
GROUP BY AccountNumber) B
ON UA.AccountNumber = B.AccountNumber
LEFT JOIN ( SELECT AccountNumber, SUM(DepositTotal) DepositTotal
FROM dbo.[Total Amount]
GROUP BY AccountNumber) TA
ON UA.AccountNumber = TA.AccountNumber
LEFT JOIN ( SELECT AccountNumber, SUM(ProfitTotal) ProfitTotal
FROM dbo.[Total Profit]
GROUP BY AccountNumber) TP
ON UA.AccountNumber = TP.AccountNumber
LEFT JOIN ( SELECT AccountNumber, SUM(WithdrawalTotal) WithdrawalTotal
FROM dbo.[Total Withdrawal]
GROUP BY AccountNumber) TW
ON UA.AccountNumber = TW.AccountNumber
Of course, you shouldn't need to do that aggregation for every table, only the ones that have multiple rows for each AccountNumber.

LEFT OUTER JOIN (SELECT * FROM TABLE) is this possible?

I have two tables Person and Salary.
Person:
PersonId | Name | Surname
--------------------------------
1 John Deer
2 Mark Bear
Salary:
SId | PersonId | Date | Salary
----------------------------------------------------
1 2 2013-01-01 00:00:00.000 100
2 2 2012-01-01 00:00:00.000 90
3 2 2011-01-01 00:00:00.000 80
What I am trying to do is, if a person has a salary record then it should display the most current salary info in the results, if no salary record then it should display the salary info as null, which is like...
Result
------------------------------------------------------------------------
PersonId | Name | Surname | Date | Salary
1 John Deer NULL NULL
2 Mark Bear 2013-01-01 00:00:00.000 100
I know it has to be something like this but with lack of knowledge I just couldn't achieve..
SELECT
P.PersonId, P.Name, P.Surname, SL.Date, SL.Salary
FROM
PERSON P
LEFT OUTER JOIN
(SELECT TOP 1 S.PersonId, S.Date, S.Salary
FROM Salary
WHERE S.PersonId = P.PersonId ORDER BY Date DESC) SL
I would start by ranking the salaries by person and date with a CTE and the ROW_NUMBER() function. This will put the most recent salary by person in descending order in the first position, which we can filter for later (where rank = 1). After that, it becomes a simple LEFT JOIN from Person to the aliased CTE:
WITH RankedSalaries AS
(
SELECT
PersonId
,Date
,Salary
,ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY Date DESC) AS RowNum
FROM
Salary
)
SELECT
p.PersonId
,p.Name
,p.Surname
,s.Date
,s.Salary
FROM
Person p
LEFT JOIN
RankedSalaries s
ON
p.PersonId = s.PersonId
WHERE
s.RowNum = 1
Alternatively, you could take the contents of the CTE and move it in between the parenthesis of the query you started (i.e. LEFT JOIN (<CTE query>)). Just remember to add the = 1 constraint.

Resources