GROUP BY and create columns for each unique value grouped on (output as 1 row always), then use the output as a JOIN - SQL Server - sql-server

I need to GROUP data BY 2 columns (BillingId and PaymentType) - no issues with that AND have output of 1 row with columns of unique PaymentType - issue with this step. So each BillingId has only 1 row and that table will be used for joining another Tables in the Database.
Billing Table:
BillingId (unique)
12345
67890
Payment Table:
PaymentId PaymentType BillingId PaymentAmount
(unique)
12 electronic 12345 62.29
14 electronic 12345 73.28
56 electronic 12345 -62.29
6 electronic 67890 83.58
2 adjustment 67890 30.43
MY CODE:
SELECT GroupedTable.*
FROM (SELECT b.BillingId,
p.PaymentType,
SUM(p.PaymentAmount) AS AmountPaid
FROM Billing AS b
LEFT JOIN Payment AS p
ON (b.BillingId = p.BillingId)
GROUP BY b.BillingId, p.PaymentType) AS GroupedTable
OUTPUT (obviously incorrect):
BillingId PaymentType AmountPaid
67890 electronic 83.58
12345 electronic 73.28
67890 adjustment 30.43
OUTPUT I NEED:
BillingId AmountPaid AmountAdjusted
(electronic) (adjustment)
67890 83.58 30.43
12345 73.28 0

It looks easier if you use Case When expressions as follows:
Select B.BillingId, Sum(Case When P.PaymentType='electronic' Then P.PaymentAmount End) As [AmountPaid (electronic)],
Sum(Case When P.PaymentType='adjustment' Then P.PaymentAmount End) As [AmountAdjusted (adjustment)]
From Billing As B Left Join Payment As P On (B.BillingId=P.BillingId)
Group by B.BillingId
db<>fiddle
BillingId
AmountPaid (electronic)
AmountAdjusted (adjustment)
12345
73,28
NULL
67890
83,58
30,43

You should group by BillingId only and use conditional aggregation:
SELECT b.BillingId,
SUM(CASE WHEN p.PaymentType = 'electronic' THEN p.PaymentAmount ELSE 0 END) AS AmountPaid,
SUM(CASE WHEN p.PaymentType = 'adjustment' THEN p.PaymentAmount ELSE 0 END) AS AmountAdjusted
FROM Billing AS b LEFT JOIN Payment AS p
ON b.BillingId = p.BillingId
GROUP BY b.BillingId;
See the demo.

Related

SQL Pivot / Case Query based on Row Value

Problem
Using SQL Server, I'm trying to pivot data based on values in a column. I want to move Bob and John's value column over if Salary is in the metric column.
Sample data:
Person table
Person ID
-------------
Bob 1
Bob 1
John 2
John 2
Value table
Metric Value ID
---------------------
Age 52 1
Salary 60000 1
Age 45 2
Salary 55000 2
Expected output
My goal is to pivot the table if salary is present in the Metric column.
Person Metric Value Salary ID
---------------------------------------
Bob Age 52 60000 1
John Age 45 55000 2
Current code:
SELECT *
FROM person_table pt, value_table vb
WHERE pt.id = vb.id
AND vb.metric IN ('Age', 'Salary')
Use the following pivot query:
SELECT
pt.Person,
'Age' AS Metric,
MAX(CASE WHEN vb.Metric = 'Age' THEN vb.Value END) AS Value,
MAX(CASE WHEN vb.Metric = 'Salary' THEN vb.Value END) AS Salary,
pt.ID
FROM person_table pt
INNER JOIN value_table vb
ON pt.id = vb.id
GROUP BY
pt.Person,
pt.ID
ORDER BY
pt.ID;

Joined tables - how to aggregate problem (sum)?

I have two tables joined like this:
SELECT count(DISTINCT T1.ContractNumber) AS nr_of_contracts,
count(T3.DateofInstallmentPayment) AS nr_of_paid_installents,
count(T3.DateofDueInstallment) AS nr_of_installments,
sum(T1.DisbursementAmount) AS disbursed_amount
FROM q.T1
LEFT JOIN q.T3
ON T1.ContractNumber=T3.ContractNumber
WHERE DateOfDisbursement BETWEEN '2019-12-01' AND '2019-12-31'
AND T3.DateofDueInstallment < GETDATE()
where T1 table contains data about clients (per contract number) and T3 about their payment schedules (per every instalment).
What I want is to have paid off amount (disbursement amount) of contracts from table T1 (aggregated by contract number) and not by every instalment. When I tried to select just sum(T1.Disbursement amount) then I receive sum but for all instalment which is incorrect.
T1:
Contract Number
DisbursementDate
Disbursement Amount
1
2019-12-01
1000
2
2019-12-01
2000
3
2019-12-01
3000
T3:
Contract Number
DateofDueInstallment
DateofInstallmentPayment
1
2020-01-01
2020-01-01
1
2020-02-01
2020-02-06
1
2020-03-01
2020-04-01
What I get after joining two tables for Contract Number = 1 is sum(DisbursementAmount) = 3000.
Contract Number
sum(DisbursementAmount)
1
3000
What I want after joining two tables for Contract Number = 1 is sum(DisbursementAmount) = 1000.
Contract Number
sum(DisbursementAmount)
1
1000
Something like this,not tested - a subquery with a different aggregation column
SELECT T1.product, T1.NrOfInstallment, count(DISTINCT T1.ContractNumber),
SELECT paid_amound FROM(SELECT ContractNumber, sum(tt.DisbursementAmount + (tt.ContractNumber*0.01))
- sum(tt.ContractNumber*0.01) as paid_amount
FROM abc.T1 as tt WHERE tt.ContractNumber = T1.ContractNumber GROUP BY ContractNumber) AS t)
FROM abc.T1
LEFT JOIN bde.T3
ON T1.ContractNumber=T3.ContractNumber
GROUP BY T1.product, T1.NrOfInstallment

Sql Server - Get SUM() of values for only Active Users

I have a requirement where i need to get Total Active Employees and Total Sales by RegionId
My query result should be like below.
RegionId | TotalEmployees | TotalSales | Average
1 10 100 10
2 3 15 5
My front end application will pass all the RegionIds as a single string separated by a comma, my query parameter is of type VARCHAR() and the Input paramter will look like '1,2,3,4,7,14,26' and there can be upto 20 Region Ids in a single string separated by a comma.
SELECT E.[RegionId] as RegionId
,COUNT(E.[EmployeeId) AS TotalEmployees
,(SELECT SUM([Sale])
FROM dbo.[Sales]
WHERE RegionId = R.[RegionId]
) AS TotalSales
,TotalSales/TotalEmployees AS Average
FROM dbo.[Employee]
JOIN [dbo].[ufn_StringSplit](#RegionIdCollection, ',') RegionId
ON E.RegionId = CAST(RegionId.[Data] AS Varchar(5000))
WHERE E.[Active] = 1
GROUP BY E.[RegionId]
My Employee table structures look alike below
EmployeeId | Name | RegionId | Active
100 Tom 2 1
101 Jim 4 0
103 Ben 2 1
Sales Table
SaleId | EmployeeId| RegionId | Sale
1 100 2 3500
2 101 4 2000
3 100 2 1500
Now my issue is when i am getting TotalSales the below query gets all the sales by RegionId, but i need to get All the sales done by only current Active employees in the Employee table
(SELECT SUM([Sale])
FROM dbo.[Sales]
WHERE RegionId = R.[RegionId]
) AS TotalSales
There is no reason to use a sub-select to find the sum of sales here, that will result in running that query for each and every row. You want to aproach this in a set based way which means you need to join and group appropriately:
with s as
(
select e.RegionId
,e.EmployeeId
,sum(s.Sale) as EmployeeSales
from dbo.ufn_StringSplit(#RegionIdCollection, ',') as r
join dbo.Employee as e
on r.RegionId = CAST(r.[Data] AS varchar(20)) -- Do you really need 5000 characters here?
left join dbo.Sales as s
on r.RegionId = s.RegionId
and e.EmployeeId = s.EmployeeId
where e.Active = 1
group by e.RegionId
,e.EmployeeId
)
select s.RegionId
,count(s.EmployeeId) as TotalEmployees
,sum(s.EmployeeSales) as TotalSales
,sum(s.EmployeeSales)/count(s.EmployeeId) as Average
from s
group by s.RegionId

Go through each row of a table, do a calculation on that row, insert in temp table and use temp table for next row

Problem:
We have a transaction table. All records in that table have one of the following transaction types: wallet deposit (payments), wallet withdrawal (sales) and cashback (discount to be used for future sales). I want to add a additional column to each row displaying the cashback balance. Cashback is either used for discount on new sales or to reduce negative overall balance.
transaction table:
customer (int)
transaction_date (date)
amount (int)
transaction_type (varchar(25))
I've tried using the lag function to get the value of the previous row and use that for the calculation in the current row. But that doesn't always work because the lag function looks back to the row it's specificly pointed to.
using lag function in calculation:
case when
isnull(lag(balance_cashback) over (partition by client_id order by transaction_date), 0)
+ case when type = "cashback" then amount else 0 end
+ case when type = "revenu" and amount < 0 then amount else 0 end
<= 0 then 0
else
lag(balance_cashback) over (partition by client_id order by transaction_date)
+ case when type = "cashback" then amount else 0 end
+ case when type = "revenu" and amount < 0 then amount else 0 end
end
Searching the internet I think I should be using a loop or maybe a cursor?
Idea:
The idea is to use the transaction table and add two rownumber columns. A rownumber for all rows in the transaction table I want to loop through. And a second rownumber on all transactions of each clients. The next step seems to create an empty balance table with fields rownumclient, client_id, overall_balance and cashback_balance.
calculation of rownumber colums:
row_number () over (order by client_id, transaction_date) as rownumber_all
row_number () over (partition by client_id order by client_id, transaction_date) as rownumber_client
transaction table with rownumbers:
rownumber_all (int)
rownumber_client (int)
client (int)
transaction_date (date)
amount (int)
transaction_type (varchar(25))
balance table:
rownumber_client (int)
client_id (int)
overall_balance (int)
cashback_balance (int)
example transaction table with rownumbers:
rownumbwr_all | rownumber_client | client_id | transaction_date | amount | transaction_type
1 1 123 2018-10-12 10 wallet deposit
2 2 123 2018-10-27 5 cashback
3 3 123 2018-11-03 -2,5 wallet withdrawal
4 4 123 2018-11-13 -5 wallet withdrawal
5 5 123 2018-12-18 10 wallet deposit
6 6 123 2018-12-19 20 wallet deposit
7 7 123 2018-12-21 5 cashback
8 1 456 2018-10-11 -45 wallet withdrawal
9 2 456 2018-10-23 5 cashback
10 3 456 2018-11-01 5 cashback
11 4 456 2018-11-04 10 wallet deposit
Etc.
With the additional rownumber columns and new balance table in place, I have to create a loop through all rows in the transaction table. Using the column rownumber_all to start with the first one. The newly created balance table is used to calculate the cashback balance in the current row. We use this table with a left join to the transaction tabel with the rownumber columns. When we loop through the first row, the balance table is empty, but from the second row on there is a calculated cashback balance from the previous row.
select statement for calculating current cashback balance:
select
t1.rownumall,
t1.rownumclient,
t1.client_id,
t2.overall_balance + t1.amount as overall_balance,
case
when (t2.overall_balance + case when t1.type = 'cashback' then t1.amount else 0 end) < 0 then 0
when t1.type in (sales, cashback) then amount
else null
end + t2.cashback_balance as cashback_balance
/*insert into balance*/
from
transactions as t1
left join cashback as t2 on t2.client_id = t1.client_id and t2.rownumber_client = t1.rownumber_client-1
For each row that is looped through the result of the select statement above should be inserted into the balance table as long as there are transaction records available. And as said before cashback balance is either used for discount on new sales or to reduce negative overall balance. That said the expected result I'm looking for is as follows and cashback_balance is the most important field.
expected transaction table with balances:
client_id | transaction_date | amount | transaction_type | overall_balance | cashback balance
123 2018-10-12 10 wallet deposit 10 0
123 2018-10-27 5 cashback 15 5
123 2018-11-03 -2,5 wallet withdrawal 12,5 2,5
123 2018-11-13 -5 wallet withdrawal 7,5 0
123 2018-12-18 10 wallet deposit 17,5 0
123 2018-12-19 20 wallet deposit 37,5 0
123 2018-12-21 5 cashback 42,5 5
456 2018-10-11 -45 wallet withdrawal -2,5 0
456 2018-10-23 5 cashback 2,5 2,5
456 2018-11-01 5 cashback 7,5 7,5
456 2018-11-04 10 wallet deposit 17,5 7,5
Etc.
I tried to explain as much as possible and hope the idea and expected result is clear. I can't imagine that what I need hasn't been done before, but I just can't seem to find the specific use case anywhere.
So which SQL expert will be kind enough to tell me in plain english how this can be achieved bu using a loop, cursor or any other way? Any help would be highly appreciated. If any clarification is needed, please let me know.
Are you looking for a running total like a bank statement. This query does that
SELECT
client_id,
Transaction_Date,
Trans_Type,
Amount,
Balance = SUM(CASE WHEN Trans_Type = 'Cash back' Then Amount ELSE 0 END) OVER(ORDER BY RowNumber)
FROM
(
SELECT
client_id,
Transaction_Date,
Trans_Type,
Amount,
ROW_NUMBER() OVER(ORDER BY Client_id) AS RowN
FROM temp.dbo.Trans_Table) RC
GROUP BY client_id, Trans_Type, Transaction_Date, Amount, RowN
Sample Data
After searching and some trial and error I've found a way to loop through all the rows and calculate the correct cashback balance for each row. I want to thank all the people who tried to help me and Jorge E. Hernández for the solution to my "loop problem".
Here the final code I used.
-- declare the start and end variable
declare
#counter int = 1,
#max_rownumber int = (select max(rownumber_all) as max_rownumber from dbo.transactions)
-- loop
while #counter <= #max_rownumber
begin
-- calculate overall_balance and cashback_balance for each row in the transactions table filtered by the rownumber_all field
insert into dbo.transactions_enriched
select
t1.rownumber_client as rownumber
, t1.client_id
, t1.transaction_date
, t1.amount
, t1.transaction_type
, t1.payment_method
, isnull(t2.overall_balance ,0) + t1.amount as overall_balance
, case
when t1.transaction_type = 'cashback' and isnull(t2.overall_balance, 0) >= 0 then isnull(t2.cashback_balance, 0) + t1.amount
when (case when t1.transaction_type = 'revenue' and t1.amount < 0 then t1.amount else 0 end) + isnull(t2.cashback_balance, 0) <= 0 then 0
else (case when t1.transaction_type = 'revenue' and t1.amount < 0 then t1.amount else 0 end) + isnull(t2.cashback_balance, 0)
end as cashback_balance
from
dbo.transactions as t1
left join dbo.transactions_enriched as t2 on t2.client_id = t1.client_id and t2.rownumber_client = t1.rownumber_client - 1
where
t1.rownumber_all = #counter
-- update the counter by adding 1
set #counter = #counter + 1
end

Count from a Count based on a condition in SQL server

I have 4 tables in my database.
Students (Idno, Name, CourseId)
Sample data:
Idno Name CourseId
------------------------
-101123456 Vijay 101
-101123457 John 102
-101123458 Sam 101
-101123459 Arvind 102
-101123460 Smith 101
Courses (CourseId, CourseNo, CourseName, StreamId)
Sample data:
CourseId CourseNo CourseName StreamId
------------------------------------------
-101 53245 C 1
-102 53245 C++ 2
Streams (StreamId, StreamName)
Sample data:
StreamId StreamName
---------------------------
-1 Engineering
-2 Medical
Booking (BId, Idno, BStatus)
Sample data:
Bid Idno BStatus
--------------------------------
-1110 101123456 Confirmed
-1111 101123456 Confirmed
-1112 101123457 Confirmed
-1113 101123458 Confirmed
-1114 101123459 Confirmed
-1115 101123460 Confirmed
-1116 101123456 Confirmed
-1117 101123457 Confirmed
-1118 101123458 Confirmed
-1119 101123459 Confirmed
-1119 101123460 Cancelled
I have a problem generating the following output
SNo Stream BookedOnce BookedTwice NonBooked
1 Engineering 2 3 0
2 Medical 3 1 1
Thanks
I think this requires a two step process. First, calculate the number of bookings by each student for a stream. Then per stream, count the number of students that have one, two or zero bookings.
Here's an example, with the first step in the inner query:
select StreamId
, StreamName
, sum(case when Bookings = 1 then 1 else 0 end) as BookedOnce
, sum(case when Bookings = 2 then 1 else 0 end) as BookedTwice
, sum(case when Bookings = 0 then 1 else 0 end) as NoneBooked
from (
select str.StreamId
, str.StreamName
, s.Idno
, count(b.BId) as Bookings
from Students s
left join
Booking b
on b.Idno = s.Idno
left join
Courses c
on c.CourseId = s.CourseId
left join
Streams str
on str.StreamId = c.StreamId
group by
str.StreamId
, str.StreamName
, s.Idno
) BookingsPerStudentPerStream
group by
StreamId
, StreamName

Resources