How to subtract two values from the same column SQL - sql-server

I am building a procedure that when given a customerID it will subtract an account's type 2 (Credit card) balance from an account type 1 (Savings) balance, if there is an savings account then it subtracts the credit card balance.
(ex savings balance - credit card balance = total balance)
My table is set up like such
ID Number Balance AccountType CustomerID
-----------------------------------------------------------
1 2434789 451.23 1 1
2 2435656 1425.12 1 2
3 2434789 12.56 2 1
4 4831567 45894.23 2 2
5 8994785 500.00 2 3
6 4582165 243.10 2 4
7 7581462 1567842.21 1 3
8 2648956 1058.63 2 5
9 4582165 4865.12 1 4
10 4186545 481.56 2 6
I have tried looking this up to get some guidance but everything I have found hasn't quite helped me. If someone can explain or show me what I need to do that would be great, this is the only part of my assignment I am stuck on.

You could group by CustomerId and get the sum of saving and credit balances
select
c.CustomerId,
SUM(CASE WHEN AccountType = 1 THEN Balance ELSE 0 END) Saving,
SUM(CASE WHEN AccountType = 2 THEN Balance ELSE 0 END) Credit,
from
Customer c
group by
c.CustomerId
And then you can easily get the total with below query:
Select
CustomerId,
Saving - Credit
from
(
select
c.CustomerId,
SUM(CASE WHEN AccountType = 1 THEN Balance ELSE 0 END) Saving,
SUM(CASE WHEN AccountType = 2 THEN Balance ELSE 0 END) Credit,
from Customer c
group by c.CustomerId
) cust

You join the table to itself, where each side of the join only includes the appropriate account type records:
SELECT coalesce(s.CustomerID, cc.CustomerID) CustomerID
,coalesce(s.Number, cc.Number) Number
coalesce(s.Balance,0) - coalesce(cc.Balance,0) Balance
FROM (SELECT * FROM [accounts] WHERE AccountType = 2) s
FULL JOIN (SELECT * FROM [accounts] WHERE AccountType = 1) cc on cc.customerID = s.customerID

Related

Sql server - Using aggregate functions in where clause

I am working on a sql query for Transport business, this query when executed should get the drivers information who got more than 20% star rating(5*) rating from his customers in last 30 days... also that should be a minimum of 5 trips..
Lets say if a driver completed 100 trips in last 30 days and he received 30 star rating (5*) feedback then this Driver and all his star (5*) Trips information should be retrieved by the query..this driver has completed more than 20% 5 star trips
select tr.[TripId], tr.[DriverId], tr.[Rating], dr.[DriverName]
from tblTripInfo
left outer join tblDriver dr
on tr.[DriverId] = dr.[DriverId]
where tr.[Rating] = 5 and tr.[TripDate] >= GetDate() - 30
the above query gets all the information of trips and driver who got 5* ratings in last 30 days, i want to get only those who have minimum of 20% 5* trips out of their total trips and that should me minimum of 5 trips
Initially i wanted to get only DriverId's who met the above condition and the below query worked
select DriverId,
count(case when Rating = 5 then DriverId end) as TotalStars,
100.0 * avg(case when Rating = 5 then 1.0 else 0 end) as Average5Stars
from tblTripInfo
where TripDate >= GetDate() - 30
group by DriverId
having
count(case when Rating = 5 then DriverId end) > 10
and
100.0 * avg(case when Rating = 5 then 1.0 else 0 end) > 25
But now i want to get all the information like tripId, driverName, trip date of those 5* trips as well
You need something in the line of this:
WITH TotalTrips as (
SELECT Count() as TotalTrips,
DriverId
FROM tblTripInfo
GROUP BY DriverId
)
SELECT DriverId,
count(case when Rating = 5 then DriverId end) as Total5StarTrips,
100.0 * avg(case when Rating = 5 then 1.0 else 0 end) as Average5Stars
FROM tblTripInfo t1
JOIN TotalTrips t2
ON t1.DriverId = t2.DriverId
AND t2.TotalTrips > 5 --more than 5 trips
where TripDate >= GetDate() - 30
group by DriverId
HAVING COUNT(case when Rating = 5 then DriverId end) / t2.TotalTrips > 0.2 --more than 20% 5-starred trips
No need of complicated logic if you can use some SubQuery for simplicity.

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

Daily report by date with mssql for mutiple column

I want to display daily report like this
Fulltime Contract Casual
2018/06/04 1 0 0
2018/06/05 1 0 0
2018/06/06 0 1 1
2018/06/07 2 1 0
2018/06/08 1 1 1
2018/06/09 0 1 1
but what I have is like this
Date Jobtype Meal
2018/06/04 Fulltime 1
2018/06/05 Fulltime 1
2018/06/06 Casual 1
2018/06/06 Contract 1
2018/06/07 Casual 1
2018/06/07 Contract 2
2018/06/08 Casual 1
2018/06/08 Contract 1
2018/06/08 Fulltime 1
2018/06/09 Casual 1
2018/06/09 Contract 1
What I have tried:
select Date, Jobtype,'Meal'=(COUNT(Date))
from CanLog
where WW BETWEEN '2018/06/06' and '2018/06/09'
group by Date, Jobtype
order by 1
I think you can try this:
SELECT Date,
(SELECT COUNT(*) FROM CanLog as c WHERE c.WW = clog.WW AND jobtype = 'fulltime') AS Fulltime,
(SELECT COUNT(*) FROM CanLog as c WHERE c.WW = clog.WW AND jobtype = 'contract') AS Contract,
(SELECT COUNT(*) FROM CanLog as c WHERE c.WW = clog.WW AND jobtype = 'casual') AS Casual
FROM CanLog AS clog
WHERE WW BETWEEN '2018/06/06' AND '2018/06/09'
GROUP BY Date, Jobtype
ORDER BY Date
Select in parenthesis count how much of given string is in the day.
And if you write from small all enlarged letters, it doesnt matter, sql is case insensitive
Not sure where your WW column comes from. I assumed it's the date column. If it's not, please adjust.
You need to use UNPIVOT operator and discard records that equal to 0:
select u.date, u.jobtype, u.meal
from canlog
unpivot
(
meal
for jobtype in ( fulltime, contract, casual )
) u
where
[Date] between '2018/06/06' and '2018/06/09'
and meal <> 0;
See Live DEMO.
Result:
date jobtype meal
2018-06-04 Fulltime 1
2018-06-05 Fulltime 1
2018-06-06 Contract 1
2018-06-06 Casual 1
2018-06-07 Fulltime 2
2018-06-07 Contract 1
2018-06-08 Fulltime 1
2018-06-08 Contract 1
2018-06-08 Casual 1
2018-06-09 Contract 1
2018-06-09 Casual 1
SELECT distinct Date,
(SELECT COUNT() FROM CanLog as c WHERE c.WW = clog.WW AND jobtype = 'fulltime') AS Fulltime,
(SELECT COUNT() FROM CanLog as c WHERE c.WW = clog.WW AND jobtype = 'contract') AS Contract,
(SELECT COUNT(*) FROM CanLog as c WHERE c.WW = clog.WW AND jobtype = 'casual') AS Casual
FROM CanLog AS clog
WHERE WW BETWEEN '2018/06/06' AND '2018/06/09'
GROUP BY Date, Jobtype
ORDER BY Date

Passing values into CASE statement

and thank you all in advance for your help.
I'm trying to take the results from two separate queries and include them in a third query that has a CASE statement. I've had some success but I'm not able to present the results of the third query in the proper order. The purpose of this is to show the employee count for each department under the different managers. So far I can only load separately the manager names and their departments and employee department count totals by department. What I can't figure out is how to get the manager names in and the employee department count in for each manager row. Below are the two source queries I've used so far and the query with the CASE statement. I've also looked at UNPIVOT function with no success yet.
a) This simple query lists each primary manager name. There are also sub managers that will be returned using a hierarchy query later.
select name from employees "Boss" where employeeid in
(‘1’,'5','25','84','85');
b) This query returns the department id count for each main manager (‘1’,'5','25','84','85') as well as all sub-managers.
select departmentid, count(departmentid) COUNT from employees
where departmentid = departmentid and level <= 3
connect by prior employeeid = bossid
start with employeeid = 5
group by departmentid
order by departmentid;
c) Here’s a CASE statement that outputs exactly as desired. The problem here is the select statement currently outputs only the manager names and the manager departments into the columns. What I need to do is output both the manager names and the manager's employee department counts into the individual manager row columns. I've tried to do a separate select of the manager names to get the ‘Boss’ column and another select to include the department counts. But that got messy. Also passing the counts in a second statement would create an additional unwanted column.
select e.name "Boss",
COUNT(CASE WHEN d.departmentid = '1' THEN 1 END) AS "Finance",
COUNT(CASE WHEN d.departmentid = '2' THEN 1 END) AS "HR",
COUNT(CASE WHEN d.departmentid = '3' THEN 1 END) AS "IT",
COUNT(CASE WHEN d.departmentid = '4' THEN 1 END) AS "Marketing",
COUNT(CASE WHEN d.departmentid = '5' THEN 1 END) AS "Sales"
from employees e, departments d
where e.employeeid in (select distinct e.bossid from employees e)
and e.departmentid = d.departmentid (+)
group by e.name
order by e.name;
Boss Finance HR IT Marketing Sales
-------------------- ---------- ---------- ---------- ---------- ----------
Baxter Carney 0 0 0 0 1
Blythe Pierce 0 0 0 0 1
Here's an altered CASE query that loads the employee department counts but unfortunately it loads by department and not by individual manager. That is the problem I'm stuck on right now. How to pass the counts to the right manager and into the right column.
select departmentid "DEPTNO",
COUNT(CASE WHEN departmentid = '1' THEN 1 END) AS "Finance",
COUNT(CASE WHEN departmentid = '2' THEN 1 END) AS "HR",
COUNT(CASE WHEN departmentid = '3' THEN 1 END) AS "IT",
COUNT(CASE WHEN departmentid = '4' THEN 1 END) AS "Marketing",
COUNT(CASE WHEN departmentid = '5' THEN 1 END) AS "Sales"
from employees
where departmentid = departmentid and level <= 3
connect by prior employeeid = bossid
start with employeeid = 5
group by departmentid
order by departmentid
/
DEPTNO Finance HR IT Marketing Sales
3 0 0 1 0 0
5 0 0 0 0 21
And here's for all managers. You can see that it just keeps increasing the individual department count.
DEPTNO Finance HR IT Marketing Sales
1 4 0 0 0 0
2 0 23 0 0 0
3 0 0 20 0 0
4 0 0 0 1 0
5 0 0 0 0 28

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