Querying a log table with nonlinear data - sql

I'm trying to run some queries on a log-style table, which contains a bunch of nonlinear data. I have the following schema:
Signouts
+------------+----------------+------------+----------+
| signout_id | environment_id | date_start | date_end |
+------------+----------------+------------+----------+
| int | int | datetime | datetime |
+------------+----------------+------------+----------+
Environments
+-----+---------+
| id | name |
+-----+---------+
| int | varchar |
+-----+---------+
Signouts is the log table (and I say "log table" because records are never updated, only marked as "disabled" and added anew). When a user signs out an environment, their chosen start and end time is entered into the signouts table. Currently, to see if an environment is signed out, I simply check if the current date falls between date_start and date_end. If another user wants to sign out that environment, the minimum time they can choose is the ending date of the current signout.
I have a new challenge now, though. I now need to implement a reservation system. All of a sudden, dates can be anywhere in the future, and an environment can be reserved at any time. Now I need to know when an environment can still be signed out, and what those minimum (and now maximum) values are!
I've gotten it down to this naive plan, but I'm having trouble getting it into SQL:
get all signouts where start < curdate & end > curdate
if there is no current signout, get the min start of all signouts where start > curdate
if there is a signout, get the max end
Here is the closest I've gotten, among many other scrapped queries:
SELECT s.date_start_unavailable, s.date_available, e.id AS environment_id
FROM Environments AS e
LEFT OUTER JOIN (
SELECT TOP (100) PERCENT signout_id, environment_id, username, date_start, date_end, project, notes, in_use, max(date_end) as date_available, min(date_start) as date_start_unavailable
FROM dbo.Signouts
WHERE date_end >= GETDATE()
GROUP BY signout_id, environment_id, username, date_start, date_end, project, notes, in_use
ORDER BY date_start DESC
) AS s ON s.environment_id = e.id
This almost works. date_start_unavailable is the time at which the system becomes unavailable for a signout, and dave_available is the time when there are no more signouts. This still has problems, however; someone could reserve an environment years into the future for a month, and normal users wouldn't be able to see that most of the time is unallocated. I'll have to find a way to restrict this, but I can worry about that later.
The signouts last for arbitrary, user-entered amounts of time, otherwise implementing a time-block system would be trivial. If anybody can offer some DBA wisdom, it would be much appreciated!

Setting up my test-environment like this:
create table environment (id int, name varchar(255));
insert into environment values (1, 'DVD');
insert into environment values (2, 'BluRay');
create table signout (id int, environment_id int, date_start date, date_end date);
insert into signout values (1, 1, '01.11.2015', '09.11.2015');
insert into signout values (2, 1, '10.11.2015', '12.11.2015');
insert into signout values (3, 1, '01.12.2015', '24.12.2015');
insert into signout values (4, 2, '01.12.2015', '02.12.2015');
insert into signout values (5, 2, '04.12.2015', '07.12.2015');
insert into signout values (6, 2, '11.12.2015', '13.12.2015');
insert into signout values (7, 2, '14.12.2015', '23.12.2015');
Now, selecting the booked times is trivial:
select e.name, s.date_start d_start, s.date_end d_end, 'booked' as d_status FROM
signout s inner join environment e ON e.id = s.environment_id
Bu what about free times? These would be the times where no booking exists - so you join the table with itself in a given order:
select e.name, dateadd(DAY, 1, s.date_end) d_start,
COALESCE(dateadd(day, -1, s2.date_start), '31.12.2025') d_end, 'free'
FROM signout s
OUTER APPLY (
SELECT TOP 1 date_start, date_end from signout sx
WHERE sx.environment_id = s.environment_id
AND sx.date_start > s.date_end ORDER BY sx.date_start
) s2
inner join environment e ON e.id = s.environment_id
WHERE (s2.date_end is NULL OR s2.date_start > dateadd(DAY, 1, s.date_end))
Now UNION these together and add sort according to environment and date:
select e.name, s.date_start d_start, s.date_end d_end, 'booked' as d_status FROM
signout s inner join environment e ON e.id = s.environment_id
AND s.date_start > getdate()
UNION
select e.name, dateadd(DAY, 1, s.date_end) d_start,
COALESCE(dateadd(day, -1, s2.date_start), '31.12.2025') d_end, 'free'
FROM signout s
OUTER APPLY (
SELECT TOP 1 date_start, date_end from signout sx
WHERE sx.environment_id = s.environment_id
AND sx.date_start > s.date_end ORDER BY sx.date_start
) s2
inner join environment e ON e.id = s.environment_id
WHERE (s2.date_end is NULL OR s2.date_start > dateadd(DAY, 1, s.date_end))
AND s.date_start > getdate()
ORDER BY 1, 2
Here's what this gets me:
BluRay 2015-12-01 2015-12-02 booked
BluRay 2015-12-03 2015-12-03 free
BluRay 2015-12-04 2015-12-07 booked
BluRay 2015-12-08 2015-12-10 free
BluRay 2015-12-11 2015-12-13 booked
BluRay 2015-12-14 2015-12-23 booked
BluRay 2015-12-24 2025-12-31 free
DVD 2015-11-10 2015-11-12 booked
DVD 2015-11-13 2015-11-30 free
DVD 2015-12-01 2015-12-24 booked
DVD 2015-12-25 2025-12-31 free

Related

Check Hall Booking status

I'm making a Hall Booking System, and I've being struggling with the Booking Module
I have 2 tables Halls & Bookings
Halls table With Sample Data
id hallName hallType numSeats status
---------------------------------------------
1 Hall 1 Normal 500 active
2 Hall 2 VIP 30 active
3 Hall 3 VVIP 5 active
4 Hall 4 Normal 60 active
5 Hall 5 Normal 80 active
6 Hall 4+5 Normal 140 active
Booking Table with Sample Data
id custId hallId beginDate endDate time status
-----------------------------------------------------------------
1 1 1 2022-09-26 2022-09-26 morning confirmed
2 6 4 2022-09-26 2022-09-26 evening cancelled
3 4 3 2022-09-26 2022-09-26 full time pending
4 9 4 2022-09-26 2022-09-30 after noon confirmed
Booking time slots are "morning", "after noon", "evening" & "full time"
I have being trying on the Booking validations as well as a report that shows Halls booking status
Edit
After suggestions in the comments I have edited and removed the second part, maybe will post seperately if I can't figure out
Here is what I want to be the result
The records can be filtered by endDate(e.g 2022-09-26)
if Booking exists which is not cancelled for the provided beginDate, the time slot should be labelled as Booked
if a booking does not exist for the provided beginDate or its canclled, the time slot should be labeled as Available
If a full time Booking slot ecists for the provied beginDate, all the time the 3 slots should be labeled as Booked
Here is the twist that may complicate things
As you can see from the Halls table, Hall 6 is a combination of Halls 4 & 5 which means if any of them is booked, Hall 6 should be marked as unavailable or even Booked will be fine.
Overall, here is a sample result based on the above booking table
hallName hallType morning after noon evening
------------------------------------------------------
hall 1 Normal Booked available available
hall 2 VIP available available available
hall 3 VVIP Booked Booked Booked
hall 4 Normal available Booked available
hall 5 Normal available available available
hall 6 Normal available unavailable available
if we take the Booking Table with Sample data, the result should be as shown above
I'm not that much familiar with Joins, merges, etc which are what I'm thinking the solution is. I tried merge with When matched and when Not matched but could not figure it out to work
I then tried left joining the Halls table to the Booking table seems to be the closest so far and the output is clear.
DECLARE #date NVarchar(50) = '2022-09-26'
SELECT h.id, h.hallName, h.hallType, b.time FROM Halls h LEFT JOIN
Bookings b ON b.hallId=h.id WHERE b.beginDate=#date
this returns only Booked halls with beginDate of that
If I drop the WHERE clause, all the 6 halls are returned but time slots which aren't booked are returned as NULL
btw I'm working on the last module and at firts I was working with a single check in date and requirement changed into Range beginDate & endDate & that is when problems arose.
Firstly you need to fix your design how are you intend to store information that Hall 6 is a combine of Hall 4 + 5
One simple way is to add another column in the Halls table that indicate that. Example
create table Halls
(
id int,
hallName varchar(10),
hallType varchar(10),
numSeats int,
status varchar(10),
combine_id int
);
insert into Halls values
(4, 'Hall 4', 'Normal', 60, 'active', 6),
(5, 'Hall 5', 'Normal', 80, 'active', 6),
(6, 'Hall 4+5', 'Normal', 140, 'active', null);
Once that is in-placed, you need to translate the Bookings to handle the combined Halls. This is perform by the CTE BookingData. It will create row for Hall 6 if Hall 4 or Hall 5 is pending or confirmed. And simiarly the other way round. When Hall 6 is Booked, Hall 4 and Hall 5 will be unavailable.
After that it is just simple pivoting of data
The solution:
DECLARE #date date = '2022-09-26';
with BookingData as
(
select b.hallId, b.time, b.status, beginDate
from Bookings b
union all
select hallId = h.combine_id, b.time,
status = case when b.status in ('pending', 'confirmed')
then 'unavailable'
else 'available'
end,
beginDate
from Bookings b
inner join Halls h on b.hallId = h.id
where h.combine_id is not null
union all
select hallId = h.id, b.time,
status = case when b.status in ('pending', 'confirmed')
then 'unavailable'
else 'available'
end,
beginDate
from Bookings b
inner join Halls h on b.hallId = h.combine_id
where h.combine_id is not null
)
SELECT id,
hallName,
hallType,
[morning] = isnull([morning], 'available'),
[afternoon] = isnull([afternoon], 'available'),
[evening] = isnull([evening], 'available')
FROM
(
SELECT h.id, h.hallName, h.hallType, t.timeSlot,
status = case when b.status in ('pending', 'confirmed')
then 'Booked'
when b.status in ('cancelled')
then 'unavailable'
when b.status in ('unavailable')
then b.status
else NULL
end
FROM Halls h
LEFT JOIN BookingData b ON b.hallId = h.id
and b.beginDate = #date
OUTER APPLY
(
select timeSlot = 'morning' where b.time in ('morning', 'full time')
union all
select timeSlot = 'afternoon' where b.time in ('afternoon', 'full time')
union all
select timeSlot = 'evening' where b.time in ('evening', 'full time')
) t
) D
PIVOT
(
MAX (status)
FOR timeSlot in ( [morning], [afternoon], [evening] )
) P
db<>fiddle demo
Your question is overtly complicated due to the fact the table does not implement normalization properly for "combined halls." We will need to separate Hall 4+5 using string operation and then store the extracted information somewhere. Here is the query that I could think off:
with
composite_halls as
(select *,
substring(hallName, charindex(' ', hallName) + 1,
charindex('+', hallName) - charindex(' ', hallName) - 1) as id1,
right(hallName, len(hallName) - charindex('+', hallName)) as id2,
(null) as id3
from halls where charindex('+', hallName) > 0),
singular_halls as
(select *,
(select ch.id2 from composite_halls as ch where ch.id1 = h.id) as id1,
(select ch.id1 from composite_halls as ch where ch.id2 = h.id) as id2,
(select ch.id from composite_halls as ch where ch.id1 = h.id or ch.id2 = h.id) as id3
from halls as h where charindex('+', hallName) = 0),
all_halls as (
select * from singular_halls
union
select * from composite_halls),
valid_bookings as (
select * from bookings where status = 'confirmed'
and beginDate >= '2022-09-26' and endDate <= '2022-09-26')
select *,
(case when exists (
select *
from valid_bookings as vb
where (time = 'morning' or time = 'full time')
and (vb.hallId = h.id
or (vb.hallId = h.id1 and h.id3 is null)
or (vb.hallId = h.id2 and h.id3 is null)
or vb.hallId = h.id3))
then 'booked'
else 'available'
end) as morning
from all_halls as h
You can try it on fiddle: https://dbfiddle.uk/baQyI1Y7
It looks somewhat detestable and I believe someone else can write shorter queries.
The query above uses 4 CTEs with 3 of them deals with extracting the ids of relevant "combined halls." composite_halls will extract the numbers from hallName and puts them into id1 and id2. singular_halls will put its counterpart's id in either id1 or id2 and its composite in id3. all_halls merges the two result into one. valid_bookings filters bookings for given timeframe and only confirmed bookings are taken.
The next step, it checks if any bookings' hallId correspond to any of id, id1, id2, id3 within the all_halls entries. So far it only handles morning bookings and only provide booked and available status. You can expand it to provide unavailable by adding more case when to specifically handle id1 up to id3. You can handle after noon and evening can be done the same way by changing the time parameter within the case when clause.
I don't think the query above is performant enough for a live application. Especially the fact that we operate on row-by-row basis to extract the relevant halls from the "combined" one.
I believe things would be much easier should you normalize the table a bit more. I feel its like doing excel using TSQL which doesn't seem right.

Count records using Date filter related tables SQL Server

I'm trying to calculates the number of reports (report_user table) per user between two date (Calendar table) and by day worked (agenda_user table).
Here is the diagram of my tables:
Calendar table :
DATE Year Month
---------------------------------
2020-01-01 2020 1
2020-01-02 2020 1
2020-01-03 2020 1
2020-01-04 2020 1
AGENDA_USER table :
ID_USER DATE Value
---------------------------------
1 2020-01-01 1
2 2020-01-01 1
1 2020-01-02 0
2 2020-01-02 1
User table :
ID_USER Name
-------------------------
1 Jack
2 Robert
Report_Result table :
ID_USER Date Result
-----------------------------------
1 2020-01-01 good
1 2020-01-01 good
2 2020-01-01 bad
2 2020-01-01 good
2 2020-01-02 good
2 2020-01-02 good
Result I'm trying to find with an SQL query
ID_USER Date Number of report Day work report/work
---------------------------------------------------------------------------
1 2020-01-01 2 1 2/1 = 2
2 2020-01-01 2 1 1
1 2020-01-02 0 0 0
2 2020-01-02 2 1 2
SELECT
REPORT_USER.ID_USER,
COUNT(ID_USER) AS result
FROM [DB].[dbo].REPORT_USER AS report,
JOIN [DB].[dbo].[USER] AS [USER]
ON [USER].ID_USER = report.ID_USER
JOIN [DB].[dbo].AGENDA_USER AS agenda
ON agenda.ID_USER = report.ID_USER
WHERE CAST(agenda.[Date] AS DATE) >= '2020-09-01'
AND CAST(agenda.[Date] AS DATE) <= '2021-07-28'
AND [USER].ID_user = 1167
GROUP BY
report.ID_VENDEUR;
I'm not entirely sure I understand your problem, but I think I'm reasonably close so here is a start, point out my invalid assumptions and we can refine. More data, particularly in Agenda and Reports would really help. An explanation is below (plus see the comment in the code).
The overall flow is to generate a list of days/people you want to report on (cteUserDays), generate a list of how many reports each user generated on each date (cteReps), generate a list of who worked on what days (cteWork), and then JOIN all 3 parts together using a LEFT OUTER JOIN so the report covers all workers on all days.
EDIT: Add cteRepRaw where DATETIME is converted to DATE and "bad" reports are filtered out. Grouping and counting still happens in cteReps, but joining to cteUserDays is not there because it was adding 1 to count if there was a NULL.
DECLARE #Cal TABLE (CalDate DATETIME, CalYear int, CalMonth int)
DECLARE #Agenda TABLE (UserID int, CalDate DATE, AgendaVal int)
DECLARE #User TABLE (UserID int, UserName nvarchar(50))
DECLARE #Reps TABLE (UserID int, CalDate DATETIME, RepResult nvarchar(50))
INSERT INTO #Cal(CalDate, CalYear, CalMonth)
VALUES ('2020-01-01', 2020, 1), ('2020-01-02', 2020, 1), ('2020-01-03', 2020, 1), ('2020-01-04', 2020, 1)
INSERT INTO #Agenda(UserID, CalDate, AgendaVal)
VALUES (1, '2020-01-01', 1), (2, '2020-01-01', 1), (1, '2020-01-02', 0), (2, '2020-01-02', 1)
INSERT INTO #User (UserID , UserName )
VALUES (1, 'Jack'), (2, 'Robert')
INSERT INTO #Reps (UserID , CalDate , RepResult )
VALUES (1, '2020-01-01', 'good'), (1, '2020-01-01', 'good')
, (2, '2020-01-01', 'bad'), (2, '2020-01-01', 'good')
, (2, '2020-01-02', 'good'), (2, '2020-01-02', 'good')
; with cteUserDays as (
--First, you want zeros in your table where no reports are, so build a table for that
SELECT CONVERT(DATE, D.CalDate) as CalDate --EDIT add CONVERT here
, U.UserID FROM #Cal as D CROSS JOIN #User as U
WHERE D.CalDate >= '2020-01-01' AND D.CalDate <= '2021-07-28'
--EDIT Watch the <= date here, a DATE is < DATETIME with hours of the same day
), cteRepRaw as (--EDIT Add this CTE to convert DATETIME to DATE so we can group on it
--Map the DateTime to a DATE type so we can group reports from any time of day
SELECT R.UserID
, CONVERT(DATE, R.CalDate) as CalDate --EDIT add CONVERT here
, R.RepResult
FROM #Reps as R
WHERE R.RepResult='good' --EDIT Add this test to only count good ones
), cteReps as (
--Get the sum of all reports for a given user on a given day, though some might be missing (fill 0)
SELECT R.UserID , R.CalDate , COUNT(*) as Reports --SUM(COALESCE(R.RepResult, 0)) as Reports
FROM cteRepRaw as R--cteUserDays as D
--Some days may have no reports for that worker, so use a LEFT OUTER JOIN
--LEFT OUTER JOIN cteRepRaw as R on D.CalDate = R.CalDate AND D.UserID = R.UserID
GROUP BY R.UserID , R.CalDate
) , cteWork as (
--Unclear what values "value" in Agenda can take, but assuming it's some kind of work
-- unit, like "hours worked" or "shifts" so add them up
SELECT A.UserID , A.CalDate, SUM(A.AgendaVal) as DayWork FROM #Agenda as A
WHERE A.CalDate >= '2020-01-01' AND A.CalDate <= '2021-07-28'
GROUP BY A.CalDate, A.UserID
)
SELECT D.UserID , D.CalDate, COALESCE(R.Reports, 0) as Reports, W.DayWork
--NOTE: While it's probably a mistake to credit a report to a day a worker had
--no shifts, it could happen and would throw an error so check
, CASE WHEN W.DayWork > 0 THEN R.Reports / W.DayWork ELSE 0 END as RepPerWork
FROM cteUserDays as D
LEFT OUTER JOIN cteReps as R on D.CalDate=R.CalDate AND R.UserID = D.UserID
LEFT OUTER JOIN cteWork as W on D.UserID = W.UserID AND D.CalDate = W.CalDate
ORDER BY CalDate , UserID
First, as per the comments in your OP "Agenda" represents when the user is working, you don't say how it's structured so I'll assume it can have multiple entries for a given person on a given day (i.e. a 4 hour shift and an 8 hour shift) so I'll add them up to get total work (cteWork). I also assume that if somebody didn't work, you can't have a report for them. I check for this, but normally I'd expect your data validator to pre-screen those out.
Second, I'll assume reports are 1 per record, and a given user can have multiple per day. You have that in your given, but it's important to this solution so I'm restating in case somebody else reads this later.
Third, I assume you want all days reported for all users, I assure this by generating a CROSS join between users and days (cteUserDays)

How to display data on separate rows in one row

I have a table with the following setup
ID InOut_Status InOut_Datetime
1 IN 9/12/2017 8:00
2 IN 9/12/2017 10:00
1 OUT 9/12/2017 1:00
2 OUT 9/12/2017 3:00
I want to be able to see both status and date on the same row vs separate rows for example
ID In_Status In_Datetime Out_Status Out_Datetime
1 IN 9/12/2017 8:00 OUT 9/12/2017 1:00
2 IN 9/12/2017 10:00 OUT 9/12/2017 3:00
I would like to return all columns. I just provided a few for example. I also would like to show only the most recent Datetime for each ID and if the user hasn't checked out, I would like for the Out_Datetime to be blank.
Any assistance would be greatly appreciated. Thanks.
You can use self join:
SELECT *
FROM
(
SELECT ins.id
, ins.InOut_Datetime as in_time
, outs.InOut_Datetime as out_time
, row_number() over (partition by ins.id order by ins.InOut_Datetime desc) as ranking
FROM table ins
LEFT JOIN table outs
ON ins.id = outs.id
AND outs.InOut_Status = 'OUT'
AND outs.InOut_Datetime > ins.InOut_Datetime
WHERE ins.InOut_Status = 'IN'
and ins.InOut_Datetime > DATEADD(day, -1, GETDATE())
) t
WHERE t.ranking = 1
Updated query to :
get logins within last 24 hours
get the latest login of a user only
show out time only if it's later than in time
You need to left join, however, you want to limit the join to the first record returned in descending order using a sub query.
SELECT * FROM
(
SELECT
ID,InOut_Status,InOutDateTime,
CheckOutInstanceDescending = ROW_NUMBER() OVER(PARTITION BY ClockOut.ID ORDER BY ClockOut.InOutDateTime DESC)
FROM
MyTable ClockIn
LEFT OUTER JOIN MyTable ClockOut ON ClockOut.ID=ClockIn.ID
WHERE
ClockIn.InOut_Status='IN'
)AS Combined
WHERE
Combined.CheckOutInstanceDescending=1
A simple pivot would fix this the easiest:
https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx. You can run this in SSMS 2008 or higher(I wrote it in SQL 2016).
DECLARE #Temp TABLE (Id INT, InOut_Status VARCHAR(8), InOut_Datetime DATETIME)
INSERT INTO #Temp (Id, InOut_Status, InOut_Datetime) VALUES (1, 'IN', '9-12-2017 8:00'), (2, 'IN', '9-12-2017 10:00'),(1, 'OUT', '9-12-2017 13:00'),(2, 'OUT', '9-12-2017 16:00'),(3, 'IN', '9-12-2017 06:00')
SELECT
pvt.Id
, 'IN' AS In_Status
, pvt.[In]
, 'OUT' AS Out_Status
, pvt.OUT
From #Temp
PIVOT(MAX(InOut_Datetime) FOR InOut_Status IN ([In], [OUT])) AS pvt

Make balance report with previous

I have to make daily report from an SQL table which contains ID (user id), Timestamp, Balance Transactions.
My quest: Every transactions have been stored in the table. I have to know the summary all user's balance on every day.
For examle:
27/06/2016 8:10 User1 50$
27/06/2016 10:22 User1 75$
27/06/2016 11:32 User2 10$
28/06/2016 09:22 User3 40$
28/06/2016 17:35 User1 22$
In this case the results have to be the following:
27/06/2016: 85$ (75+10) because last user1's balance 75 and user2 10
28/06/2016: 72$ (22+10+40) because last user1's balance 22 and user2 10 (it was modified on yesterday but I have to count it!!!) and user3 22$
Please help.
Thanks
My solution but it is not correct: Only provide result if transaction was on the day and does not add previous day result.
Here are the requests I tried so far :
Request 1 :
USE DB1;
GO
WITH cte (bin, currency, id, currentbalance, currentledgerbalance, dt) as ( SELECT bin, w.currency, w.id,t.currentbalance, t.currentledgerbalance, t.dt
FROM [DB1].[Tb1] b
inner join [tb2] c
on b.[id]=c.[id]
inner join tb3 w
on c.id=w.id and w.currency=b.currency
inner join [DB1].[tb4] t
on t.walletid=w.id )
, CTE2 (bin,currency,id,currentbalance,currentledgerbalance,dt) as (
select *
from cte
where dt in (select MAX(dt) FROM cte GROUP BY currency,id,DAY(dt), MONTH(dt), YEAR(dt))
)
Request 2 :
select currency
,cast(dt as date) as stat_day
,sum(currentbalance) as currentbalance
from CTE2
GROUP BY currency,cast(dt as date)
order by stat_day go
I am not sure what other tables involved in the solution. Just giving you a generic solution based on the query given:
---Creating a test table
create table usertrans (tid int identity, tdate date, uname varchar(30),balance int);
insert into usertrans values ('06/27/2017','user1',50);
insert into usertrans values ('06/27/2017','user1',75);
insert into usertrans values ('06/27/2017','user2',10);
insert into usertrans values ('06/28/2017','user3',40);
insert into usertrans values ('06/28/2017','user1',22);
select * from usertrans
-- Retrieving (2017-06-28) balance
with UMaxTrans(UName,TID)
AS(
select uname, max(tid) AS TID from usertrans
WHERE TDate < = '2017-06-28'
group by uname)
select CAST(GETDATE() AS DATE) AS 'Today' , sum(Balance) FROM UserTrans UT
INNER JOIN UMaxTrans UMT ON UT.TID = UMT.TID;
-- Retrieving (2017-06-27) balance
with UMaxTrans(UName,TID)
AS(
select uname, max(tid) AS TID from usertrans
WHERE TDate < = '2017-06-27'
group by uname)
select CAST(GETDATE() AS DATE) AS 'Today' , sum(Balance) FROM UserTrans UT
INNER JOIN UMaxTrans UMT ON UT.TID = UMT.TID;
Logic:
We have users and the balance might get change any number of times in a given day but while calculating the total balance from all users we have to consider the latest transaction from the user. Thats what we are doing in the query. We are getting the maximum transaction ID for a given user and thats the latest balance.

SQL Server Interest calculations on transactions

I'm looking for advice on best way to build a compound interest module in SQL server. Basic set up:
Table: Transactions {TransID, MemberID, Trans_Date, Trans_Code, Trans_Value).
Table: Interest {IntID, Int_eff_Date, Int_Rate}
In the interest table, there may be different rates with an effective date - can never have an over lapping date though. For example:
Int_Eff_Date Int_Rate
01/01/2016 7%
01/10/2016 7.5%
10/01/2017 8%
I want to calculate the interest based on the transaction date and transaction value, where the correct interest rate is applied relative to transaction date.
So if Table transaction had:
TransID MemberID Trans_Date Trans_Value
1 1 15/04/2016 150
2 1 18/10/2016 200
3 1 24/11/2016 200
4 1 15/01/2017 250
For transID 1 it would use 7% from 15/04/2016 until 30/09/2016 (168 days) from 1/10/2016 to 09/01/2017 it would use 7.% and then from 10/01/2007 to calculation date (input parameter) it would use 8%.
It would apply similar methodology for all transactions, add them up and display the interest value.
I'm not sure if I should use cursors, UDF, etc.
This should provide an outline of what you're trying to do.
--Build Test Data
CREATE TABLE #Rates(Int_Eff_Date DATE
, Int_Rate FLOAT)
CREATE TABLE #Transactions(TransID INT
,MemberID INT
,Trans_Date DATE
,Trans_Value INT)
INSERT INTO #Rates
VALUES ('20160101',7)
,('20161001',7.5)
,('20170110',8)
INSERT INTO #Transactions
VALUES
(1,1,'20160415',150)
,(2,1,'20161018',200)
,(3,1,'20161124',200)
,(4,1,'20170115',250)
;WITH cte_Date_Rates
AS
(
SELECT
S.Int_Eff_Date
,ISNULL(E.Int_Eff_Date,'20490101') AS "Expire"
,S.Int_Rate
FROM
#Rates S
OUTER APPLY (SELECT TOP 1 Int_Eff_Date
FROM #Rates E
WHERE E.Int_Eff_Date > S.Int_Eff_Date
ORDER BY E.Int_Eff_Date) E
)
SELECT
T.*
,R.Int_Rate
FROM
#Transactions T
LEFT JOIN cte_Date_Rates R
ON
T.Trans_Date >= R.Int_Eff_Date
AND
T.Trans_Date < R.Expire