How to calculate expiring voucher balances - sql-server

I have a simple table for vouchers that subscribers buy to enable usage minutes on a system
then a table for usages that were actually used
now, to figure out the balance should be pretty simple with a window function. problem is, that some of the vouchers have expiration dates. And for the life of me I can't figure out how to exclude usages after the expiration date. Here is a very simple fiddle:
CREATE TABLE [dbo].[Vouchers](
[SubscriptionID] [int] NOT NULL,
[ID] [int] IDENTITY(1,1) NOT NULL,
[AddedOn] [datetime2](7) NOT NULL,
[Cost] [money] NOT NULL,
[Expiry] [datetime2](7) NULL)
GO
CREATE TABLE [dbo].[Usages](
[Date] [datetime2](7) NOT NULL,
[SubscriptionID] [int] NOT NULL,
[Price] [money] NOT NULL,
[ID] [int] IDENTITY(1,1) NOT NULL)
GO
INSERT INTO Vouchers (SubscriptionID,AddedOn,Cost,Expiry) Values (1,'2000-01-01',100,NULL)
INSERT INTO Vouchers (SubscriptionID,AddedOn,Cost,Expiry) Values (1,'2000-02-01',100,'2000-02-25')
INSERT INTO Vouchers (SubscriptionID,AddedOn,Cost,Expiry) Values (1,'2000-03-01',100,NULL)
INSERT INTO Usages (SubscriptionID,Date,Price) Values (1,'2000-01-01',50)
INSERT INTO Usages (SubscriptionID,Date,Price) Values (1,'2000-02-01',70)
INSERT INTO Usages (SubscriptionID,Date,Price) Values (1,'2000-02-28',30)
http://sqlfiddle.com/#!18/68f03
This is my current query:
SELECT *, SUM(Amount) OVER (PARTITION BY subscriptionid ORDER BY date ROWS UNBOUNDED PRECEDING) AS Balance
from (
SELECT SubscriptionID, AddedOn as Date, Cost as Amount
from vouchers
UNION
SELECT SubscriptionID, Date, -Price
FROM Usages)t
If you run this in the fiddle, You will see the last line has a balance of 0. Which is wrong. The second voucher expires midmonth. So the last 50 dollars cannot be calculated in the voucher. And the customer should be in a minus 50.
I would appreciate any help
Thanks!

Related

How to calculate running total from due amount in SQL Server

I have customer Table ID and Customer Name
CREATE TABLE [dbo].[Customer]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [nvarchar](500) NULL
) ON [PRIMARY]
I have Payment Schedule Table each customer may have different schedule
CREATE TABLE [dbo].[SchedulePayment]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Date] [date] NULL,
[Amount] [decimal](18, 0) NULL,
[CustomerID] [int] NULL
) ON [PRIMARY]
I have ReceivedPayment table:
CREATE TABLE [dbo].[ReceivedPayment]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[CustomerID] [int] NULL,
[ReceivedAmount] [decimal](18, 0) NULL,
[ReceivedDate] [date] NULL
) ON [PRIMARY]
I want to deduct received amount from the schedule amount and show the remaining amount till today month and year future amount would be 0 because it is not due now but if customer pay extra then it will show in the future months
I want below output
SELECT rp.CustomerID,[date] ScheduleDate, ReceivedAmount,
(SumAmount -
SUM(ReceivedAmount)
OVER (PARTITION BY rp.CustomerID ORDER BY Receiveddate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
) AS PendingAmount
,Amount AS ScheduleAmount
FROM
(SELECT [date], Amount, CustomerID,
(SUM(amount)
OVER (PARTITION BY CustomerID ORDER BY [date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
) AS SumAmount
FROM SchedulePayment) sp
JOIN ReceivedPayment rp ON rp.CustomerID = sp.CustomerID
AND ReceivedDate >= [date]
AND
(
(DATEPART(MM,ReceivedDate)=DATEPART(MM,[date]))
AND (DATEPART(YYYY,ReceivedDate)=DATEPART(YYYY,[date]))
)
ORDER BY rp.CustomerID,ReceivedDate,[date]
i looked at it too, and there is more going on here than can be explained at first glance. in normal payment posting, a balance carry forward rule to be held pending apply, and another rule would exist as the first of the month occurs, for payments in excess of payment due let alone no arrears. the solution has some rules than in all intense purposes is not necessarily database driven yet process and application driven from an accounting a/r perspective. here is some sample testing I did just to see how close I could come yet then realized expected output requires rules and dates and what I call float.
use test1
/*
CREATE TABLE [dbo].[Customer]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [nvarchar](500) NULL
) ON [PRIMARY]
-- drop table customer
insert into customer select ('Customer1')
insert into customer select ('Customer2')
select * from customer
CREATE TABLE [dbo].[SchedulePayment]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Date] [date] NULL,
[Amount] money NULL,
[CustomerID] [int] NULL
) ON [PRIMARY]
insert into SchedulePayment values ('2022-01-01', 1000, 1)
insert into SchedulePayment values ('2022-02-01', 1000, 1)
insert into SchedulePayment values ('2022-03-01', 1000, 1)
insert into SchedulePayment values ('2022-04-01', 1000, 1)
insert into SchedulePayment values ('2022-01-01', 1000, 2)
insert into SchedulePayment values ('2022-02-01', 1000, 2)
insert into SchedulePayment values ('2022-03-01', 1000, 2)
insert into SchedulePayment values ('2022-04-01', 1000, 2)
-- drop table schedulepayment
select * from schedulepayment
CREATE TABLE [dbo].[ReceivedPayment]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[CustomerID] [int] NULL,
[ReceivedAmount] money NULL,
[ReceivedDate] [date] NULL
) ON [PRIMARY]
insert into ReceivedPayment values (1, 500, '2022-01-06')
insert into ReceivedPayment values (1, 1500, '2022-02-15')
insert into ReceivedPayment values (1, 500, '2022-03-10')
insert into ReceivedPayment values (2, 100, '2022-01-01')
insert into ReceivedPayment values (2, 500, '2022-02-06')
insert into ReceivedPayment values (2, 400, '2022-02-08')
insert into ReceivedPayment values (2, 600, '2022-03-04')
-- drop table receivedpayment
select * from receivedpayment
*/
create table #t
(CustomerID int,
ScheduleDate date,
ReceivedAmount money,
PendingAmount money,
ScheduledAmount money
)
insert into #t (customerid, scheduledate, scheduledamount)
select
distinct customer.ID,
schedulepayment.date,
schedulepayment.Amount
from customer
left join schedulepayment on SchedulePayment.CustomerID = customer.ID
select * from #t
drop table #t
-- below is a pay flow by yet not a balance over paid and carry over to be spread
select
SchedulePayment.CustomerID,
SchedulePayment.Date,
ReceivedAmount = sum(ReceivedPayment.ReceivedAmount)
from ReceivedPayment
join SchedulePayment on ReceivedPayment.CustomerID = SchedulePayment.CustomerID
where datepart(month, SchedulePayment.Date) = datepart(month, ReceivedPayment.ReceivedDate) and datepart(year, schedulepayment.Date) = datepart(year, ReceivedPayment.ReceivedDate)
GROUP BY SchedulePayment.CustomerID, SchedulePayment.Date

After insert trigger doesn't work when using Inserted

I'm trying to write a trigger on my Employees table that should not allow the insertion of a new employee that has a hire date that is older than the hire date of his boss
CREATE TABLE [dbo].[Employees]
(
[EID] [int] IDENTITY(1,1) NOT NULL,
[Ename] [nvarchar](20) NOT NULL,
[Gender] [nvarchar](1) NOT NULL,
[IsMarried] [nvarchar](1) NOT NULL,
[Birthdate] [date] NOT NULL,
[HireDate] [date] NOT NULL,
[Salary] [float] NOT NULL,
[Notes] [nvarchar](200) NULL,
[NationalityID] [int] NULL,
[BossID] [int] NULL,
CONSTRAINT [PK_Employees]
PRIMARY KEY CLUSTERED ()
)
And here's the trigger code:
CREATE TRIGGER [dbo].[Trig_04]
ON [dbo].[Employees]
AFTER INSERT
AS
BEGIN
IF ((SELECT INSERTED.HireDate FROM INSERTED WHERE BossID <> EID) <
(SELECT Employees.HireDate FROM Employees
WHERE EID IN (SELECT Employees.BossID FROM Employees WHERE BossID <> EID)))
ROLLBACK
END
It executes normally (no errors) but it just doesn't work, but when I was using the employees table in the subquery instead of the inserted table, it was working normally. Does anyone have an answer for this?
You have to write triggers in SQL Server to handle the fact that INSERTED could contain multiple records. You cannot assume it will only be a single record. I think the following is what you are looking for:
if exists (
select 1
from Inserted I
where I.BossID <> I.EID
and I.HireDate < (select E.HireDate from Employees E where E.EID = I.BossID)
) begin
ROLLBACK;
end

How can I find last row in multiple group by WITHOUT window functions over?

I have a query that returns a result fairly quickly:
SELECT [Date],[AccountCode],[ModelCode],[Generic],SUM([MTMusd]) AS 'MTMusd'
FROM [dbo].[MTM]
WHERE [AccountCode] = 'XXX'
AND [ModelCode] = '1'
GROUP BY [Date],[AccountCode],[ModelCode],[Generic]
ORDER BY [Date]
CREATE TABLE [dbo].[MTM](
[AccountCode] [nvarchar](50) NULL,
[ModelCode] [nvarchar](50) NULL,
[Date] [date] NULL,
[TIMESTAMP] [time](7) NOT NULL,
[Generic] [nvarchar](50) NULL,
[MTMUsd] [float] NOT NULL
) ON [PRIMARY]
However, I don't want SUM() I want LAST().
In the GROUP BY I have missed out [TIMESTAMP] which is the time of day. I want my query to return the record with the LAST TIMESTAMP for each GROUP { [Date],[AccountCode],[ModelCode],[Generic] }
How can I achieve this efficiently? I have approx 5 millions rows.
I tried ROW_NUMBER() OVER () based example I found, but processing took an order of minutes.

SQL Server: How to improve performance for queries with multiple CTEs and subqueries in the WHERE clause

I have the following two tables:
CREATE TABLE Portfolio.DailyPortfolio
(
BbgID varchar(30) NOT NULL,
Ticker varchar(22) NULL,
Cusip char(9) NULL,
SecurityDescription varchar(50) NOT NULL,
AssetCategory varchar(25) NOT NULL,
LSPosition char(3) NULL,
Ccy varchar(25) NOT NULL,
Quantity int NULL,
AvgCost decimal(7,3) NULL,
PriceLocal decimal(7,3) NULL,
Cost int NULL,
MktValNet int NULL,
GLPeriod int NULL,
Beta decimal(4,2) NULL,
BetaExpNet int NULL,
BetaExpGross int NULL,
Delta decimal(4,2) NULL,
DeltaExpNet int NULL,
DeltaExpGross int NULL,
Issuer varchar(48) NOT NULL,
Country varchar(30) NOT NULL,
Region varchar(20) NOT NULL,
Sector varchar(30) NOT NULL,
Industry varchar(48) NOT NULL,
MktCapCategory varchar(24) NULL,
MktCapEnd int NULL,
Date date NOT NULL,
PortfolioID AS BbgID+LSPosition+ Convert(varchar(8),Date,112) Persisted Primary Key
)
GO
Here is the second table:
CREATE TABLE Portfolio.DailyStats
(
Date date NOT NULL Primary Key,
NAV int NOT NULL,
SP500 decimal(8,4) NULL,
R2K decimal(8,4) NULL,
NetExp decimal(8,4) NULL,
GrossExp decimal(8,4) NULL,
)
GO
ALTER TABLE Portfolio.DailyStats
ADD [YrMn] as CONVERT(varchar(7), Date)
GO
Between 80-100 rows get added to the DailyPortfolio table every business day (the table has about 32,000 rows currently). 1 row gets added to the DailyStats table every business day (it has about 500 rows currently). The Date column in the Daily Portfolio table has a Foreign Key relationship with the Date column in the DailyStats table.
I had to create a view that included a few columns from both tables using last quarter as the date range. The last column of this view uses the Average of the NAV column in its calculation where the Average is calculated by using the NAV on the 1st date of each of the 3 months in the quarter. Here is my DDL for the view:
CREATE VIEW Portfolio.PNLLastQTD
AS
WITH CTE1
AS
(
Select Date, NAV,YrMn, ROW_NUMBER() OVER (PARTITION BY YrMn ORDER BY Date) AS Row
FROM Portfolio.DailyStats
WHERE DATE BETWEEN
(SELECT Convert(date, DATEADD(q, DATEDIFF(q,0,GETDATE()) -1 ,0)))
AND
(SELECT Convert(date, DATEADD(s,-1,DATEADD(q, DATEDIFF(q,0,GETDATE()),0))))
),
CTE2
AS
(
SELECT AvG (NAV) As AvgNAV
FROM CTE1
WHERE Row=1
)
SELECT IssuerLS, Issuer, Ticker, SUM (GLPeriod) As [PNL],
CAST(SUM(GLPeriod)As Decimal (13,2)) / CAST(CTE2.[AvgNAV] As Decimal (13,2)) as [%ofNAV]
FROM Portfolio.DailyPortfolioIssuerLS ls
JOIN cte2 on 1=1
WHERE ReportDate
BETWEEN
(SELECT Convert(date, DATEADD(q, DATEDIFF(q,0,GETDATE()) -1 ,0)))
AND
(SELECT Convert(date, DATEADD(s,-1,DATEADD(q, DATEDIFF(q,0,GETDATE()),0))))
GROUP BY
Issuer, Ticker, IssuerLS, CTE2.[AvgNAV]
GO
The view works fine but takes almost 20 seconds to execute! I have a couple of questions here:
Should there be some changes made to my DDL for the view?
Is it a good idea to create a non-clustered index on the date column (if at all possible) of the DailyPortfolio table?
Is there anything else I should be thinking of to improve query performance for this particular issue?
Thanks much for your help. Please forgive blatant mistakes as I'm new to SQL.
I wanted to close on the loop on this question. What I needed to do here was create two non clustered indices. I used the following steps:
Placed my query on the query window.
Clicked on "Display Estimated Execution Plan" button on my toolbox which immediately informed me of a missing non cluster index.
Created the first non-clustered index:
USE [OurDB]
GO
CREATE NONCLUSTERED INDEX NCI_DailyPort_Issuer_Date
ON [Portfolio].[DailyPortfolio] ([Issuer],[Date])
GO
Repeated step 2 and created the second non-clustered index as recommended:
USE [OurDB]
GO
CREATE NONCLUSTERED INDEX NCI_DailyPort_Date_INC_DexpN_Issuer
ON [Portfolio].[DailyPortfolio] ([Date])
INCLUDE ([DeltaExpNet],[Issuer])
GO
The query now takes less than 3 seconds to execute, significantly better than the 24 seconds it was taking before this.
Note: If you right click on the line that informs you about the missing index, you can choose an option to see the code for the index which saves you time.

show records after current time

Actually I want some records from my database. There is a field Time_From. I just want to show only those records which are after DateTime.Now().
But it is not showing, it shows records before and after DateTime.Now()
Select *
from mytable
where Vehicle_Booking_Date= #Vehicle_Booking_Date
AND Time_From > #Time_From order by Time_From"
The table definition (from the comment)
CREATE TABLE [dbo].[mtblVehicle_Booking]
(
[Sno] [int] IDENTITY(1,1) NOT NULL,
[Vehicle_Number] [nvarchar](100) NULL,
[Vehicle_Booking_Date] [datetime] NULL,
[Time_From] [datetime] NULL,
[Time_To] [datetime] NULL,
[Vehicle_Used_By] [varchar](100) NULL,
[Vehicle_Booked_By] [varchar](100) NULL,
[Cost_Code] [nvarchar](50) NULL,
[Budget_Line] [nvarchar](50) NULL,
[Purpose] [varchar](20) NULL,
[Destination] [nvarchar](500) NULL,
[Trip_Details] [nvarchar](500) NULL
)
ON [PRIMARY]
If you want to select all records whose Time_From is greater than current time and date is equal to specified date,
Select *
from mytable
where Vehicle_Booking_Date= #Vehicle_Booking_Date
AND Time_From > now() order by Time_From'
You have not mentioned what database you are using. But concept remains same. Just substract the other date time from current time. If it is greater than 0 it means that the given date time is greater than the current date time.
Sample SQL
Select *
from mytable
where Vehicle_Booking_Date= #Vehicle_Booking_Date
AND DATEDIFF(Time_From,SYSDATETIME()) > 0
Hope this helps.

Resources