I am currently building reports off a legacy system in SQL. The data is stored like the below.
Which I have now unpivoted, so this information is stored as a row per contract.
Here is what I am struggling to achieve, I am trying to build a payment schedule table for these contracts. The majority of them are fine as the "FirstPaymentDate" is the full payment we expect, however for some of them we receive the payment every 30 days until the contract is paid off.
So, in the example above, there is a contractvalue of 1000 and a sold term of 2 years. 1000/24 months = £41.6 per month. I need to display this as a table with each date that we expect payment up until the last date, such as the below
Not going to lie, I am struggling how to approach this. I have tried searching but not sure what labels I would search for to cover my issue.
Can anyone help with an approach to solve this? Even if it's just pointing me in the right direction?
You could do this with a recursive CTE. The code below outputs the desired result set you have shown. Your question asks for payments every 30 days but the desired result set example shows every month. This example shows a payment every month. If you need every 30 days you can update to DATEADD(DAY, 30, PaymentDate).
DROP TABLE IF EXISTS #Contract;
CREATE TABLE #Contract
(
ContractId INT
, SoldTerm INT
, FirstPaymentDate DATE
, ContractValue MONEY
, PaymentTerms INT
);
INSERT INTO #Contract VALUES (10222, 2, '01/04/2022', 1000, 30);
WITH ContractDates AS
(
SELECT ContractId, FirstPaymentDate AS PaymentDate, 1 AS Payment, (ContractValue / (SoldTerm * 12)) AS PaymentAmount, (SoldTerm * 12) AS LastPayment
FROM #Contract
UNION ALL
SELECT ContractId, DATEADD(MONTH, 1, PaymentDate), Payment + 1 AS Payment, PaymentAmount, LastPayment
FROM ContractDates
WHERE Payment + 1 <= LastPayment
)
SELECT * FROM ContractDates;
DROP TABLE IF EXISTS #Contract;
Related
The problem statement is: I have a table (order_t) which has customer feedback (one column) and quarter number (as another column).
Using a CTE, I need to calculate the percentage of number of customer feedback in each category as well as the total number of customer feedback in each quarter.
After this happens, I need the percentage of different types of customer feedback (like good, bad, ok, very good, very bad) but using CTE.
How can I solve this statement?
I try to solve customer feedback as
WITH total_feedback AS
(
SELECT *
COUNT(CUSTOMER_FEEDBACK), QUARTER NUMBER
FROM
table1
GROUP BY
2
)
But I'm unable to calculate the first half portion, i.e. percentage of different types of customer feedback in each quarter using CTE.
How can I do that?
Find the file of the data
What you could do, and I'll keep the example as close to the code you provided as possible, is the following - using 2 CTE's:
WITH total_feedback AS (
SELECT COUNT(CUSTOMER_FEEDBACK) AS total_feedback, QUARTER_NUMBER
FROM table1
GROUP BY 2
),
category_feedback AS (
SELECT COUNT(CUSTOMER_FEEDBACK) AS feedback_count, CUSTOMER_FEEDBACK, QUARTER_NUMBER
FROM table1
GROUP BY 2, 3
)
SELECT
category_feedback.CUSTOMER_FEEDBACK,
category_feedback.QUARTER_NUMBER,
(feedback_count / total_feedback.total_feedback) * 100 AS feedback_percentage
FROM category_feedback
INNER JOIN total_feedback
ON category_feedback.QUARTER_NUMBER = total_feedback.QUARTER_NUMBER
My product team has asked if I could create a very crude forecasting data table for them to work with. I have a pretty good idea on a lot of the steps I need to take, but I am stuck on figuring how to to calculate the Inventory Quantity for tomorrow, the next day, etc.
In my database, I am able to see our current quantity on hand. I would call that starting balance (for today). I am then going to create an average usage field and that will be my estimated daily sales. I will then take the starting balance - estimated daily sales = ending balance. I can do that for today, my question is how do I roll that formula forward for the next 120 days
You can use a recursive CTE to generate number from 0 to 120 and then calculate the day and balance with them.
DECLARE #estimated_daily_sales integer = 2;
DECLARE #starting_balance integer = 12345;
WITH
cte AS
(
SELECT 0 i
UNION ALL
SELECT i + 1 i
FROM cte
WHERE i + 1 <= 120
)
SELECT dateadd(day, i, convert(date, getdate())) day,
#starting_balance - i * #estimated_daily_sales balance
FROM cte
OPTION (MAXRECURSION 120);
db<>fiddle
In a MS Access DB I have information about clients' arrival and departure dates. Based on this I would like to calculate the number of clients that will be visiting per week.
Consider the example below. Peter arrives in the first week of January and leaves on the third week (weeks start on Sunday). Mary on the other hand arrives and leaves in the first week.
I would like the output to be as shown below, with the week number in the first column and the total number of guests in the second.
What is the best way of achieving this please?
My solution with german date format, test with ORACLE. I build some calendar table (tw_test_week) for every week to join it.
CREATE TABLE tw_test_client (
client VARCHAR2(10),
arrival DATE,
departure DATE
);
INSERT INTO tw_test_client VALUES
( 'Peter', to_date('01.01.2018','DD.MM.YYYY'), to_date('11.01.2018','DD.MM.YYYY'));
INSERT INTO tw_test_client VALUES
( 'Mary', to_date('01.01.2018','DD.MM.YYYY'), to_date('01.02.2018','DD.MM.YYYY'));
CREATE TABLE tw_test_week (
weekid INT,
started DATE,
ended DATE
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('01.01.2018','DD.MM.YYYY'),'WW'),
to_date('01.01.2018','DD.MM.YYYY'),
to_date('07.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('08.01.2018','DD.MM.YYYY'),'WW'),
to_date('08.01.2018','DD.MM.YYYY'),
to_date('14.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('15.01.2018','DD.MM.YYYY'),'WW'),
to_date('15.01.2018','DD.MM.YYYY'),
to_date('21.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('22.01.2018','DD.MM.YYYY'),'WW'),
to_date('22.01.2018','DD.MM.YYYY'),
to_date('28.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('29.01.2018','DD.MM.YYYY'),'WW'),
to_date('29.01.2018','DD.MM.YYYY'),
to_date('04.02.2018','DD.MM.YYYY')
);
SELECT w.weekid, COUNT(*)
FROM tw_test_week w
JOIN tw_test_client c
ON w.started BETWEEN c.arrival and c.departure
GROUP BY w.weekid
ORDER BY w.weekid;
Result
WEEKID COUNT
1 2
2 2
3 1
4 1
5 1
Create a table containing the numbers 1 to 53. This table in my query is called WeekNumTable and contains a single field with each week number listed.
SELECT WeekNum
, COUNT(WeekNum) AS TotalClients
FROM WeekNumTable INNER JOIN ClientTable ON
WeekNumTable.WeekNum>=DatePart("ww",ClientTable.Arrival-Weekday(ClientTable.Arrival,1)+7) AND
WeekNumTable.WeekNum<=DatePart("ww",ClientTable.Departure-Weekday(ClientTable.Departure,1)+7)
GROUP BY WeekNum
Your example confused me a little as you list week 3 as having 1 client, while the data table doesn't.
Edit: The one problem that I can see with this is that if you do want to look at the next 100 weeks you'll need something to separate the years otherwise week 1 from both years will be lumped together.
You can use a series of queries to obtain this.
First, create a query named Ten:
SELECT DISTINCT Abs([id] Mod 10) AS N
FROM MSysObjects;
Then you can create a query, ClientDays, that lists all your dates between first arrival and latest departure:
SELECT DISTINCT
[Ten_0].[N]+[Ten_1].[N]*10+[Ten_2].[N]*100 AS Id,
DateAdd("d",[Ten_0].[N]+[Ten_1].[N]*10+[Ten_2].[N]*100,[StartDate]) AS [Date]
FROM
Ten AS Ten_0,
Ten AS Ten_1,
Ten AS Ten_2,
(Select
Min([Arrival]) As StartDate,
DateDiff("d", Min([Arrival]), Max([Departure])) As Days
From
ClientDates) AS T
WHERE
((([Ten_0].[N]+[Ten_1].[N]*10+[Ten_2].[N]*100)<=[Days])
AND
((Ten_0.N)<=[Days]\1)
AND
((Ten_1.N)<=[Days]\10)
AND
((Ten_2.N)<=[Days]\100));
Use this in yet a query, ClientWeeks, to find the week numbers:
SELECT
Year([Date]) AS [Year],
DatePart("ww",[Date]) AS Week,
ClientDates.Client
FROM
ClientDays,
ClientDates
WHERE
ClientDays.Date Between [Arrival] And [Departure]
GROUP BY
Year([Date]),
DatePart("ww",[Date]),
ClientDates.Client;
Finally, count the clients:
SELECT
ClientWeeks.Year,
ClientWeeks.Week,
Count(ClientWeeks.Client) AS TotalClients
FROM
ClientWeeks
GROUP BY
ClientWeeks.Year,
ClientWeeks.Week;
Please note, that you will have trouble counting around New Year as you intend to use an inconsistent week numbering method.
The only week number that is unambiguous is the ISO-8601 system of yyyy-ww because the first and/or last week will cross the calendar year boundaries.
Leave a note if you wish to implement this as it cannot be done with native VBA functions; custom functions must be used, but I don't wish to post them here if you will not use them.
EDIT: I updated this post cause I had to change my table, I had to change my Quantity from an INT to varchar to accommodate for decimals and now the below answers won't work, is varchar the best option for this?
Sorry, this might be a bit of a simple question for someone who knows what they're doing but I'm super new to SQL.
I've created this table (60 results in total)
However these are just the results for the first month of the year (January)
all further results are the same except the quantity is a 1.5% increase per month, so in February (2016-02-01) the first employee's six quantity's would be
111
165
463
156
99
63
However I'm having trouble getting my head around using scripts to enter results. I could manually enter each month but that would take me quite a few hours as there's 60 results for each month. So I was thinking maybe I could copy the current contents of the table into a temporary variable then create some sort of loop that has a 1.5% increase for each month and also changes the date to the relevant month, but I have absolutely no idea how to go about doing this.
If anyone could help me out with how I could go about doing this I'd be super grateful, I'm so stumped and I don't even know where to start
You can add the next month by doing:
insert into t(employee_id, month, product_id, quantity)
select employee_id, dateadd(month, 1, [month]), product_id, quantity*(1 + 0.015)
from t
where t.[month] = '2016-01-01';
I would recommend that you just repeat this 11 times, changing the where clause each time. You can set up a query to do all 11 months at the same time, but it is probably not worth the effort.
You can do an INSERT/SELECT where your table is both source and target:
INSERT INTO tab
SELECT Sale_ID, Employee_ID, dateadd(month, 1, [Month]), Product_ID, Quantity * 1.015
FROM tab
WHERE [Month] = '2016-01-01'
Repeat this for 10 times and increase the number of months by one in each repetiton.
To do the inserts for all month in a single query you could add a CROSS JOIN to a series of numbers from 1 to 11:
INSERT INTO tab
SELECT Sale_ID, Employee_ID, dateadd(month, numbers.n, [Month]), Product_ID, Quantity * 1.015
FROM tab CROSS JOIN (select sale_ID AS n from tab where sale_ID between 1 and 11) AS numbers
WHERE [Month] = '2016-01-01'
I have looked high and low for this particular query and have not seen it.
We have two tables; Accounts table and then Visit table. I want to return the complete list of account names and fill in the corresponding fields with either null or the correct year etc. this data is used in a matrix report in SSRS.
sample:
Acounts:
AccountName AccountGroup Location
Brown Jug Brown Group Auckland
Top Shop Top Group Wellington
Super Shop Super Group Christchurch
Visit:
AcccountName VisitDate VisitAction
Brown Jug 12/12/2012 complete
Super Shop 1/10/2012 complete
I need to select weekly visits and show those that have had a complete visit and then the accounts that did not have a visit.
e.g.
Year Week AccountName VisitStatus for week 10/12/2012 should show
2012 50 Brown Jug complete
2012 50 Top Group not complete
2012 50 Super Shop not complete
e.g.
Year Week AccountName VisitStatus for week 1/10/2012 should show
2012 2 Brown Jug not complete
2012 2 Top Group not complete
2012 2 Super Shop complete
please correct me if am worng
select to_char(v.visitdate,'YYYY') year,
to_char(v.visitdate,'WW') WEAK,a.accountname,v.visitaction
from accounts a,visit v
where a.accountname=v.ACCCOUNTNAME
and to_char(v.visitdate,'WW')=to_char(sysdate,'WW')
union all
select to_char(sysdate,'YYYY') year,
to_char(sysdate,'WW') WEAK,a.accountname,'In Complete'
from accounts a
where a.accountname not in ( select v.ACCCOUNTNAME
from visit v where to_char(v.visitdate,'WW')=to_char(sysdate,'WW'));
The following answer assumes that
A) You want to see every week within a given range, whether any accounts were visited in that week or not.
B) You want to see all accounts for each week
C) For accounts that were visited in a given week, show their actual VisitAction.
D) For accounts that were NOT visited in a given week, show "not completed" as the VisitAction.
If all those are the case then the following query may do what you need. There is a functioning sqlfiddle example that you can play with here: http://sqlfiddle.com/#!3/4aac0/7
--First, get all the dates in the current year.
--This uses a Recursive CTE to generate a date
--for each week between a start date and an end date
--In SSRS you could create report parameters to replace
--these values.
WITH WeekDates AS
(
SELECT CAST('1/1/2012' AS DateTime) AS WeekDate
UNION ALL
SELECT DATEADD(WEEK,1,WeekDate) AS WeekDate
FROM WeekDates
WHERE DATEADD(WEEK,1,WeekDate) <= CAST('12/31/2012' AS DateTime)
),
--Next, add meta data to the weeks from above.
--Get the WeekYear and WeekNumber for each week.
--Note, you could skip this as a separate query
--and just included these in the next query,
--I've included it this way for clarity
Weeks AS
(
SELECT
WeekDate,
DATEPART(Year,WeekDate) AS WeekYear,
DATEPART(WEEK,WeekDate) AS WeekNumber
FROM WeekDates
),
--Cross join the weeks data from above with the
--Accounts table. This will make sure that we
--get a row for each account for each week.
--Be aware, this will be a large result set
--if there are a lot of weeks & accounts (weeks * account)
AccountWeeks AS
(
SELECT
*
FROM Weeks AS W
CROSS JOIN Accounts AS A
)
--Finally LEFT JOIN the AccountWeek data from above
--to the Visits table. This will ensure that we
--see each account/week, and we'll get nulls for
--the visit data for any accounts that were not visited
--in a given week.
SELECT
A.WeekYear,
A.WeekNumber,
A.AccountName,
A.AccountGroup,
IsNull(V.VisitAction,'not complete') AS VisitAction
FROM AccountWeeks AS A
LEFT JOIN Visits AS V
ON A.AccountName = V.AccountName
AND A.WeekNumber = DATEPART(WEEK,V.VisitDate)
--Set the maxrecursion number to a number
--larger than the number of weeks you will return
OPTION (MAXRECURSION 200);
I hope that helps.