Table driven payment schedule - sql

My Payment Schedule holds a row for a payment schedule that gets run on a specific day, based on an 'effective date'.
CREATE TABLE [dbo].[PaymentSchedule] (
[PaymentScheduleId] INT IDENTITY (1, 1) NOT NULL,
[EffectiveDate] DATE NOT NULL,
[EffectiveDays] INT NOT NULL,
CONSTRAINT [pk_PaymentSchedule] PRIMARY KEY CLUSTERED ([PaymentScheduleId] ASC)
);
So, if the effectivedate is '01-JAN-2013', and the 'EffectiveDays' is 7, then payment get made on the 1st of January, and then every 7 days after that. So, on the 8th of January, a payment must be made. On the 15th, a payment must be made.. etc etc.
If the effectivedate was '01-JAN-2013', and the EffectiveDays was 20, then the first payment is the 1st of Jan, the next payment day is the 21th of Jan, and the next after that would be 9th Feb, 2013.. etc etc.
What I am trying to do, is make a function that uses the above table, or a stored proc for that matter, that returns 'Next Payment Date', and takes in a DATE type. So, based on the date passed in, what is the next payment date? And also, 'Is today a payment date'.
Can this be done efficiently? In 7 years time, would I be able to tell if a date is a payment day, for example?

Your description of the problem is wrong. If the first payment is on Jan 1, the subsequent payments would be on the Jan 8, Jan 15 and so on.
The answer to your question about the current date is datediff() along with the modulus operator. To see if today is a payment date, take the difference and see if it is an exact multiple of the period you are looking at:
select getdate()
from PaymentSchedule ps
where datediff(day, ps.EffectiveDate, getdate()) % ps.EffectiveDays = 0;
The % is the modulus operator that takes the remainder between two values. So, 3%2 is 1 and 10%5 is 0.
For the next date, the answer is similar:
select dateadd(day,
ps.EffectiveDays - datediff(day, ps.EffectiveDate, today) % ps.EffectiveDays,
today) as NextDate
from PaymentSchedule ps cross join
(select cast(getdate() as date) as today) const
I've structured this as a subquery that defines the current date as today. This makes it easier to substitute in any other date that you might want.

You can use the method DATEDIFF(datepart, startdate, enddate) setting as datepart "dayofyear" to the result of this method will give you the number of days between the two dates, and divide as Modulo (%) this result by the EffectiveDays and if the result is 0 there is payment day; and if not you will have the days passed from the last payment day (if you sustract it from the EffectivedDays you must have the restant days to the next payment day).
Here is some doc for the DATEDIFF method:
http://msdn.microsoft.com/en-us/library/ms189794.aspx

I may be answering the wrong question, but I think the following code returns payment schedules that hit the selected payment date, if that's what you're looking for?
IF OBJECT_ID('tempdb..#PaymentSchedules') IS NOT NULL
DROP TABLE #PaymentSchedules;
CREATE TABLE #PaymentSchedules
( PaymentScheduleID INT NOT NULL IDENTITY(1,1)
CONSTRAINT PK_PaymentSchedules_PaymentScheduleID PRIMARY KEY
, EffectiveDate DATE NOT NULL
, EffectiveDays INT NOT NULL )
;
INSERT #PaymentSchedules (EffectiveDate, EffectiveDays)
VALUES
('20120401', 3)
, ('20120401', 2)
, ('20120401', 1)
, ('20120401', 7)
, ('20120401', 14)
;
DECLARE #PaymentDate DATE = '20140310';
WITH myCTE AS
(
SELECT PaymentScheduleID, PaymentDate = EffectiveDate, EffectiveDays
FROM #PaymentSchedules
UNION ALL
SELECT PaymentScheduleID, PaymentDate = DATEADD(DAY, EffectiveDays, PaymentDate), EffectiveDays
FROM myCTE
WHERE DATEADD(DAY, EffectiveDays, PaymentDate) <= #PaymentDate
)
SELECT * FROM myCTE
WHERE PaymentDate = #PaymentDate
OPTION (MAXRECURSION 10000)
;

Related

Split Hours into Multiple Months using SQL

CREATE TABLE EventLog
(
EventID INT
, EventName VARCHAR(50) NOT NULL
, EventStartDateTime DATETIME NOT NULL
, EventEndDateTime DATETIME NULL
)
INSERT INTO EventLog(EventID, EventName, EventStartDateTime, EventEndDateTime)
VALUES(100, 'Planting', '20210620 10:34:09 AM','20211018 10:54:49 PM')
,(200, 'Foundation', '20200420 10:34:09 AM','20211018 10:54:49 PM')
,(300, 'Seeding', '20210410 10:27:19 AM','')
,(400, 'Spreading', '20220310 10:24:09 PM','')
I have a requirement to split hours into multiple months and even years depending on the length of the event. In some cases, the event may have an end date but if the event is still ongoing, there will be no end date.
The result or output of the solution is to be held by another table:
CREATE TABLE EventSummary
(
EventID INT
, EventName VARCHAR(50) NOT NULL
, [Year] INT
, [MonthName] VARCHAR(25)
, [Hours] DECIMAL(12,2)
)
The image above is the output of the first row.
If the event runs over multiple years, the values should be spread across the multiple years and months likewise.
In cases where there is no end date I am to use GETUTCDATE() to do the calculation.
Some events span across months or event years. I would like to be able to break down the total duration into individual month's duration (or individual duration by month) in hours respectively and populate it into a table
Consider that I have an event with start and end date: '20210620 10:34:09 AM','20211018 10:54:49 PM'
For the first month which basically is not a full month, I am to calculate the remaining hours of that month and store it against that month.
I do the same for the next month. If the event runs for the entire month which is now the month of July, I store the entire hours for that month which is 744 hours against July. I keep doing that till the end of the event. But if the event is still open(blank or empty) I use
GETUTCDATE() as the end date
The sum of Hours is grouped by EventID, EventName, Year and Months
It is expected that the first and or last month may be decimals as they may not be fully formed.
I have tried to work this out but do not know how to get the best result with SQL Server.
I kindly will appreciate your help with this.
Thanks
If I understand correctly, you can try to use CTE recursive, get each month startdate then use antoher cte2 get the starttime and endtime between each month.
;WITH CTE AS (
SELECT EventID,EventName,EventStartDateTime,IIF(EventEndDateTime = '',GETUTCDATE(),EventEndDateTime) EventEndDateTime
FROM EventLog
UNION ALL
SELECT EventID,EventName, DATEADD(month, DATEDIFF(month, 0, DATEADD(month , 1 , EventStartDateTime)), 0) , EventEndDateTime
FROM CTE
WHERE DATEADD(month, DATEDIFF(month, 0, DATEADD(month , 1 , EventStartDateTime)), 0) <= EventEndDateTime
), CTE2 AS (
SELECT EventID,EventName,EventStartDateTime,LEAD(EventStartDateTime,1,EventEndDateTime) OVER(PARTITION BY EventID,EventName ORDER BY EventStartDateTime) n_EventStartDateTime
FROM CTE
)
INSERT INTO EventSummary(EventID,EventName,Year,MonthName,Hours)
SELECT EventID,EventName,YEAR(EventStartDateTime),DATENAME(MONTH,EventStartDateTime),DATEDIFF(second, EventStartDateTime, n_EventStartDateTime) / 3600.0
FROM CTE2
option (maxrecursion 0)
sqlfiddle

how do I create a calculated field that returns days remaining till end of FISCAL_QUARTER?

Current output with no DAYS_LEFT_IN_QUARTERI am new to using Snowflake and was tasked to create a Calendar Dimension table that would aid in reporting weekly / monthly /quarterly reports. I am confused on how to return days remaining in the FISCAL_QUARTER. Q1 spans from Feb - Apr.
Attached below is the code I have been writing to generate the dates projecting 14 years in the future.
--Set the start date and number of years to produce
SET START_DATE = '2012-01-01';
SET NUMBER_DAYS = (SELECT TRUNC(14 * 365));
--Set parameters to force ISO
ALTER SESSION SET WEEK_START = 1, WEEK_OF_YEAR_POLICY = 1;
WITH CTE_MY_DATE AS (
SELECT DATEADD(DAY, SEQ4(), $START_DATE) AS MY_DATE
FROM TABLE(GENERATOR(ROWCOUNT=>$NUMBER_DAYS)) -- Number of days after reference date in previous line
)
SELECT
MY_DATE::date
,YEAR(MY_DATE) AS YEAR
,MONTH(MY_DATE) AS MONTH
,MONTHNAME(MY_DATE) AS MONTH_ABBREVIATION
,DAY(MY_DATE)
,DAYOFWEEK(MY_DATE)
,WEEKOFYEAR(MY_DATE)
,DAYOFYEAR(MY_DATE)
,YEAR(ADD_MONTHS(DATE_TRUNC('month', MY_DATE),11)) AS FISCAL_YEAR
,CONCAT('Q', QUARTER(ADD_MONTHS(DATE_TRUNC('month', MY_DATE),11))) AS FISCAL_QUARTER
,MONTH(ADD_MONTHS(DATE_TRUNC('month', MY_DATE),11)) AS FISCAL_MONTH
FROM CTE_MY_DATE
;
firstly your generator will get gaps, as SEQx() function are allowed to have gaps, so you need to use SEQx() as the OVER BY of a ROW_NUMBER like so:
WITH cte_my_date AS (
SELECT DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY SEQ4()), $START_DATE) AS my_date
FROM TABLE(GENERATOR(ROWCOUNT=>$NUMBER_DAYS)) -- Number of days after reference date in previous line
)
and days left in quarter, is the day truncated to quarter, +1 quarter, date-diff in days to day:
,DATEDIFF('days', my_date, DATEADD('quarter', 1, DATE_TRUNC('quarter', my_date))) AS days_left_in_quarter
How's this? You can copy/paste the code straight into snowflake to test.
Using last_day() tends to make it look a little tidier :-)
WITH CTE_MY_DATE AS (
SELECT DATEADD(DAY, SEQ4(), current_date()) AS MY_DATE
FROM TABLE(GENERATOR(ROWCOUNT=>300)))
SELECT
MY_DATE::date
,YEAR(last_day(my_date,year)) AS FISCAL_YEAR
,concat('Q',quarter(my_date)) AS FISCAL_QUARTER
,datediff(d, my_date, last_day(my_date,quarter)) AS
DAYS_LEFT_IN_QUARTER
FROM CTE_MY_DATE

How to count number of work days and hours extracting public holidays between two dates in SQL

I am new to SQL and stuck in some complex query.
What am I trying to achieve?
I want to calculate following two types of total days between two timestamp fields.
Number of Working Days (Excluding Weekend & Public Holidays)
Number of Total Days (Including Weekend & Public Holidays)
Calculation Condition
If OrderDate time is <= 12:00 PM then start count from 0
If OrderDate Time is > 12:00 PM then start count from -1
If Delivery Date is NULL then count different till Today's Date
Data Model
OrderDate & DeliveryDate resides in 'OrderTable'
PublicHolidayDate resides 'PublicHolidaysTable'
As with many tasks in SQL, this could be solved in multiple ways.
You can use COUNT aggregate operations on the date range with the BETWEEN operator to give you aggregate totals of the weekend days and holidays from a start date (OrderDate) to an end date (DeliveryDate).
This functionality can be combined with CTEs (Common Table Expressions) to give you the end result set you are looking for.
I've put together a query that illustrates one way you could go about doing it. I've also put together some test data and results to illustrate how the query works.
DECLARE #DateRangeBegin DATETIME = '2016-01-01'
, #DateRangeEnd DATETIME = '2016-07-01'
DECLARE #OrderTable TABLE
(OrderNum INT, OrderDate DATETIME, DeliveryDate DATETIME)
INSERT INTO #OrderTable VALUES
(1, '2016-02-12 09:30', '2016-03-01 13:00')
, (2, '2016-03-15 13:00', '2016-03-30 13:00')
, (3, '2016-03-22 14:00', NULL)
, (4, '2016-05-06 10:00', '2016-05-19 13:00')
DECLARE #PublicHolidaysTable TABLE
(PublicHolidayDate DATETIME, Description NVARCHAR(255))
INSERT INTO #PublicHolidaysTable VALUES
('2016-02-15', 'President''s Day')
, ('2016-03-17', 'St. Patrick''s Day')
, ('2016-03-25', 'Good Friday')
, ('2016-03-27', 'Easter Sunday')
, ('2016-05-05', 'Cinco de Mayo')
Some considerations you may of already thought of are that you don't want to count both weekend days and holidays that occur on a weekend, unless your company observes the holiday on the next Monday. For simplicity, I've excluded any holiday that occurs on a weekend day in the query.
You'll also want to limit this type of query to a specific date range.
The first CTE (cteAllDates) gets all dates between the start and end date range.
The second CTE (cteWeekendDates) gets all weekend dates from the first CTE (cteAllDates).
The third CTE (ctePublicHolidays) gets all holidays that occur on weekdays from your PublicHolidaysTable.
The last CTE (cteOrders) fulfills the requirement that the count of total days and working days must begin from the next day if the OrderDate is after 12:00PM and the requirement that the DeliveryDate should use today's date if it is null.
The select statement at the end of the CTE statements gets your total day count, weekend count, holiday count, and working days for each order.
;WITH cteAllDates AS (
SELECT 1 [DayID]
, #DateRangeBegin [CalendarDate]
, DATENAME(dw, #DateRangeBegin) [NameOfDay]
UNION ALL
SELECT cteAllDates.DayID + 1 [DayID]
, DATEADD(dd, 1 ,cteAllDates.CalendarDate) [CalenderDate]
, DATENAME(dw, DATEADD(d, 1 ,cteAllDates.CalendarDate)) [NameOfDay]
FROM cteAllDates
WHERE DATEADD(d,1,cteAllDates.CalendarDate) < #DateRangeEnd
)
, cteWeekendDates AS (
SELECT CalendarDate
FROM cteAllDates
WHERE NameOfDay IN ('Saturday','Sunday')
)
, ctePublicHolidays AS (
SELECT PublicHolidayDate
FROM #PublicHolidaysTable
WHERE DATENAME(dw, PublicHolidayDate) NOT IN ('Saturday', 'Sunday')
)
, cteOrders AS (
SELECT OrderNum
, OrderDate
, CASE WHEN DATEPART(hh, OrderDate) >= 12 THEN DATEADD(dd, 1, OrderDate)
ELSE OrderDate
END [AdjustedOrderDate]
, CASE WHEN DeliveryDate IS NOT NULL THEN DeliveryDate
ELSE GETDATE()
END [DeliveryDate]
FROM #OrderTable
)
SELECT o.OrderNum
, o.OrderDate
, o.DeliveryDate
, DATEDIFF(DAY, o.AdjustedOrderDate, o.DeliveryDate) [TotalDayCount]
, (SELECT COUNT(*) FROM cteWeekendDates w
WHERE w.CalendarDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate) [WeekendDayCount]
, (SELECT COUNT(*) FROM ctePublicHolidays h
WHERE h.PublicHolidayDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate) [HolidayCount]
, DATEDIFF(DAY, o.AdjustedOrderDate, o.DeliveryDate)
- (SELECT COUNT(*) FROM cteWeekendDays w
WHERE w.CalendarDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate)
- (SELECT COUNT(*) FROM ctePublicHolidays h
WHERE h.PublicHolidayDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate) [WorkingDays]
FROM cteOrders o
WHERE o.OrderDate BETWEEN #DateRangeBegin AND #DateRangeEnd
OPTION (MaxRecursion 500)
Results from the above query using the test data...
What I'd probably do is simplify the above by adding a Calendar table populated with sufficiently wide date ranges. Then I'd take some of the CTE statements and turn them into views.
I think specifically valuable to you would be a view that gets you the work days without weekends or holidays. Then you could just simply get the date difference between the two dates and count the work days in the same range.

Query to check number of records created in a month.

My table creates a new record with timestamp daily when an integration is successful. I am trying to create a query that would check (preferably automated) the number of days in a month vs number of records in the table within a time frame.
For example, January has 31 days, so i would like to know how many days in january my process was not successful. If the number of records is less than 31, than i know the job failed 31 - x times.
I tried the following but was not getting very far:
SELECT COUNT (DISTINCT CompleteDate)
FROM table
WHERE CompleteDate BETWEEN '01/01/2015' AND '01/31/2015'
Every 7 days the system executes the job twice, so i get two records on the same day, but i am trying to determine the number of days that nothing happened (failures), so i assume some truncation of the date field is needed?!
One way to do this is to use a calendar/date table as the main source of dates in the range and left join with that and count the number of null values.
In absence of a proper date table you can generate a range of dates using a number sequence like the one found in the master..spt_values table:
select count(*) failed
from (
select dateadd(day, number, '2015-01-01') date
from master..spt_values where type='P' and number < 365
) a
left join your_table b on a.date = b.CompleteDate
where b.CompleteDate is null
and a.date BETWEEN '01/01/2015' AND '01/31/2015'
Sample SQL Fiddle (with count grouped by month)
Assuming you have an Integers table*. This query will pull all dates where no record is found in the target table:
declare #StartDate datetime = '01/01/2013',
#EndDate datetime = '12/31/2013'
;with d as (
select *, date = dateadd(d, i - 1 , #StartDate)
from dbo.Integers
where i <= datediff(d, #StartDate, #EndDate) + 1
)
select d.date
from d
where not exists (
select 1 from <target> t
where DATEADD(dd, DATEDIFF(dd, 0, t.<timestamp>), 0) = DATEADD(dd, DATEDIFF(dd, 0, d.date), 0)
)
Between is not safe here
SELECT 31 - count(distinct(convert(date, CompleteDate)))
FROM table
WHERE CompleteDate >= '01/01/2015' AND CompleteDate < '02/01/2015'
You can use the following query:
SELECT DATEDIFF(day, t.d, dateadd(month, 1, t.d)) - COUNT(DISTINCT CompleteDate)
FROM mytable
CROSS APPLY (SELECT CAST(YEAR(CompleteDate) AS VARCHAR(4)) +
RIGHT('0' + CAST(MONTH(CompleteDate) AS VARCHAR(2)), 2) +
'01') t(d)
GROUP BY t.d
SQL Fiddle Demo
Explanation:
The value CROSS APPLY-ied, i.e. t.d, is the ANSI string of the first day of the month of CompleteDate, e.g. '20150101' for 12/01/2015, or 18/01/2015.
DATEDIFF uses the above mentioned value, i.e. t.d, in order to calculate the number of days of the month that CompleteDate belongs to.
GROUP BY essentially groups by (Year, Month), hence COUNT(DISTINCT CompleteDate) returns the number of distinct records per month.
The values returned by the query are the differences of [2] - 1, i.e. the number of failures per month, for each (Year, Month) of your initial data.
If you want to query a specific Year, Month then just simply add a WHERE clause to the above:
WHERE YEAR(CompleteDate) = 2015 AND MONTH(CompleteDate) = 1

Select Start date that is 60 business days old

I need the ability to select a start date that is 60business days prior to the current date. I have a calendar built with dates and business dates, etc. My current (and unsuccessful) method is below:
DECLARE #Mode int
SET #Mode = 0
SET #StartDate = CASE WHEN #Mode = 0
THEN (SELECT BusDate FROM Leads.dbo.Calendar
WHERE Date = DATEADD(DAY,DATEDIFF (DAY,0,GETDATE())-60,0))
WHEN #Mode = 1
THEN DATEADD(mm,DATEDIFF (mm,0,GETDATE())-2,0)
END
This method goes back 60 calendar days, not 60 business days. I'm having a challenge with getting this down to Business days. Disregard the Mode = 1 portion (this calculates by month).
Any help would be appreciated.
The Calendar table has the following fields:
SELECT [Date]
,[MMDDYYYY]
,[Year]
,[QTR]
,[Month]
,[Week]
,[YTDDay]
,[QTDDay]
,[MTDDay]
,[WeekDayNbr]
,[Quarter]
,[MonthLName]
,[MonthName]
,[DayOfWeekS]
,[DayOfWeek]
,[KindOfDay]
,[Description]
,[Period]
,[YrMo]
,[YrWk]
,[StartDate]
,[EndDate]
,[BusPeriod]
,[Holiday]
,[NonBus]
,[BusDaysInMonth]
,[BusDay]
,[BusDaysRemain]
,[BusDate]
,[YYYYMMDD]
BusDay is the business day for a month (1, 2, 3, etc). There currently is no column that says 1 for yes and 0 for No to indicate it is a business day, although I could add that. The NonBus and Holiday fields operate that way: 1 is Holiday or Non-business day (holidays and weekends) and 0 is not.
We don't know much about your calendar table but you could do something like this. Use a derived table that only consists of business days that are less than today's date and give each a row number ordered by date desc. Then select the date from 60th row.
select busdate
from
(
select *,
row_number() over (order by date desc) as dayNo
from leads.dbo.calendar
where nonBus = 0 -- Only business days
and date < cast(getdate() as date)
) busDays
where dayno = 60