How to calculate running total from due amount in SQL Server - 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

Related

How to calculate expiring voucher balances

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!

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

Appointment Slots not working

I have three tables.
Table 1.(Booking)
CREATE TABLE [dbo].[Booking](
[Booking_Serno] [int] IDENTITY(1,1) NOT NULL,
[Dt] [datetime] NULL,
[start] [nvarchar](50) NULL,
[todate] [nvarchar](50) NULL,
[Service_Id] [int] NULL
) ON [PRIMARY]
INSERT [dbo].[Booking] ([Booking_Serno], [Dt], [start], [todate], [Service_Id]) VALUES (1, CAST(0x0000A6DA00000000 AS DateTime), N'9:30 AM', N'10:00 AM', 1)
GO
Table 2.(Service)
CREATE TABLE [dbo].[Service](
[Service_Serno] [int] IDENTITY(1,1) NOT NULL,
[Service_Duration] [int] NULL
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Service] ON
INSERT [dbo].[Service] ([Service_Serno], [Service_Duration]) VALUES (1, 30)
Table 3 (Rules)
CREATE TABLE [dbo].[Rules](
[Rule_Serno] [int] IDENTITY(1,1) NOT NULL,
[Start_Dt] [varchar](50) NULL,
[End_Dt] [varchar](50) NULL,
[from_dt] [varchar](50) NULL,
[to_dt] [varchar](50) NULL,
[Service_Id] [int] NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
SET IDENTITY_INSERT [dbo].[Rules] ON
INSERT [dbo].[Rules] ([Rule_Serno], [Start_Dt], [End_Dt], [from_dt], [to_dt], [Service_Id]) VALUES (1, N'2016-07-02', N'2016-07-13', N'07:00', N'17:00', 1)
I am running a stored procedure. It gets me the desired result but then I am trying to book a time by changing the interval, the slots shows empty even if there is a slot booked. Ex. If i am setting a slot for 60 minutes and book a slot from 7:00-8:00 it shows booked(xxx) but when i change the interval to 30 the 7:00-8:00 becomes available. It should actually display 7:00-7:30 and 7:00-8:00 unavailable.
the Stored Procedure is
Dt:-12/12/2016 ; ServiceId:-1
CREATE PROCEDURE [dbo].[RealGetFollowUp] #Dt varchar(50), #ServiceId int
AS
--declare #starttime datetime = '2015-10-28 12:00', #endtime datetime = '2015-10-28 14:00'
DECLARE #starttime varchar(50),
#endtime varchar(50),
#interval int
SELECT
#starttime = Rules.from_dt,
#endtime = Rules.to_dt,
#interval = Service.Service_Duration
FROM Service
INNER JOIN Rules
ON Service.Service_Serno = Rules.Service_Id
WHERE Service.Service_Serno = #ServiceId
--SELECT * INTO #tmp FROM d;
DECLARE #slots int
SELECT
#slots = DATEDIFF(MINUTE, #starttime, #endtime) / #interval
SELECT TOP (#slots)
N = IDENTITY(int, 1, 1) INTO #Numbers
FROM master.dbo.syscolumns a
CROSS JOIN master.dbo.syscolumns b;
SELECT
DATEADD(MINUTE, ((n - 1) * #interval), #starttime) AS start,
DATEADD(MINUTE, (n * #interval), #starttime) AS todate INTO #slots
FROM #numbers
SELECT
#Dt AS 'Date',
LEFT(CONVERT(varchar, s.start, 108), 10) AS Start,
LEFT(CONVERT(varchar, s.todate, 108), 10) AS 'End',
CASE
WHEN b.start IS NULL THEN '-'
ELSE 'xx'
END AS Status
FROM [#slots] AS s
LEFT JOIN Booking AS b
ON s.start = b.start
AND s.todate = b.todate
AND b.Dt = #Dt
DROP TABLE #numbers, #slots
GO
I need to check if there is a slot booked in the Booking table and even if i change the interval in the service table, the slot booked in the booking table should be shown as booked.
Change the output SELECT in the sproc to...
SELECT
#Dt AS 'Date',
LEFT(CONVERT(varchar, s.start, 108), 10) AS Start,
LEFT(CONVERT(varchar, s.todate, 108), 10) AS 'End',
CASE
WHEN b.start IS NULL THEN '-'
ELSE 'xx'
END AS Status
FROM [#slots] AS s
LEFT JOIN Booking AS b
ON (
--Range is bigger than the meeting
(s.start <= b.start
AND s.todate >= b.todate)
OR
--Range is smaller than the meeting
(s.start Between b.start and b.toDate
AND s.todate Between b.start and b.toDate)
)
AND b.Dt = #Dt

Can I make this trigger shorter / better / faster?

The task itself is very basic. I have a transaction table that moves a certain quantity of a product in or out of stock. It becomes a bit more difficult knowing that a product can be composed from multiple 'base' products and only the base products may exist in stock.
My current trigger works but works like this:
Create temporary table that can combine inserted, deleted and composed products.
Add inserted non composed products to this temp table
Add inserted composed products
Subtract deleted non composed products
Subtract deleted composed products
I now have a table containing all transactions of non composed products. But i.e. with an update of a row, the same product is added twice. Once subtracting the old value and once adding the new value.
Create second temporary table for the joined non composed products
Take a sum of the products and put these into the new temp table
Merge the stock with this temp table
Again.. my trigger works fine and i'm having no performance problem (yet) but I feel i'm missing something. I bet there is someone around here with a better solution then mine.
My tables and sample data: (fiddle here)
CREATE TABLE [dbo].[Product](
[id] [int] IDENTITY(1,1) NOT NULL,
[plantId] [int] NOT NULL,
[reference] [nvarchar](50) NOT NULL,
[composed] [bit] NOT NULL DEFAULT ((0)),
[name] [nvarchar](max) NOT NULL,
[unit] [nvarchar](50) NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate())
);
INSERT INTO [dbo].[Product] VALUES (2, 'FILLER VULPROF 2 BR', 0, 'FILLER VULPROF 2 BR', 'ton', GETDATE());
INSERT INTO [dbo].[Product] VALUES (2, 'K 0/2GEW BR', 0, 'K 0/2GEW BR', 'ton', GETDATE());
INSERT INTO [dbo].[Product] VALUES (2, 'K 14/20 BR', 0, 'K 14/20 BR', 'ton', GETDATE());
INSERT INTO [dbo].[Product] VALUES (2, 'KWS BR 3A31', 1, 'KWS BR 3A31', 'ton', GETDATE());
CREATE TABLE [dbo].[ProductComposition](
[id] [int] IDENTITY(1,1) NOT NULL,
[productId] [int] NOT NULL,
[parentId] [int] NOT NULL, -- This is the parent product
[quantity] [float] NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate())
);
INSERT INTO [dbo].[ProductComposition] VALUES (1, 4, 0.001, GETDATE());
INSERT INTO [dbo].[ProductComposition] VALUES (3, 4, 0.001, GETDATE());
CREATE TABLE [dbo].[Transaction](
[id] [int] IDENTITY(1,1) NOT NULL,
[load] [bit] NOT NULL,
[plantId] [int] NOT NULL,
[vehicleId] [int] NOT NULL,
[productId] [int] NOT NULL,
[contactId] [int] NOT NULL,
[quantity] [float] NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate()),
[isSynched] [bit] NOT NULL CONSTRAINT [DF_Transaction_isSynched] DEFAULT ((0))
);
CREATE TABLE [dbo].[Stock](
[id] [int] IDENTITY(1,1) NOT NULL,
[plantId] [int] NOT NULL,
[productId] [int] NOT NULL,
[quantity] [float] NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate())
);
My Trigger:
ALTER TRIGGER [dbo].[StockCalculate]
ON [dbo].[Transaction]
AFTER INSERT, UPDATE, DELETE AS
BEGIN
-- Create temporary table
CREATE TABLE #TransactionsComposed (
[load] [bit] NOT NULL,
[plantId] [int] NOT NULL,
[productId] [int] NOT NULL,
[quantity] [float] NOT NULL
);
-- Insert basic materials
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
[Inserted].[load],
[Inserted].[plantId],
[Inserted].[productId],
[Inserted].[quantity]
FROM
[Inserted]
INNER JOIN [Product] ON [Product].[id] = [Inserted].[productId]
WHERE
[Product].[composed] = 0;
-- Insert operations
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
[Inserted].[load],
[Inserted].[plantId],
[ProductComposition].[productId],
([Inserted].[quantity] * [ProductComposition].[quantity])
FROM
[Inserted]
INNER JOIN [Product] ON [Product].[id] = [Inserted].[productId]
INNER JOIN [ProductComposition] ON [ProductComposition].[parentId] = [Product].[id]
WHERE
[Product].[composed] = 1;
-- Insert basic materials but ALTER it's load status.
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
CASE [Deleted].[load]
WHEN 0 THEN 1
WHEN 1 THEN 0
END,
[Deleted].[plantId],
[Deleted].[productId],
[Deleted].[quantity]
FROM
[Deleted]
INNER JOIN [Product] ON [Product].[id] = [Deleted].[productId]
WHERE
[Product].[composed] = 0;
-- Insert operations but ALTER it's load status.
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
CASE [Deleted].[load]
WHEN 0 THEN 1
WHEN 1 THEN 0
END,
[Deleted].[plantId],
[ProductComposition].[productId],
([Deleted].[quantity] * [ProductComposition].[quantity])
FROM
[Deleted]
INNER JOIN [Product] ON [Product].[id] = [Deleted].[productId]
INNER JOIN [ProductComposition] ON [ProductComposition].[parentId] = [Product].[id]
WHERE
[Product].[composed] = 1;
-- Prepare multiple products for merge
CREATE TABLE #TransactionsJoined (
[plantId] [int] NOT NULL,
[productId] [int] NOT NULL,
[quantity] [float] NOT NULL
);
INSERT INTO #TransactionsJoined
([plantId], [productId], [quantity])
SELECT
[plantId],
[productId],
SUM(CASE [load]
WHEN 0 THEN [quantity]
WHEN 1 THEN 0 - [quantity]
END)
FROM
#TransactionsComposed
GROUP BY [plantId], [productId]
-- Merge composed transactions into the stock
MERGE [Stock]
USING #TransactionsJoined
ON [Stock].[plantId]=#TransactionsJoined.[plantId]
AND [Stock].[productId]=#TransactionsJoined.[productId]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([plantId], [productId], [quantity])
VALUES (
#TransactionsJoined.[plantId],
#TransactionsJoined.[productId],
#TransactionsJoined.[quantity])
WHEN MATCHED THEN
UPDATE SET
[Stock].[quantity] = [Stock].[quantity] + #TransactionsJoined.[quantity],
[Stock].[dateUpdate] = GETDATE();
END

Insert current row number into SQL Server table

I have the following table in SQL Server:
CREATE TABLE [dbo].[tblTempPo](
[TempPoID] [int] IDENTITY(1,1) NOT NULL,
[guid] AS ([dbo].[GetIdentity]()),
[Qty] [int] NULL,
[MobileBrandID] [int] NULL,
[MobileID] [int] NULL
)
I need to insert the current row number to the guid column every time a new row is added. I tried to use the following function but it's not working as expected:
ALTER FUNCTION GetIdentity()
RETURNS INT AS
BEGIN
RETURN (SELECT top 1 ROW_NUMBER() OVER(ORDER BY TempPoID asc)FROM tblTempPo)
END
Your function GetIdentity() will probably always return 1 but it is not a sure thing because you are using select top 1... without an order by clause.
If you want the highest value returned by row_number() you need to add order by 1 desc which would be the same as doing SELECT count(*) from tblTempPo.
Fixing GetIdentity() like that will not help much in your situation because [guid] AS ([dbo].[GetIdentity]()) will give you a computed column that is evaluated every time you query the table and not when you insert a new row. You will always have the same value for all rows.
You could use a function that takes the TempPoID as a parameter as a computed column.
CREATE FUNCTION GetIdentity(#P int)
RETURNS INT AS
BEGIN
RETURN (SELECT rn
FROM (SELECT TempPoID,
ROW_NUMBER() OVER(ORDER BY TempPoID ASC) AS rn
FROM tblTempPo) AS T
WHERE TempPoID = #P)
END
Table definition:
CREATE TABLE [dbo].[tblTempPo](
[TempPoID] [int] IDENTITY(1,1) NOT NULL primary key,
[guid] as dbo.GetIdentity(TempPoID),
[Qty] [int] NULL,
[MobileBrandID] [int] NULL,
[MobileID] [int] NULL
I have never used this so I can't tell you if it is a good thing to do or not. It might be devastating for your query performance, I just don't know.
I agree with comments and you should change the GUID column name. If you really have to store the TempPoID column twice use computed column. Example:
CREATE TABLE #tblTempPo (
[TempPoID] [int] IDENTITY(1,1) NOT NULL,
[second_id] AS TempPoID,
[Qty] [int] NULL,
[MobileBrandID] [int] NULL,
[MobileID] [int] NULL
)
INSERT INTO #tblTempPo (Qty, MobileBrandID, MobileID) VALUES
(10, 10, 15), (20, 23, 45), (55, 23, 12), (10, 1, 1)
SELECT * FROM #tblTempPo
DROP TABLE #tblTempPo
If you need more complicated approach - use trigger.
It's all no good idea, but if you REALLY need it, try trigger:
create table [dbo].[tblTempPo](
[TempPoID] [int] identity(1,1) NOT NULL,
[guid] int,
[Qty] [int] NULL,
[MobileBrandID] [int] NULL,
[MobileID] [int] NULL
)
go
create trigger [dbo].[tblTempPo_Trig] on [dbo].[tblTempPo] instead of insert as
declare #cnt int
select #cnt = count(*)
from [dbo].[tblTempPo] with(nolock)
insert into [dbo].[tblTempPo]([guid], [Qty], [MobileBrandID], [MobileID])
select #cnt+row_number() over (order by [TempPoID]), [Qty], [MobileBrandID], [MobileID] from inserted
go
insert into [dbo].[tblTempPo]([Qty], [MobileBrandID], [MobileID]) values (0, 0,0), (0, 0,0), (0, 0,0), (0, 0,0)
insert into [dbo].[tblTempPo]([Qty], [MobileBrandID], [MobileID]) values (0, 0,0), (0, 0,0), (0, 0,0), (0, 0,0)
select * from [dbo].[tblTempPo]
go
drop table [dbo].[tblTempPo]
go

Resources