Get userwise balance and first transaction date of users in SQL - 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

Related

Return latest values for each month filling empty values

In SQL Server 2017 I have a table that looks like this https://i.stack.imgur.com/Ry106.png and I would like to get the amount of members at the end of each month, filling out the blank months with the data from the previous month.
So having this table
Create table #tempCenters (
OperationId int identity (1,1) primary key,
CenterId int,
members int,
Change_date date,
Address varchar(100), --non relevant
Sales float --non relevant
)
with this data
INSERT INTO #tempCenters VALUES
(1, 100, '2020-02-20', 'non relevant column', 135135),
(1, 110, '2020-04-15', 'non relevant column', 231635),
(1, 130, '2020-04-25', 'non relevant column', 3565432),
(1, 180, '2020-09-01', 'non relevant column', 231651),
(2, 200, '2020-01-20', 'non relevant column', 321365),
(2, 106, '2020-03-20', 'non relevant column', 34534),
(2, 135, '2020-06-25', 'non relevant column', 3224),
(2, 154, '2020-06-20', 'non relevant column', 2453453)
I am expecting this result
CenterId, Members, EOM_Date
1, 100, '2020-2-28'
1, 100, '2020-3-30'
1, 130, '2020-4-31'
1, 130, '2020-5-30'
1, 130, '2020-6-31'
1, 130, '2020-7-31'
1, 130, '2020-8-30'
1, 180, '2020-9-31'
2, 200, '2020-1-31'
2, 200, '2020-2-28'
2, 106, '2020-3-31'
2, 106, '2020-4-30'
2, 106, '2020-5-31'
2, 135, '2020-6-30'
And this is what I´ve got so far
SELECT
t.centerId,
EOMONTH(t.Change_date) as endOfMonthDate,
t.members
FROM #tempCenters t
RIGHT JOIN (
SELECT
S.CenterId,
Year(S.Change_date) as dateYear,
Month(S.Change_date) as dateMonth,
Max(s.OperationId) as id
FROM #tempCenters S
GROUP BY CenterId, Year(Change_date), Month(Change_date)
) A
ON A.id = t.OperationId
which returns the values per month, but not fill the blank ones.
First I get start date (min date) and finish date (max date) for each CenterId. Then I generate all end of months from start date to finish date for each CenterId. Finally I join my subuqery (cte) with your table (on cte.CenterId = tc.CenterId AND cte.EOM_Date >= tc.Change_date) and get last (previous or same date) members value for each date (end of month).
WITH cte AS (SELECT CenterId, EOMONTH(MIN(Change_date)) AS EOM_Date, EOMONTH(MAX(Change_date)) AS finish
FROM #tempCenters
GROUP BY CenterId
UNION ALL
SELECT CenterId, EOMONTH(DATEADD(MONTH, 1, EOM_Date)), finish
FROM cte
WHERE EOM_Date < finish)
SELECT DISTINCT cte.CenterId,
FIRST_VALUE(Members) OVER(PARTITION BY cte.CenterId, cte.EOM_Date ORDER BY tc.Change_date DESC) AS Members,
cte.EOM_Date
FROM cte
LEFT JOIN #tempCenters tc ON cte.CenterId = tc.CenterId AND cte.EOM_Date >= tc.Change_date
ORDER BY CenterId, EOM_Date;
I know it looks cumbersome and I'm sure there is a more elegant solution, but still you can use a combination of subqueries with union all and outer apply to get the desired result.
Select t.CenterId, Coalesce(t.members, tt.members), t.Change_date
From (
Select CenterId, Max(members) As members, Change_date
From
(Select t.CenterId, t.members, EOMONTH(t.Change_date) As Change_date
From #tempCenters As t Inner Join
(Select CenterId, Max(Change_date) As Change_date
From #tempCenters
Group by CenterId, Year(Change_date), Month(Change_date)
) As tt On (t.CenterId=tt.CenterId And
t.Change_date=tt.Change_date)
Union All
Select t.CenterId, Null As member, t.Change_date
From (
Select tt.CenterId, EOMONTH(datefromparts(tt.[YEAR], t.[MONTH], '1')) As Change_date,
Min_Change_date, Max_Change_date
From (Select [value] as [Month] From OPENJSON('[1,2,3,4,5,6,7,8,9,10,11,12]')) As t,
(Select CenterId, Year(Change_date) As [YEAR],
Min(Change_date) As Min_Change_date, Max(Change_date) As Max_Change_date
From #tempCenters Group by CenterId, Year(Change_date)) As tt) As t
Where Change_date Between Min_Change_date And Max_Change_date) As t
Group by CenterId, Change_date) As t Outer Apply
(Select members
From #tempCenters
Where CenterId=t.CenterId And
Change_date = (Select Max(Change_date)
From #tempCenters Where CenterId=t.CenterId And Change_date<t.Change_date Group by CenterId)) As tt
Order by t.CenterId, t.Change_date

Match a row with a date that is between 2 dates in another table

I am writing a query that calculates whether the correct booking fee was charged for all bookings. The structure of the tables are below. Please note that due to the environment I am working in I cannot create any stored procedures or functions to help me.
BOOKINGS
Date date,
CustomerId int,
BookingTypeId int,
FeeCharged money
FEES
BookingTypeId int,
FeeAmount money,
EffectiveFrom date
Given the example data set below how do I query the data to ensure the fee charged per booking matches the correct fee in the fees table based on the effective from date:
try this
WITH
Fees (BookingTypeId, FeeAmount, EffectiveFrom) AS (
SELECT 1, 50, '2019-06-01'
UNION ALL SELECT 1, 55, '2019-09-01'
),
Bookings (Date, CustomerId, BookingTypeId, FeeCharged) AS (
SELECT '2019-07-01', 1, 1, 50
UNION ALL SELECT '2019-07-02', 1, 1, 50
UNION ALL SELECT '2019-10-01', 1, 1, 150
),
BookingFeeUpdate as (
SELECT
Bookings.BookingTypeId,
Bookings.Date,
MAX(EffectiveFrom) LastFeeUpdate
FROM
Bookings
LEFT JOIN Fees ON
Fees.BookingTypeId = Bookings.BookingTypeId
AND Fees.EffectiveFrom <= Bookings.Date
GROUP BY
Bookings.BookingTypeId,
Bookings.Date
)
SELECT
Bookings.*, Fees.FeeAmount as CorrectFee, Fees.EffectiveFrom, case Fees.FeeAmount when FeeCharged then 1 else 0 end Correct
FROM
Bookings
INNER JOIN BookingFeeUpdate ON
BookingFeeUpdate.BookingTypeId = Bookings.BookingTypeId
AND BookingFeeUpdate.Date = Bookings.Date
LEFT JOIN Fees ON
Fees.BookingTypeId = Bookings.BookingTypeId
AND Fees.EffectiveFrom = BookingFeeUpdate.LastFeeUpdate
--WHERE Fees.FeeAmount <> Bookings.FeeCharged

Find date of most recent overdue

I have the following problem: from the table of pays and dues, I need to find the date of the last overdue. Here is the table and data for example:
create table t (
Id int
, [date] date
, Customer varchar(6)
, Deal varchar(6)
, Currency varchar(3)
, [Sum] int
);
insert into t values
(1, '2017-12-12', '1110', '111111', 'USD', 12000)
, (2, '2017-12-25', '1110', '111111', 'USD', 5000)
, (3, '2017-12-13', '1110', '122222', 'USD', 10000)
, (4, '2018-01-13', '1110', '111111', 'USD', -10100)
, (5, '2017-11-20', '2200', '222221', 'USD', 25000)
, (6, '2017-12-20', '2200', '222221', 'USD', 20000)
, (7, '2017-12-31', '2201', '222221', 'USD', -10000)
, (8, '2017-12-29', '1110', '122222', 'USD', -10000)
, (9, '2017-11-28', '2201', '222221', 'USD', -30000);
If the value of "Sum" is positive - it means overdue has begun; if "Sum" is negative - it means someone paid on this Deal.
In the example above on Deal '122222' overdue starts at 2017-12-13 and ends on 2017-12-29, so it shouldn't be in the result.
And for the Deal '222221' the first overdue of 25000 started at 2017-11-20 was completly paid at 2017-11-28, so the last date of current overdue (we are interested in) is 2017-12-31
I've made this selection to sum up all the payments, and stuck here :(
WITH cte AS (
SELECT *,
SUM([Sum]) OVER(PARTITION BY Deal ORDER BY [Date]) AS Debt_balance
FROM t
)
Apparently i need to find (for each Deal) minimum of Dates if there is no 0 or negative Debt_balance and the next date after the last 0 balance otherwise..
Will be gratefull for any tips and ideas on the subject.
Thanks!
UPDATE
My version of solution:
WITH cte AS (
SELECT ROW_NUMBER() OVER (ORDER BY Deal, [Date]) id,
Deal, [Date], [Sum],
SUM([Sum]) OVER(PARTITION BY Deal ORDER BY [Date]) AS Debt_balance
FROM t
)
SELECT a.Deal,
SUM(a.Sum) AS NET_Debt,
isnull(max(b.date), min(a.date)),
datediff(day, isnull(max(b.date), min(a.date)), getdate())
FROM cte as a
LEFT OUTER JOIN cte AS b
ON a.Deal = b.Deal AND a.Debt_balance <= 0 AND b.Id=a.Id+1
GROUP BY a.Deal
HAVING SUM(a.Sum) > 0
I believe you are trying to use running sum and keep track of when it changes to positive, and it can change to positive multiple times and you want the last date at which it became positive. You need LAG() in addition to running sum:
WITH cte1 AS (
-- running balance column
SELECT *
, SUM([Sum]) OVER (PARTITION BY Deal ORDER BY [Date], Id) AS RunningBalance
FROM t
), cte2 AS (
-- overdue begun column - set whenever running balance changes from l.t.e. zero to g.t. zero
SELECT *
, CASE WHEN LAG(RunningBalance, 1, 0) OVER (PARTITION BY Deal ORDER BY [Date], Id) <= 0 AND RunningBalance > 0 THEN 1 END AS OverdueBegun
FROM cte1
)
-- eliminate groups that are paid i.e. sum = 0
SELECT Deal, MAX(CASE WHEN OverdueBegun = 1 THEN [Date] END) AS RecentOverdueDate
FROM cte2
GROUP BY Deal
HAVING SUM([Sum]) <> 0
Demo on db<>fiddle
You can use window functions. These can calculate intermediate values:
Last day when the sum is negative (i.e. last "good" record).
Last sum
Then you can combine these:
select deal, min(date) as last_overdue_start_date
from (select t.*,
first_value(sum) over (partition by deal order by date desc) as last_sum,
max(case when sum < 0 then date end) over (partition by deal order by date) as max_date_neg
from t
) t
where last_sum > 0 and date > max_date_neg
group by deal;
Actually, the value on the last date is not necessary. So this simplifies to:
select deal, min(date) as last_overdue_start_date
from (select t.*,
max(case when sum < 0 then date end) over (partition by deal order by date) as max_date_neg
from t
) t
where date > max_date_neg
group by deal;

How to get the first and the last record per group in SQL Server 2008?

How can a select be crafted to where the first and last row of each set of the rows are grouped with a GROUP BY.
I've this table with the following data:
id group val start end
1 10 36 465 89
2 10 35 55 11
3 10 34 20 456
4 20 38 1140 1177
5 20 22 566 788
6 20 1235 789 4796
7 20 7894 741 1067
What I need to get is the first value of the column start and last value of the column end with group by the group column.
The resultant table should be as below:
id group val start end
1 10 36 465 89
3 10 34 20 456
4 20 38 1140 1177
7 20 7894 741 1067
I did a query but with FIRST_VALUE and LAST_VALUE and over (partition by). It works in SQL Server 2012 but didn't work in SQL Server 2008. I need a query that can be executed in SQL Server 2008.
How about using ROW_NUMBER:
SQL Fiddle
WITH Cte AS(
SELECT *,
RnAsc = ROW_NUMBER() OVER(PARTITION BY [group] ORDER BY val),
RnDesc = ROW_NUMBER() OVER(PARTITION BY [group] ORDER BY val DESC)
FROM tbl
)
SELECT
id, [group], val, start, [end]
FROM Cte
WHERE
RnAsc = 1 OR RnDesc = 1
ORDER BY [group], val
This is one way -
select t.*
from tbl t
join (
select [group],
min(val) as val_1,
max(val) as val_2
from tbl
group by [group]
) v
on t.[group] = v.[group]
and (t.val = v.val_1
or t.val = v.val_2);
Fiddle: http://sqlfiddle.com/#!3/c682f/1/0
Another approach:
select id, [group], val, [start], [end]
from(
select t.*,
max(val) over(partition by [group]) as max_grp,
min(val) over(partition by [group]) as min_grp
from tbl t
) x
where val in (max_grp,min_grp)
How to two query 'UNION'
SELECT TOP 1 EmployeeId, AttendenceId, Intime
FROM EmployeeAttendence
WHERE AttendenceDate >='1/18/2020 00:00:00'
AND AttendenceDate <='1/18/2020 23:59:59'
GROUP BY EmployeeId,AttendenceId,Intime
ORDER BY AttendenceId
SELECT TOP 1 EmployeeId, AttendenceId, OutTime
FROM EmployeeAttendence
WHERE AttendenceDate >='1/18/2020 00:00:00'
AND AttendenceDate <='1/18/2020 23:59:59'
GROUP BY EmployeeId, AttendenceId, OutTime
ORDER BY AttendenceId desc
Here is a bankaccount example:
create table transactions (
id integer identity(1,1),
aDateTime datetime not null default getdate(),
OnHand float,
AmountWithDrawn float,
AmountDeposited float,
NewOnhand float
)
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('01-jan-2021 08:15', 0, 0, 1000, 1000);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('05-jan-2021 08:15', 1000, 100, 0, 900);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('06-jan-2021 08:15', 900, 200, 0, 700);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('01-feb-2021 08:15', 700, 0, 1000, 1700);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('09-feb-2021 08:15', 1700, 200, 0, 1500);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('10-feb-2021 08:15', 1500, 300, 0, 1200);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('01-mar-2021 08:15', 1200, 0, 1000, 2200);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('07-mar-2021 08:15', 2200, 400, 0, 1800);
insert into transactions (aDateTime, OnHand, AmountWithDrawn, AmountDeposited, NewOnhand) values ('11-mar-2021 08:15', 1800, 500, 0, 1300);
List:
select * from transactions order by id
Now we want to group per month:
select distinct month(adatetime) aMonth, sum(amountWithDrawn) SumWithDrawn, sum(amountDeposited) SumDeposited from transactions group by month(adatetime);
But how can we get Onhand and NewOnhand in the query?
with cte as
(select distinct month(adatetime) aMonth, sum(amountWithDrawn) SumWithDrawn, sum(amountDeposited) SumDeposited, min(id) MinId, max(id) MaxId from transactions group by month(adatetime))
select aMonth, (select Onhand from transactions where id = minid) FirstOnhand, SumWithDrawn, SumDeposited, (select NewOnhand from transactions where id = maxid) LastNewOnhand from cte
This way would be quite efficient (add index to ID) and the code quite is easy to read and comprehend.
select tt.id, tt.groups, tt.val, x.sstart, tt.[end] from test_table tt join
(Select groups,First_value(start) over (partition by groups order by groups) sstart from test_table
Union
Select groups,Last_Value(start) over (partition by groups order by groups) sstart from test_table) as x
on tt.start=x.sstart
Order by tt.groups, sstart Desc

Results of multiple queries with aggregates combined

I have 2 seperate select statements, using aggregate functions in each. I would like to be able to take the results and combine them.
table_a
id int
entered_date datetime (holds utc stamp)
balance money
group_id int
table_b
id int
entered_date date
balance money
transaction_type int
query 1:
select convert(date,entered_date), sum(balance) as earned
from table_a
where group_id in (1, 2, 3, 4)
group by convert(date,entered_Date)
query 2:
select convert(date,entered_date), sum(balance) as spent
where transaction_type = 2
group by convert(date,entered_Date)
results:
query 1:
2012-05-13, 5000
2012-05-14, 12000
...
query 2:
2012-05-13, 9000
2012-05-14, 55856
...
I would like to return one row for each record without using temp tables. The result set should have a date, then earned vs. spent. I have a report running using union to get the totals and that is fine, but i need to generate a result set with 1 record and a earned vs against line. Any help with this is appreciated.
Try:
;With AllDates AS
(
select convert(date,entered_date) As EnteredDate
from table_a
where group_id in (1, 2, 3, 4)
group by convert(date,entered_Date)
UNION
select convert(date,entered_date)
from table_b
where transaction_type = 2
group by convert(date,entered_Date)
)
, AllEarned AS (
select convert(date,entered_date) AS EnteredDate, sum(balance) as Earned
from table_a
where group_id in (1, 2, 3, 4)
group by convert(date,entered_Date)
)
, AllSpent AS (
select convert(date,entered_date) AS EnteredDate, sum(balance) as Spent
from table_b
where transaction_type = 2
group by convert(date,entered_Date)
)
SELECT
d.EnteredDate, e.Earned, s.Spent
FROM AllDates d
LEFT OUTER JOIN AllEarned e ON d.EnteredDate=e.EnteredDate
LEFT OUTER JOIN AllSpent s ON d.EnteredDate=s.EnteredDate
ORDER BY 1,2,3
You can combine these using logic, assuming that both are from the same table
(the second query is missing the from statement):
select convert(date,entered_date),
sum(case when group_id in (1, 2, 3, 4) then balance end) as earned,
sum(case when transaction_type = 2 then balance end) as spend
from table_a
group by convert(date,entered_Date)
SELECT
CASE WHEN a.a_date IS NULL THEN b.a_date ELSE a.a_date END as a_data,
a.earned,
b.spent
FROM
(select
convert(date,entered_date) as a_date,
sum(balance) as earned
from table_a
where group_id in (1, 2, 3, 4)
group by entered_Date) A
FULL OUTER JOIN
(select
convert(date,entered_date) as a_date,
sum(balance) as spent
from table_a
where transaction_type = 2
group by entered_Date) B
ON A.a_date=b.a_date
Or using FULL OUTER JOIN if there are data that don't meet both conditions. And using CASE WHEN a.a_date IS NULL THEN b.a_date ELSE a.a_date END as a_data
Assuming earned amounts are from table_a and spent amounts are from table_b,
; WITH a AS (
select entered_date=convert(date,entered_date), balance as earned, 0 AS spent
from table_a
where group_id in (1, 2, 3, 4)
UNION ALL
select entered_date=convert(date,entered_date), 0 AS earned, balance as spent
from table_b
where transaction_type = 2
)
SELECT entered_date
, earned=SUM(earned)
, spent=SUM(spent)
FROM a
GROUP BY entered_date