Adjust date column for change over time - sql

This is an easy enough problem, but wondering if anyone can provide a more elegant solution.
I've got a table that consists of a date column (month end dates over time) and several value columns--say the price on a variety of stocks over time, one column for each stock. I'd like to calculate the change in value columns for each period represented in the date column (eg, a daily return from a table filled with prices).
My current plan is to join the table to itself and simply create a new column for the return as ret = b.price/a.price - 1. Code as follows:
select Date, Ret = (b.stock1/a.stock1 - 1)
from #temp a, #temp b
where datediff(day, a.Date,b.Date) between 25 and 35
order by a.Date
This works fine, BUT:
(1) I need to do this for, say, dozens of stocks--is there a good way to replicate the calculation without copying and pasting the return calculation and replacing 'stock1' with each other stock name?
(2) Is there a better way to do this join? I'm effectively doing a cross join at this point and only keeping entries that are adjacent (as defined by the datediff and range), but wondering if there's a better way to join a table like this to itself.
EDIT: Per request, data is in the form (my data has multiple price columns though):
Date Price
7/1/1996 349.22
7/31/1996 337.72
8/30/1996 343.70
9/30/1996 357.23
10/31/1996 364.07
11/29/1996 385.04
12/31/1996 383.68
And from that, I'd like to calculate return, to generate a table like this (again, with additional columns for the extra price columns that exist in the actual table):
Date Ret
7/31/1996 -0.03
8/30/1996 0.02
9/30/1996 0.04
10/31/1996 0.02
11/29/1996 0.06
12/31/1996 0.00

I would do the following. First, use the month and year to do the self join. I woudl recommend you take the year * 12 + the month number to get a unique value for each month and year combination. So, Jan of 2011 would have a value of (2011 * 12 + 1 = 24133) and December of 2010 would have a value of (2010 * 12 + 12 = 24132). This will allow you to accurately compare months without having to mess with rolling over from December to January. Next, you need to supply the calculations in the select clause. If you have the stock values in different columns then you will have to type them out as a.stock1-b.stock1, a.stock2-b.stock2, etc. The only way around that would be to massage the data to where there is only one stock value column and add a stockname column that would identify what stock that value is for.
Using the Month and Year for the self join, the following query should work:
select Date, Ret = (b.stock1/a.stock1 - 1)
from #temp a
inner join #temp b on (YEAR(a.Date) * 12) + MONTH(a.Date) = (YEAR(b.Date) * 12) + MONTH(b.Date) + 1
order by a.Date

Related

Can I calculate the difference between two dates if there is matching data in another column, MTBF calculation

I am attempting to find the mean time between failure of serialized equipment. I am using Access 2013 for my database and have Access 2016 also. My problem is that I have multiple entries for the serial numbers with Warranty start dates and job entry dates. I need to calculate the days between each job if the serial numbers match. Using > to find the dates and then calculate them worked great until I had more than one entry for a serial number.
I have tried using a join property between two tables but could not calculate the dates correctly with more than two entries
Below is my sql statement. I would rather use VBA to calculate this if possible. I can post my data with the criteria I am looking for, this is my first post.
SELECT qry_MTBF_Warranty_Date_Closed.BarCode,
tbl_Closed_Jobs.Cust_Part,
tbl_Closed_Jobs.Desc,
tbl_Closed_Jobs.Plant,
tbl_Closed_Jobs.Date_Ent,
qry_MTBF_Warranty_Date_Closed.Warranty_Activation,
qry_MTBF_Warranty_Date_Closed.Warr_WO,
qry_MTBF_Warranty_Date_Closed.Cust_Ref,
qry_MTBF_Warranty_Date_Closed.Rel_No,
(tbl_Closed_Jobs.Date_Ent-
[qry_MTBF_Warranty_Date_Closed].Warranty_Activation) AS MTBF,
"C" AS Status,
tbl_Closed_Jobs.Prty
FROM qry_MTBF_Warranty_Date_Closed
INNER JOIN tbl_Closed_Jobs ON qry_MTBF_Warranty_Date_Closed.BarCode = tbl_Closed_Jobs.BarCode
WHERE (((tbl_Closed_Jobs.Date_Ent)>=([qry_MTBF_Warranty_Date_Closed].[Warranty_Activation])) AND ((tbl_Closed_Jobs.Prty) Not Like "1C*"))
ORDER BY tbl_Closed_Jobs.Cust_Part;
I expect to only have an output of days where my serial(Barcodes) match and the Priority codes are no a 1C. I can post an Excel file if that would help.
Thanks
So, I have SQL to find the number of days between the two dates if there are only two matching barcodes. I need to know the difference of days between each entry. The below sql will give me the Min and Max days and the difference of them but some barcodes will have multiple entries and I need to know the difference between each entry if the barcodes match..
SELECT t1.BarCode, max(abs(DateDiff("d", t3.Date_,t1.Date_Warranty))) AS DateDifference
FROM tbl_MTBF_Multiple_Entries AS t1 INNER JOIN (SELECT t2.BarCode as BarCode_, Max(t2.Date_Ent) as Date_
FROM tbl_MTBF_Multiple_Entries t2
GROUP BY t2.BarCode
) AS t3 ON t1.BarCode=t3.Barcode_
WHERE t1.Date_Warranty < t3.Date_
GROUP BY t1.BarCode;
Try using built-in vba DateDiff()
Function DateDiff(Interval As String, Date1, Date2, [FirstDayOfWeek As VbDayOfWeek = vbSunday], [FirstWeekOfYear As VbFirstWeekOfYear = vbFirstJan1])
Member of VBA.DateTime
Example:
' Return the number of days remaining in the taxation year from the present date.
' Positive value <= 365 = taxation year has # days left.
' Positive value > 365, date is before taxation year.
' Negative value = beyond taxation year
Public Function daysRemainingInTaxYear() As Integer
Dim myTaxationYear
Dim myEoY As Variant
myTaxationYear = DLookup("TaxationYear", "myParametersT", "ID=1")
myEoY = "31/12/" & myTaxationYear
daysRemainingInTaxYear = DateDiff("d", Now(), myEoY)
End Function

Calculate stdev over a variable range in SQL Server

Table format is as follows:
Date ID subID value
-----------------------------
7/1/1996 100 1 .0543
7/1/1996 100 2 .0023
7/1/1996 200 1 -.0410
8/1/1996 100 1 -.0230
8/1/1996 200 1 .0121
I'd like to apply STDEV to the value column where date falls within a specified range, grouping on the ID column.
Desired output would like something like this:
DateRange, ID, std_v
1 100 .0232
2 100 .0323
1 200 .0423
One idea I've had that works but is clunky, involves creating an additional column (which I've called 'partition') to identify a 'group' of values over which STDEV is taken (by using the OVER function and PARTITION BY applied to 'partition' and 'ID' variables).
Creating the partition variable involves a CASE statement prior where a given record is assigned a partition based on its date falling within a given range (ie,
...
, partition = CASE
WHEN date BETWEEN '7/1/1996' AND '10/1/1996' THEN 1
WHEN date BETWEEN '10/1/1996' AND '1/1/1997' THEN 2
...
Ideally, I'd be able to apply STDEV and the OVER function partitioning on the variable ID and variable date ranges (eg, say, trailing 3 months for a given reference date). Once this works for the 3 month period described above, I'd like to be able to make the date range variable, creating an additional '#dateRange' variable at the start of the program to be able to run this for 2, 3, 6, etc month ranges.
I ended up coming upon a solution to my question.
You can join the original table to a second table, consisting of a unique list of the dates in the first table, applying a BETWEEN clause to specify desired range.
Sample query below.
Initial table, with columns (#excessRets):
Date, ID, subID, value
Second table, a unique list of dates in the previous, with columns (#dates):
Date
select d.date, er.id, STDEV(er.value)
from #dates d
inner join #excessRet er
on er.date between DATEADD(m, -36, d.date) and d.date
group by d.date, er.id
order by er.id, d.date
To achieve the desired next step referenced above (making range variable), simply create a variable at the outset and replace "36" with the variable.

SQL Server : list months between range

I am trying to calculate the values of financial hedges but I want the input to be as simple as possible. So for example, I have a contract with a quantity of 1,000 per month from 2/1/2017 to 12/31/2018 (it won't always be this date range, it could be 1 month or 3+ years) with a strike price of $3. I want to enter just 1 row of data into 4 columns: [Volume],[Start],[End],[Strike].
The issue is I need to multiply the 2017 data by one price and the 2018 by a different price. The easy answer would be to enter a row for 2017 and a row for 2018 but I don't want to do this because I may have 50 or more contracts to enter in and I want to do as little input as possible.
I would use two columns [Year],[Price] from a price table. It would look like [2017],[4.50] and [2018],[5.25]. I can easily modify this to be monthly instead of annual if it helps simplify things.
I need the final calculation to be along the lines of:
2017: 11,000 * (3 - 4.50) = -$16,500
2018: 12,000 * (3 - 5.25) = -$27,000
Total value = -$43,500
So my question is, how can I get a count of months for each year in the range?
I would like the output to be something like
2017, 11
2018, 12
Calendar tables can be helpful in situations like this, there are many sample scripts out there, here is one: https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/
You could also simply use year and month in your price table and join on the range, an Integer type Year_Month_Int field could be used '201601, 201602, 201603...`
Then:
SELECT *
FROM contracts c
JOIN prices p
ON p.Year_Month_Int BETWEEN (YEAR(c.start)*100)+MONTH(c.start) AND (YEAR(c.end)*100)+MONTH(c.end)
If you included the integer version year-month for start and end in the contracts table you could simplify the JOIN criteria.
Another variant on how to count the number of months in a range would be:
Setup:
create table soTest(
id integer not null auto_increment,
start_date datetime,
end_date datetime,
primary key (id)
);
insert into soTest (start_date, end_date) values
('2017-02-01 00:00:00','2018-12-31 00:00:00');
Query:
select distinct case when extract(year from start_date) = yr
then format(datediff(concat(yr,'-12-31'), start_date)/30,0)
else format(datediff(end_date, concat(yr,'-01-01'))/30,0) end as qtyMonths,
yr
from (select s.*, extract(year from start_date) yr from soTest s
union all
select s.*, extract(year from end_date) yr from soTest s
) a;
Result:
qtyMonths yr
11 2017
12 2018
Be aware that this technique will only count the months in case of one registry. To get data from your contracts you would have to JOIN this with your dataset tables.

How to calculate difference between two rows in a date interval?

I'm trying to compare data from an Access 2010 database based on a date interval. Example I have items from various purchase orders and I want to maintain the history of these item's delivery to a warehouse. So my purchase order has a request for a quantity of 10 of a material, for example, and it can be partially delivered in many deliveries and I want to know how this delivery varied in a date interval. To fill the date field the criteria used is the following: if the item had an update in the QtyPending field, I copy the current row deactivating it with a booelan field, create a new entry with the current update date updating the QtyPending field, so the active record is the actual state of the item. So I have a table that holds informations about these items like that
PO POItem QtyPending Date Active
4500000123 10 10 01/09/2014 FALSE
4500000123 10 8 05/09/2014 TRUE
4500000122 30 5 03/09/2014 FALSE
4500000122 30 1 04/09/2014 TRUE
With this example, for the first item, it means that from date 01/09 to 04/09 the QtyPending field didn't suffer a variation, meaning that the supplier didn't make any delivery to me, but from 01/09 to 05/08 he delivered me a qty of 2 of a material. For the second one, from date 03/09 to 04/09 the supplier delivered me a qty of 4 of a material. So, if I were to be making a report query from 02/09/2014 to 04/09/2014, the expected output is like this:
PO POItem QtyDelivered
4500000123 10 0
4500000122 30 4
And a report from 31/08/2014 to 10/09/2014, would have this output
PO POItem QtyDelivered
4500000123 10 2
4500000122 30 4
I'm not coming up with a query to make this report. Can anyone help me?
There are many ways of solving this. The easiest one would be to simply make a query of all the necessary records between two dates, loop over them and insert into a temporary table the result. This temporary table can then be the source of your report. A lot of people will scream at you for not using a big query instead but getting the result that you want in the fastest and simplest way should be your priority.
Your problem with your schema is that you don't have the QtyDelivered stored for each record. If you would have it, it would be an easy thing to sum over it in order to get needed result. By not storing this value, you have transformed a simple and fast query into a much harder and slower one because you need to recalculate this value in some way or other and you must do this without forgetting the fact that it's possible to have more than two records.
For calculating this value, you can either use a sub-query to retrieve the value from the previous row or a Left join do to the same. Once you have this value, you can subtract these two to get the needed difference; allowing for the possibility of Null value if there is no previous row. Once you have these values, you can now sum over them to get the final result with a Group By. Notice that in order to perform these calculations, you need to have one or two more levels of subquery. The first query should be something like:
Select PO, POItem, QtyPending, (Select Top 1 QtyPending from MyTable T2 where T1.PO = T2.PO and T2.Date < T1.Date And (T2.Date between #Date1 and #Date2) Order by T2.Date Desc) as QtyPending2 from MyTable T1 Where T1.Date between #Date1 and #Date2) ...
With this as either another subquery or as a View, you can then compute the desired difference by comparing the values of QtyPending and QtyPending2; without forgetting that QtyPendin2 may be Null. The remaining steps are easy to do.
Notice that the above example is for SQL-Server, you might have to change it a little for Access. In any case, you can find here many examples on how to compare two rows under Access. As noted earlier, you can also use a Left Join instead of a subquery to compare your rows.
I came up with this query that solved the problem, it wasn't that simple
SELECT
ItmDtIni.PO
,ItmDtIni.POItem AS [PO Item]
,ROUND(ItmDtIni.QtyPending - ItmDtEnd.QtyPending, 3) AS [Qty Delivered]
,ROUND((ItmDtIni.QtyPending - ItmDtEnd.QtyPending) * ItmDtEnd.Price, 2) AS [Value delivered(US$)]
//Filtering subqueries to bring only the items in the date interval to make a self join
FROM (((SELECT
PO
,POItem
,QtyPending
,MIN(Date) AS MinDate
FROM Item
WHERE Date BETWEEN FORMAT(begin_date, 'dd/mm/yyyy') AND FORMAT(end_date, 'dd/mm/yyyy')
GROUP BY
PO
,POItem
,QtyPending) AS ItmDtIni
//Self join filtering to bring only items in the date interval with the previously filtered table
INNER JOIN (SELECT
PO
,POItem
,QtyPending
,Price
,MAX(Date) AS MaxDate
FROM Item
WHERE Date BETWEEN FORMAT(begin_date, 'dd/mm/yyyy') AND FORMAT(end_date, 'dd/mm/yyyy')
GROUP BY
PO
,POItem
,QtyPending
,Price) AS ItmDtEnd
ON ItmDtIni.PO = ItmDtEnd.PO
AND ItmDtIni.POItem = ItmDtEnd.POItem)
INNER JOIN PO
ON ItmDtEnd.PO = PO.Numero)
WHERE
//Showing only items that had a variation in the date interval
ROUND(ItmDtIni.QtyPending - ItmDtEnd.QtyPending, 3) <> 0
//Anchoring min date in the interval for each item found by the first subquery
AND ItmDtIni.MinDate = (SELECT MIN(Item.Date)
FROM Item
WHERE
ItmDtIni.PO = Item.PO
AND ItmDtIni.POItem = Item.POItem
AND Date BETWEEN FORMAT(begin_date, 'dd/mm/yyyy') AND FORMAT(end_date, 'dd/mm/yyyy'))
//Anchoring max date in the interval for each item found by the second subquery
AND ItmDtEnd.MaxDate = (SELECT MAX(Item.Date)
FROM Item
WHERE
ItmDtEnd.PO = Item.PO
AND ItmDtEnd.POItem = Item.POItem
AND Date BETWEEN FORMAT(begin_date, 'dd/mm/yyyy') AND FORMAT(end_date, 'dd/mm/yyyy'))

SQL for statement of accounts

I would like to thank in advance for any help.
My problem relates to two tables in MySQL (Now switching to postgresql). The tables are related to a ticketing database.
a) booking. It has four columns ccode,date,time,amount
b) account It has three columns ccode,date,amount
The booking table has ticket bookings and account table has advances and payments received.
I have to prepare a statement of account based on ccode (customer code).
The statement shows columns as below
*Ccode Type Date time amount balance*
- the report in sorted on ccode and then on date (account table row appears first)
- Type column displays B or A depending on record type
- Time column is present only in booking table
- Report has a running balance for each row
- At the end for a customercode, the totals of amount and balance is displayed
I have had success, so far in creating a join as below. (and after dicussion below, have been able to generate TYPE column using IF)
SELECT booking.cname, booking.bdate, booking.btime, booking.rate, booking.ID,
IF(booking.btime IS NOT NULL, "B", "A") AS type, account.cname, account.date,
account.amount, account.ID
FROM booking
LEFT JOIN account ON booking.bdate = account.date AND booking.cname=account.cname AND
booking.rate = account.amount
UNION
SELECT booking.cname, booking.bdate, booking.btime, booking.rate, booking.ID,
IF(booking.btime IS NOT NULL, "B", "A") AS type, account.cname, account.date,
account.amount, account.ID
FROM booking
RIGHT JOIN account ON booking.bdate = account.date AND booking.cname=account.cname AND
booking.rate = account.amount
It displays all the records. A report can be generated using this table.
But is there a way to display the formatted report just by SQL.
I can change the order of columns and even add or remove existing ones as long as Record type is known and a running balance is displayed against each record.
A SAMPLE REPORT ---- REPORT A
CODE DATE TYPE AMOUNT BALANCE TIME
A1 02/19/2011 A 50 50
A1 02/20/2011 B 35 15 1230
A1 02/21/2011 A 40 55
A1 02/21/2011 B 20 35 1830
optional > TOTAL Account = 90 Booking = 55 Balance = 35
A SAMPLE REPORT ---- REPORT B
CODE AMOUNT BOOKED AMOUNT PAID BALANCE
A1 50 50 0
A1 35 15 20
A1 40 55 -15
A1 20 35 -15
this is a weekly statement version of REPORT A.
the reason is i can add where and between to get only records in a given week.
and since it is a weekly report, running balance is just omitted.
It is report grouped for all entries with customercode A1 present
in booking and account tables.
thanx
Since your data is not normalized for this, you pay the price in query complexity, as shown below:
SELECT ccode, date, time, type, amount,
(SELECT SUM(amount) AS balance
FROM numbered AS n
WHERE numbered.rownum <= n.rownum AND numbered.ccode = n.ccode) AS balance
FROM (SELECT sorted.*, #rownum := #rownum + 1 AS rownum
FROM (SELECT *
FROM (SELECT ccode, date, time, 'B' AS type, amount
FROM booking
UNION
SELECT ccode, date, "0000" AS time, 'A' AS type, -amount
FROM account) AS unsorted
ORDER BY ccode, date, time, type) AS sorted) AS numbered
The idea here is that you first need to get your booking (debits) and account (credits) lined up as in the "unsorted" statement above. Then, you need to sort them by date and time as in the "sorted" statement. Next, add row numbers to the results as in the "numbered" statement. Finally, select all that data along with a sum of amounts with row number less than or equal to the current row number that matches your ccode.
In the future, please consider using a transaction table of some sort which holds all account balance changes in a single table.
I have found the answer.
It is to use cummulative sql statement to find a running balance