Only check max date for specific rows? - sql

Edit: Realized there's tons of these questions. Trying a subquery and looking through those.
Edit2: Just needed a subquery. Works now. For the sake of anyone else looking at this, I added
where t2.startdate = (select max(startdate) from table2 as sub
where sub.item = t1.item)
and t1.effectivedate = (select max(startdate) from table2 as sub2)
I'm currently writing a query to pull two rates from two separate tables, and return the difference between the two rates. I'm having trouble with getting the proper rates. I need to only get the rates for the most recent listing for each item. My data in the tables looks like this:
todate item rate
2014-01-15 pencil -0.07
2014-01-17 pencil -0.03
2014-02-22 pencil -0.05
2014-01-15 pen -0.013
2014-01-17 pen -0.02
2014-02-22 pen -0.032
I want it to return this (assuming both tables are exactly the same):
Item Rate1 Rate2 Difference Date
Pencil -0.05 -0.05 0 2014-02-22
Pen -0.032 -0.032 0 2014-02-22
Both tables are more or less the same thing, just with different rates. My problem is I end up getting multiple dates regardless of how I change the query.
I have this right now:
use db
select t1.item, t2.Rate as t2Rate, t1.Rate as t1Rate,(abs(t2.Rate) - abs(t1.Rate))
as Dffrnce, t2.startDate
from table2 as t2 join table1 as t1
on t2.item = t1.item
where t2.StartDate = t1.EffectiveDate
group by t1.item, t2.StartDate, t2.Rate, t1.Rate
having t2.StartDate = max(t2.StartDate)
order by t1.item
I'm guessing my problem is stemming from me not checking each item for their max date specifically. But I'm not entirely sure how to do that. I tried using distinct but that returned the same result. Am I missing something obvious? I only want to grab the rates from the most recent date. I've tried joining on the item and max date, a having statement having max(t2.StartDate) = t1.effectivedate but nothing seems to be working.

Just saw your edit that says you got it working. Nice.
You might consider a different way to identify the rows you want to work with.
Which flavor of SQL are you using? Not everything works everywhere.
DECLARE #Rate1 AS TABLE (id INT, rateDate DATE, rate INT)
DECLARE #Rate2 AS TABLE (id INT, rateDate DATE, rate INT)
INSERT INTO #Rate1 (id, rateDate, rate) VALUES (1, '2000-01-01', 1),(1, '2001-01-01', 3),(2, '2000-01-01', 4)
INSERT INTO #Rate2 (id, rateDate, rate) VALUES (1, '2001-01-01', 2),(2, '2002-01-01', 3)
;WITH r1 AS (SELECT *, ROW_NUMBER() OVER(PARTITION BY id ORDER BY rateDate DESC) rn FROM #Rate1)
, r2 AS (SELECT *, ROW_NUMBER() OVER(PARTITION BY id ORDER BY rateDate DESC) rn FROM #Rate2)
SELECT *
FROM r1
INNER JOIN
r2 ON r1.id=r2.id
WHERE r1.rn=1
AND r2.rn=1

Related

SQL Join on nearest available date

I currently have these tables:
CREATE TABLE #SECURITY_TEMP (ID CHAR(30))
CREATE TABLE #SECURITY_TEMP_PRICE_HISTORY (ID CHAR(30), PRICEDATE DATE, PRICE FLOAT)
CREATE TABLE #SECURITY_POST (ID CHAR(30), SECPOS int)
INSERT INTO #SECURITY_TEMP (ID) VALUES ('APPL') ,('VOD'),('VOW3'), ('AAA')
INSERT INTO #SECURITY_TEMP_PRICE_HISTORY (ID,PRICEDATE, PRICE) VALUES
('APPL', '20150101',10.4), ('APPL', '20150116',15.4), ('APPL', '20150124',22.4),
('VOD', '20150101', 30.5), ('VOD', '20150116',16.5), ('VOD', '20150124',16.5),
('VOW3', '20150101', 45.5), ('VOW3', '20150116',48.8) ,('VOW3', '20150124',50.55),
('AAA', '20100118', 0.002)
INSERT INTO #SECURITY_POST (ID,SECPOS) VALUES ('APPL', 100), ('VOD', 350), ('VOW3', 400)
I want to have a clean table that shows me the security ID, the security position and the latest available price for that security when a date is passed.
Now when I do the following:
SELECT sec.ID, sec.SECPOS, t.PRICE
FROM #SECURITY_POST as SEC INNER JOIN #SECURITY_TEMP_PRICE_HISTORY as t
ON sec.ID = t.ID
WHERE t.PriceDate = '20150101'
GROUP BY sec.ID, secPos, t.price
I get the correct result
1. ID SECPOS PRICE
2. APPL 100 10.4
3. VOD 350 30.5
4. VOW3 400 45.5
However, there may be individual circumstances where, the price of a stock is not available. In that sense, I therefore want to be able to get the most recent price available.
Doing
SELECT sec.ID, sec.SECPOS, t.PRICE
FROM #SECURITY_POST as SEC INNER JOIN
#SECURITY_TEMP_PRICE_HISTORY as t
ON sec.ID = t.ID
WHERE t.PriceDate = '20150117'
GROUP BY sec.ID, secPos, t.price
Returns 0 rows because of no data, and doing
SELECT sec.ID, sec.SECPOS, t.PRICE
FROM #SECURITY_POST as SEC INNER JOIN
#SECURITY_TEMP_PRICE_HISTORY as t
ON sec.ID = t.ID
WHERE t.PriceDate <= '20150117'
GROUP BY sec.ID, sec.secPos, t.price
HAVING sec.secpos <> 0
Returns duplicate rows.
I have tried loads of different methodologies and I just cannot get the output I want. Furthermore, I would also like to be able to get one column with the price nearest a date (call it START_DATE) and one column with the price nearest a second date (call it END_DATE) and one column that is going to be the position Price#END_DATE - Price#START_DATE. The price is always taken from the same #SECURITY_TEMP_PRICE_HISTORY.
However, my SQL knowledge is just embarrassing, and I could not figure out a good efficient way of doing this. Any help would be appreciated. Please also note that the #SECURITY_PRICE_HISTORY table may contain more securities than the #SECURITY_POST Table.
This should do the trick. OUTER APPLY is a join operator that (like CROSS APPLY) allows a derived table to have an outer reference.
SELECT
s.ID,
s.SecPos,
t.Price
t.PriceDate
FROM
#SECURITY_POST s
OUTER APPLY (
SELECT TOP 1 *
FROM #SECURITY_TEMP_PRICE_HISTORY t
WHERE
s.ID = t.ID
AND t.PriceDate <= '20150117'
ORDER BY t.PriceDate DESC
) t
;
You may also want to consider flagging security prices that are very old, or limiting the lookup for the most recent security to a certain period (a week or a month or something).
Make sure that your price history table has an index with (ID, PriceDate) so that the subquery lookups can use range seeks and your performance can be good. Make sure you do any date math on the security table, not the history table, or you will force the price-lookup subquery to be non-sargable, which would be bad for performance as the range seeks would not be possible.
If no price is found for the security, OUTER APPLY will still allow the row to exist, so the price will show as NULL. If you want securities to not be shown when no appropriate price is found, use CROSS APPLY.
For your second part of the question, you can do this with two OUTER APPLY operations, like so:
DECLARE
#StartDate date = '20150101',
#EndDate date = '20150118';
SELECT
S.ID,
S.SecPos,
StartDate = B.PriceDate,
StartPrice = B.Price,
EndDate = E.PriceDate,
EndPrice = E.Price,
Position = B.Price - E.Price
FROM
#SECURITY_POST S
OUTER APPLY (
SELECT TOP 1 *
FROM #SECURITY_TEMP_PRICE_HISTORY B
WHERE
S.ID = B.ID
AND B.PriceDate <= #StartDate
ORDER BY B.PriceDate DESC
) B
OUTER APPLY (
SELECT TOP 1 *
FROM #SECURITY_TEMP_PRICE_HISTORY E
WHERE
S.ID = E.ID
AND E.PriceDate <= #EndDate
ORDER BY E.PriceDate DESC
) E
;
With your data this yields the following result set:
ID SecPos StartDate StartPrice EndDate EndPrice Position
---- ------ ---------- ---------- ---------- -------- --------
APPL 100 2015-01-01 10.4 2015-01-16 15.4 -5
VOD 350 2015-01-01 30.5 2015-01-16 16.5 14
VOW3 400 2015-01-01 45.5 2015-01-16 48.8 -3.3
Last, while not all agree, I would encourage you to name your ID columns with the table name as in SecurityID instead of ID. In my experience the use of ID only leads to problems.
Note: there is a way to solve this problem using the Row_Number() windowing function. If you have relatively few price points compared to the number of stocks, and you're looking up prices for most of the stocks in the history table, then you might get better performance with that method. However, if there are a great number of price points per stock, or you're filtering to just a few stocks, you may get better performance with the method I've shown you.

Select left 10 numbers, left join for a price from second table, and then sum, SQL

I am currently working in sql 2012 visual management studio. I have two tables. Table1 has three columns (ItemNumber as varchar, Quantity as int, and TimeOrdered as datetime). Table2 has 2 columns (ItemNumber as varchar, and Price as float). Please note these item numbers are not the same, the part numbers on table 1 have a letter after the number while the table 2 item number does not. For example on table 1 the item number will look something like this 999999999-E and the other table will just be 999999999-. Therefore I must use a select Left for 10 digits to get the part number.
I need to pull a list of item numbers from table 1 based on the time ordered and then cross compare that list to table 2 and multiple the price times the quantity for a grand total. Here is my code so far:
SELECT sum(tbl.quantity * table2.price) as grandtotal,
tbl.PartNumber,
tbl.quanity,
table2.price
FROM
(SELECT left(itemnumber, 10) as itemnumber, quantity
FROM table1
WHERE TimeOrdered between
('2014-05-05 00:00:00.000')
AND
('2015-05-05 00:00:00.000')) as tbl
Left table2 on
tbl.partnumber =tbl2.itemnumber
I am receiving an error here for aggregate columns but I am not sure this is the correct way to go about this to begin with.
-------------update---------------
I got it working. Sorry for taking so long to get back to you guys, I was stuck in a meeting all day,
How About This. The case is just to avoid div by Zero errors.
SELECT sum( Isnull(tbl.quantity,0) * Isnull(table2.price,0) ) as grandtotal,
tbl.PartNumber,
Sum(tbl.quanity),
case when Isnull(Sum(tbl.quanity),0) = 0 then null else
sum(Isnull(tbl.quantity,0) * Isnull(table2.price,0) ) / Sum(tbl.quanity) end
as Price
FROM
(SELECT left(itemnumber, 10) as itemnumber, quantity FROM table1 WHERE TimeOrdered between
('2014-05-05 00:00:00.000')
AND ('2015-05-05 00:00:00.000')) as tbl
Left outer join table2 on
tbl.partnumber =tbl2.itemnumber
group by tbl.PartNumber
SQL server requires you to explicitly group by the columns you're not aggregating by. So you need to add group by tbl.PartNumber, tbl.quantity, table2.price. Of course, that's probably going to make the tbl.quantity * table2.price kind of useless. What are you actually trying to do? :)
Here is a fiddle with some sample data that should give you what you want. You need to include any non-aggregate columns in your group by.
Your code ends up as follows:
SELECT left(table1.ItemNumber, 10) as PartNumber,
table2.price,
sum(table1.quantity) as totalquantity,
sum(table1.quantity * table2.price) as grandtotal
FROM table1
INNER JOIN table2 ON left(table1.ItemNumber, 10) = table2.ItemNumber
WHERE t1.Timerordered BETWEEN '2014-05-05 00:00:00.000' AND '2015-05-05 00:00:00.000'
GROUP BY table1.ItemNumber, table2.price
SQL Fiddle Example
SELECT SUM(t1.quantity * t2.price) AS 'GrandTotal'
,SUM(t1.quantity) AS 'Quantity'
,t1.itemnumber
,t2.price
FROM Table1 t1
JOIN Table2 t2 ON LEFT(t1.itemnumber, 10) = t2.itemnumber
WHERE t1.Timeordered BETWEEN '2014-05-05 00:00:00.000' AND '2015-05-05 00:00:00.000'
GROUP BY t1.itemnumber, t2.price

sql query to with insert select

I have a table with the below format:
ID curr Date Bid Ask
1 AUD/NZD 20090501 00:00:00.833 1.2866 1.28733
2 AUD/NZD 20090501 01:01:01.582 1.28667 1.2874
3 AUD/NZD 20090501 02:01:01.582 1.28667 1.28747
Now I need to select the change of Bid and Ask column and store into a different table...The result should be like the following
Bid Change Ask Change
0.0000700 0.0000700
0.0000000 0.0000700
select
t1.id,
t1.curr,
t1.date,
t1.bid,
t1.bid - t2.bid [Bid Change],
t1.ask,
t1.ask - t2.ask [Ask Change]
from tbltest t1
left join tbltest t2 on t1.ID = t2.ID + 1
order by date
This query returns everything correct except the format of Bid Change and Ask Change...like the following
BID Change ASK CHANGE
7.00000000000145E-05 7.00000000000145E-05
Am really clueless on what to do with this situation...any little help will work.
Thanks in advance!
It doesn't seem necessary to me to store them in a different table, you can just calculate them on the fly using APPLY, this way any changes to the underlying data will not cause your change data to be stale:
SELECT T.*,
BidChange = t.Bid - prev.Bid,
AskChange = t.Ask - prev.Ask
FROM T
OUTER APPLY
( SELECT TOP 1 T2.Bid, T2.Ask
FROM T AS T2
WHERE T2.Curr = T.Curr
AND T2.Date < T.Date
ORDER BY T2.Date DESC
) AS prev;
If this is something you will need regularly then you may want to consider a view, rather than storing it in a table.

Calculate Absolute difference between rows in MsAccess

I have spent all morning on this and just can't get it right... I'd really appreciate the help of someone more knowledgable than myself to get this working.
I have a table with some data in that looks like this:
MonthYear WeekBeg. Week Value
Dec-10 27/12/2010 1 66.66
Jan-11 3/01/2011 2 50
Jan-11 10/01/2011 3 17.5
Jan-11 17/01/2011 4 20
Jan-11 24/01/2011 5 0
Jan-11 31/01/2011 6 50
Feb-11 7/02/2011 7 0
Feb-11 14/02/2011 8 74
Feb-11 21/02/2011 9 100
I'm sorry the table above doesn't look better... I need to calculate the difference between the values from week to week - so the results column in this case would be:
16.66
32.5
2.5
20
50
50
74
26
I've looked at lots of code on the net - (e.g. from this site) but can't seem to make it work. I added in the ABS function to make sure the differences were absolute values and got this working but the numbers themselves just aren't right.
I haven't posted what I ended up with as it just got into a bigger and bigger mess, but what I started with was the link above. Again, I'd be really grateful for any insight anyone is able to offer.
Many thanks
ADDED:
Thanks so much for the fast reply. Got this working easily - added a few bits:
SELECT T1.MonthYear AS [From], T2.MonthYear AS [To], T1.Week AS Week, T1.WeekBeg AS WeekBeg, ABS(T1.Value - T2.Value) AS Difference FROM Test AS T1 LEFT JOIN Test AS T2 ON T2.Week = T1.Week + 1
Only thing is the resulting difference values need to be in the second of the two rows whereas here they are in the first of the two. Is there any easy way of modifying this?
Many thanks again.
ADDED:
Would definitely be worth using the second option if possible as can't always guarantee weeks won't be missed out. I am probably missing something, but when I run the second option from Thomas, I get the message:
'The specified field [T1].[Datavalue] could refer to more than one table listed in the FROM clause of your SQL statement'.
I thought this might be to do with the field in the table being VALUE not DataValue, but when I change it, I get 'Type Mismatch in Expression' instead.
Many thanks.
Presuming the Week column is perfectly sequential:
Select T1.MonthYear As T1Year
, T1.WeekBeg As T1WeekBeg
, T2.MonthYear As T2Year
, T2.WeekBeg As T2WeekBeg
, [T2].[Value]-[T1].[Value] AS Expr1
From TableWithData AS T1
Left Join TableWithData AS T2
On T1.Week = T2.Week + 1;
It should be noted that this will not compile in the QBE designer. You will have to view and modify it purely through the SQL View (or in code)
If for some reason you could not depend on the Week number being sequential, then it gets trickier as you need to use a derived table. Again, this solution will only work in SQL View or in code:
Select T1.MonthYear, T1.WeekBeg
, T2.MonthYear, T2.WeekBeg
, [T2].[Value]-[T1].[Value] AS Diff
From (TableWithData AS T1
Inner Join (
Select T1.WeekBeg As T1WeekBeg
, Min(T2.WeekBg) As T2WeekBeg
From TableWithData As T1
Left Join TableWithData AS T2
On T2.WeekBeg > T1.WeekBeg
Group By T1.WeekBeg
) As Query1
On T1.WeekBeg = Query1.T1WeekBeg)
Inner Join TableWithData AS T2
On Query1.T2WeekBeg = T2.WeekBeg;
A version based off of the sample query from your base link. (It uses ORDERY BY on the Week field and TOP 1 too isolate a scalar value.)
SELECT t1.Value - (SELECT TOP 1 t2.Value FROM myTable AS t2
WHERE t2.Week < t1.Week
ORDER BY t2.Week DESC) AS t2Val
FROM myTable t1
WHERE (SELECT TOP 1 t3.Value FROM myTable AS t3
WHERE t1.Week < t3.Week) Is Not Null
ORDER BY t1.Week;
Should be close to working but the aliasing is very error prone. I suggest that if the week numbers are indded sequential that you go with Thomas' answer.

update a field based on subtotal from another table

I'm using oracle(10).
I've got two tables as follows:
Table1 (uniq rows):
ID AMOUNT DATE
Table2:
ID AMOUNT1 AMOUNT2 ...AMOUNTN DATE
Table2 is connected many to one to Table1 connected via ID.
What I need is update-ing Table1.DATE with: the last (earliest) date from Table2 where Table1.AMOUNT - SUM(Table2.AMOUNT1) <= 0, when reading table 2 backwards by the Table2.DATE field.
Is there a simple way to do it?
Thanks in advance!
UPDATE: as I see from your answers I had misspecified the question a bit. So here goes a detailed example:
Table1 has:
ID: 1 AMOUNT:100 DATE:NULL
Table2 has (for ID: 1 so ID is not listed in here):
AMOUNT1 DATE
50 20080131
30 20080121
25 20080111
20 20080101
So in this case I need 20080111 as the DATE in Table1 as 50+30+25 => 100.
Based on your revised question, this is a case for using analytic functions.
Assuming you meant >=100 rather than <= 100 as your example implies, and renaming columns DATE to THEDATE since DATE is a reserved word in Oracle:
update table1 set thedate=
( select max(thedate) from
( select id, thedate,
sum(amount1) over (partition by id order by thedate desc) cumsum
from table2
) v
where v.cumsum >= 100
and v.id = table1.id
)
If the 100 means the current value of table1 then change that line to:
where v.cumsum >= table1.amount
First off - your database layout feels severely wrong, but I guess you can't / don't want to change it. Table1 should probably be a view, and Table2 does not make the impression of proper normalization. Something like (ID, AMOUNT_TYPE, AMOUNT_VALUE, DATE) would make much more sense to me.
But to solve your problem (this is T-SQL "UPDATE FROM" syntax, but I think Oracle knows it):
UPDATE
Table1
SET
Date = Table2Aggregate.MinDate
FROM
Table1
INNER JOIN (
SELECT Id, SUM(Amount1) SumAmount1, MIN(Date) MinDate
FROM Table2
GROUP BY Id
) AS Table2Aggregate ON Table1.Id = Table2Aggregate.ID
WHERE
Table1.Amount - Table2Aggregate.SumAmount1 <= 0