Asymmetric columns - Picking latest given value - sql

Version Used: Microsoft SQL Server Management Studio, SQL Server 2008
I'm facing a frustrating issue that is caused by asymmetric columns. Basically, I want to calculate the effects of a discount on given spot prices. Both are set up as indexes in the same table pricevalues. Spot prices are given 5 days á week, while discounts are only stated on the day they were updated. So, for example:
pricevalues(priceindex, price, pricedate)
PRICEINDEX PRICE PRICEDATE
-------------------- ------------------ ------------------
DISCOUNT_INDEX_ID | 15.5 | 2013-02-26
DISCOUNT_INDEX_ID | 10.5 | 2013-04-05
DISCOUNT_INDEX_ID | 16.0 | 2013-07-10
SPOT_INDEX_ID | 356.5 | 2013-07-22
SPOT_INDEX_ID | 355.0 | 2013-07-23
SPOT_INDEX_ID | 354.6 | 2013-07-24
SPOT_INDEX_ID | 357.0 | 2013-07-25
SPOT_INDEX_ID | 358.5 | 2013-07-26
How would I best go about calculating the difference between PRICE's for SPOT_INDEX_ID and DISCOUNT_INDEX_ID on all dates that SPOT_INDEX_ID is given, if the latest given (relative to the PRICEDATE of the spot price) discount PRICE is to be used?
For example, the discount on a spot on 2013-07-22 is 16.0 (2013-07-10), while the discount on a spot on 2013-05-15 is 10.5 (2013-04-05) and the discount on a spot on 2013-03-03 is 15.5 (2013-02-26)
I only know how to do it when the PRICEDATE's match for both DISCOUNT_INDEX_ID and SPOT_INDEX_ID, so:
SELECT
(pv1.price - pv2.price) AS 'Total Price',
pv1.price AS 'Spot Price',
pv2.price AS 'Discount'
FROM
pricevalues pv1, pricevalues pv2
WHERE
pv1.priceindex = 'SPOT_INDEX_ID' AND
pv1.pricedate = pv2.pricedate AND
pv2.priceindex = 'DISCOUNT_INDEX_ID'
This is of course not possible whith these huge gaps in the discount index, so when the dates do not match, how do I instead get the value of the latest given discount?
EDIT: I would like the output to look like the following:
PRICEDATE SPOT_INDEX DISCOUNT_INDEX SPOT_PRICE
---------------- ------------------- --------------------- ----------- --->>>
2013-07-26 | SPOT_INDEX_ID | DISCOUNT_INDEX_ID | 358.5 |
DISCOUNT_PRICE TOTAL_PRICE
---------------- -------------------
16.0 | 342.5 |

You can take the Discount price in a variable for a given date and then use it in the main query. here is the sample:
Declare #Discount_Price money
select Max(pricedate),#Discount_Price=Price from pricevalues where PriceIndex='DISCOUNT_INDEX_ID' group by Price having Price=Max(PriceDate)
SELECT
(pv1.price - #Discount_Price) AS PriceDiff,
pv1.price AS 'Spot Price',
#Discount_Price AS 'Discount'
FROM
pricevalues pv1
WHERE
pv1.priceindex = 'SPOT_INDEX_ID'

If you only want to return one record this will work:
DECLARE #date DATE = GETDATE()
SELECT TOP 1 a.PRICEDATE, b.PRICE 'Spot', a.PRICE 'Discount', b.Price - a.Price 'Total'
FROM #Table1 a
JOIN (SELECT *,ROW_NUMBER() OVER (PARTITION BY PRICEINDEX ORDER BY PRICEDATE DESC)'RowRank'
FROM #Table1
WHERE PRICEINDEX = 'SPOT_INDEX_ID'
AND PRICEDATE <= #date
)b
ON b.RowRank = 1
WHERE a.PRICEINDEX = 'DISCOUNT_INDEX_ID'
AND a.PRICEDATE <= #date
ORDER BY a.PRICEDATE DESC
Change GETDATE() to whatever your inquiry date is.
I wasn't sure what you wanted for PRICEDATE, perhaps that should be b.PRICEDATE, or maybe just #date?

I solved the problem with a combination of FETCHES in SQL and processing in Excel where I also took care of the gaps in the discount index.

Related

How to check if date is in parallel time interval?

I have data like this:
TimeID | StartDate | EndDate | Price
000001 | 03.04.20 | 10.10.20 | 12
000002 | 01.02.20 | 31.12.99 | 13
000003 | 01.01.20 | 31.01.20 | 15
For a given date eg. 05.05.20 I want to get the cheapest price.
This Date would fit in TimeID 1 and 2. But 1 would be cheaper.
The needet price would be 12.
Is it possible to group intervals or how can i find the lowest price in parallel time intervals?
I found a solution:
select TOP 1 Price from Tab
where StartDate <= '05.05.20'
and EndDate >= '05.05.20'
order by Price
This gives me the lowest price for fiting time intervals.
If there is a more elegant solution, let me know.
select top 1 * from table_name where date> startdate and date <enddate order by price
A slightly simpler query is to use the min function, that returns the lowest value on the selected records (interval).
select min(Price) from Tab where '05.05.20' between StartDate and EndDate
I've also used the between operator to simplify a couple of conditions xx >= aa and xx <= bb

Is it possible to group by day, month or year with timestamp values?

I have a table ORDERS(idOrder, idProduct, Qty, OrderDate) where OrderDate is a varchar column with timestamp values, is it possible to get the Qty of each day, week, month or year ?
The table looks like this :
---------------------------------------
|idOrder | idProduct | Qty | OrderDate|
---------------------------------------
| 1 | 5 | 20 | 1504011790 |
| 2 | 5 | 50 | 1504015790 |
| 3 | 5 | 60 | 1504611790 |
| 4 | 5 | 90 | 1504911790 |
-----------------------------------------
and i want something like this
------------------------------
| idProduct | Qty | OrderDate|
-------------------------------
| 5 | 70 | 08/29/2017|
| 5 | 60 | 09/05/2017|
| 5 | 90 | 09/08/2017|
-------------------------------
looks like you want to do 2 things here: first group by your idProduct and OrderDate
select idProduct, sum(Qty), OrderDate from [yourtable] group by idProduct, OrderDate
This will get you the sums that you want. Next, you want to convert time formats. I assume that your stamps are in Epoch time (number of seconds from Jan 1, 1970) so converting them takes the form:
dateadd(s,[your time field],'19700101')
It also looks like you wanted your dates formatted as mm/dd/yyyy.
convert(NVARCHAR, [date],101) is the format for accomplishing that
Together:
select idProduct, sum(Qty), convert(NVARCHAR,dateadd(s,OrderDate,'19700101'), 101)
from [yourtable]
group by idProduct, OrderDate
Unfortunately, the TSQL TIMESTAMP data type isn't really a date. According to this SO question they're even changing the name because it's such a misnomer. You're much better off creating a DATETIME field with a DEFAULT = GETDATE() to keep an accurate record of when a line was created.
That being said, the most performant way I've seen to track dates down to the day/week/month/quarter/etc. is to use a date dimension table that just lists every date and has fields like WeekOfMonth and DayOfYearand. Once you join your new DateCreated field to it you can get all sorts of information about that date. You can google scripts that will create a date dimension table for you.
Yes its very simple:
TRUNC ( date [, format ] )
Format can be:
TRUNC(TO_DATE('22-AUG-03'), 'YEAR')
Result: '01-JAN-03'
TRUNC(TO_DATE('22-AUG-03'), 'MONTH')
Result: '01-AUG-03'
TRUNC(TO_DATE('22-AUG-03'), 'DDD')
Result: '22-AUG-03'
TRUNC(TO_DATE('22-AUG-03'), 'DAY')
Result: '17-AUG-03'

Counting number of non-trading days/days without price changes

I have a table of closing prices for bonds over time, with the essential structure:
bond_id | tdate | price
---------+------------+-------
EIX1923 | 2014-01-01 | 100.12
EIX1923 | 2014-01-02 | 100.10
EIX1923 | 2014-01-05 | 100.10
EIX1923 | 2014-01-10 | 100.15
As you can see, I don't have prices for every day -- because the bond does not trade every day. I would like to count how often this occurs in a given year and, if the bond price hasn't changed between consecutive days, I take that as the same result.
That is, for a year with N trading days (excluding weekends, ignoring holidays), I would essentially want to generate a series of dates and count how many days the price is (1) unchanged from the previous day or (2) is not recored for that day and divide it over N.
I'm using PostgreSQL, so I started out with generate_series('2014-01-01'::timestamp, '2015-01-01'::timestamp, '1 day'::interval); I can SELECT from this series and do a WHERE to exclude weekends:
SELECT dd
FROM generate_series(
'2014-01-01'::timestamp,
'2015-01-01'::timestamp,
'1 day'::timestamp
) dd
WHERE EXTRACT(dow FROM dd) NOT IN (0, 6);
Now, I figure I would like to generate a "column" of bond_id to JOIN against the trade table with, but I'm not sure how. Essentially, I figured the simplest structure would be a LEFT JOIN so that I get something like:
EIX1923 | 2014-01-01 | 100.12
EIX1923 | 2014-01-02 | 100.10
EIX1923 | 2014-01-03 |
EIX1923 | 2014-01-04 |
EIX1923 | 2014-01-05 | 100.10
EIX1923 | 2014-01-06 |
EIX1923 | 2014-01-07 |
EIX1923 | 2014-01-08 |
EIX1923 | 2014-01-09 |
EIX1923 | 2014-01-10 | 100.15
Then I could just fill in the gaps with the most recently available price and count the number of ABS(∆P) == 0 in application code. But if there are solutions to do this entirely in SQL that would be nice too! I have no idea if the approach above is the right one to go with.
(I didn't bother to check if the first days of January 2014 are weekends or not, since it's just for illustration here; but they would be excluded from the results, obviously).
EDIT: Seems there might be a number of similar questions already. Hope it's not too much of a duplicate!
EDIT: So, I played a bit more with this and this solution "works" (and I feel silly for not realizing sooner) in the above sense:
SELECT
'EI653670', dd, t.price
FROM
generate_series('2014-01-01'::timestamp, '2015-01-01'::timestamp, '1 day'::interval) dd
LEFT JOIN
trade t ON dd = t.tdate AND t.id = 'EI653670'
WHERE
EXTRACT(dow FROM dd) NOT IN (0, 6) ORDER BY dd;
Is there a better way?
I think you can do this logic with lag(). The following show the general idea -- get the previous date and price and do some logic:
select bond_id,
sum(case when prev_price = price
then date - prev_date + 1
when prev_date = date + interval '1 day'
then 0
else date - prev_date
end)
from (select t.*,
lag(t.date) over (partition by t.bond_id order by t.date) as prev_date,
lag(t.price) over (partition by t.bond_id order by t.date) as prev_price
trade t
) t
group by bond_id;
One caveat is that this probably won't handle boundary conditions the way that you want.

SQL to find the date when the price last changed

Input:
Date Price
12/27 5
12/21 5
12/20 4
12/19 4
12/15 5
Required Output:
The earliest date when the price was set in comparison to the current price.
For e.g., price has been 5 since 12/21.
The answer cannot be 12/15 as we are interested in finding the earliest date where the price was the same as the current price without changing in value(on 12/20, the price has been changed to 4)
This should be about right. You didn't provide table structures or names, so...
DECLARE #CurrentPrice MONEY
SELECT TOP 1 #CurrentPrice=Price FROM Table ORDER BY Date DESC
SELECT MIN(Date) FROM Table WHERE Price=#CurrentPrice AND Date>(
SELECT MAX(Date) FROM Table WHERE Price<>#CurrentPrice
)
In one query:
SELECT MIN(Date)
FROM Table
WHERE Date >
( SELECT MAX(Date)
FROM Table
WHERE Price <>
( SELECT TOP 1 Price
FROM Table
ORDER BY Date DESC
)
)
This question kind of makes no sense so im not 100% sure what you are after.
create four columns, old_price, new_price, old_date, new_date.
! if old_price === new_price, simply print the old_date.
What database server are you using? If it was Oracle, I would use their windowing function. Anyway, here is a quick version that works in mysql:
Here is the sample data:
+------------+------------+---------------+
| date | product_id | price_on_date |
+------------+------------+---------------+
| 2011-01-01 | 1 | 5 |
| 2011-01-03 | 1 | 4 |
| 2011-01-05 | 1 | 6 |
+------------+------------+---------------+
Here is the query (it only works if you have 1 product - will have to add a "and product_id = ..." condition on the where clause if otherwise).
SELECT p.date as last_price_change_date
FROM test.prices p
left join test.prices p2 on p.product_id = p2.product_id and p.date < p2.date
where p.price_on_date - p2.price_on_date <> 0
order by p.date desc
limit 1
In this case, it will return "2011-01-03".
Not a perfect solution, but I believe it works. Have not tested on a larger dataset, though.
Make sure to create indexes on date and product_id, as it will otherwise bring your database server to its knees and beg for mercy.
Bernardo.

Finding a date from a SUM

I am having trouble finding what date my customers hit a certain threshold in how much money they make.
customer_id | Amount | created_at
---------------------------
1134 | 10 | 01.01.2010
1134 | 15 | 02.01.2010
1134 | 5 | 03.24.2010
1235 | 10 | 01.03.2010
1235 | 15 | 01.03.2010
1235 | 30 | 01.03.2010
1756 | 50 | 01.05.2010
1756 | 100 | 01.25.2010
To determine how much total amount they made I run a simple query like this:
SELECT customer_id, SUM(amount)
FROM table GROUP BY customer_id
But I need to be able to find for e.g. the date a customer hits $100 in total amount.
Any help is greatly appreciated. Thanks!
Jesse,
I believe you are looking for a version of "running total" calculation.
Take a look at this post calculate-a-running-total.
There is number of useful links there.
This article have a lot of code that you could reuse as well: http://www.sqlteam.com/article/calculating-running-totals.
Something like having clause
SELECT customer_id, SUM(amount) as Total FROM table GROUP BY customer_id having Total > 100
I'm not sure if MySQL supports subqueries, so take this with a grain of salt:
SELECT customer_id
, MIN(created_at) AS FirstDate
FROM ( SELECT customer_id
, created_at
, ( SELECT SUM(amount)
FROM [Table] t
WHERE t.CustomerID = [Table].CustomerID
AND t.created_at <= [Table].created_at
) AS RunTot
FROM [Table]
) x
WHERE x.RunTot >= 100
GROUP BY customer_id