SQL加入最近的可用日期

时间:2015-07-24 15:17:34

标签: sql sql-server sql-server-2012

我目前有这些表格:

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)

我希望有一个干净的表格,在通过日期时向我显示该证券的安全ID,安全位置和最新可用价格。

现在,当我执行以下操作时:

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

我得到了正确的结果

 1. ID  SECPOS  PRICE 
 2. APPL  100   10.4
 3. VOD   350   30.5
 4. VOW3  400   45.5

但是,可能存在个别情况,即股票价格无法获得。从这个意义上说,我希望能够获得最新的价格。

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

由于没有数据而返回0行,并且正在执行

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

返回重复的行。

我尝试了很多不同的方法,但我无法得到我想要的输出。此外,我还希望能够获得一个价格最接近日期的列(称为START_DATE)和一个价格最接近第二个日期的列(称为END_DATE)和一列这将是位置Price@END_DATE - Price@START_DATE。价格始终取自#SECURITY_TEMP_PRICE_HISTORY

然而,我的SQL知识只是令人尴尬,我无法找到一个很好的有效方法。任何帮助,将不胜感激。另请注意#SECURITY_PRICE_HISTORY table may contain more securities than the #SECURITY_POST表。

1 个答案:

答案 0 :(得分:12)

这应该可以解决问题。 OUTER APPLY是一个连接运算符,(如CROSS APPLY)允许派生表具有外部引用。

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
;

您可能还需要考虑标记非常旧的安全价格,或将最近一次安全的查询限制在特定时期(一周或一个月或某事)。

确保您的价格历史记录表中包含(ID, PriceDate)的索引,以便子查询查找可以使用范围搜索,您的表现可以很好。确保在安全表上进行任何日期数学运算,而不是历史表,否则您将强制价格查询子查询不可搜索,这对性能不利,因为搜索范围是不可能的。

如果没有找到安全价格,OUTER APPLY仍会允许该行存在,因此价格将显示为NULL。如果您希望在找不到合适价格时不显示证券,请使用CROSS APPLY

对于问题的第二部分,您可以使用两个OUTER APPLY操作执行此操作,如下所示:

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
;

使用您的数据,会产生以下结果集:

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

最后,虽然并非所有人都同意,但我建议您使用ID而不是SecurityID的表名为ID列命名。根据我的经验,使用ID只会导致问题。

注意:有一种方法可以使用Row_Number()窗口函数来解决此问题。如果您的股票价格与股票数量相比相对较少,并且您正在查找历史表中大多数股票的价格,那么您可以通过该方法获得更好的表现。但是,如果每个股票有很多价位,或者你只是过滤几只股票,那么你可能会用我给你看过的方法获得更好的表现。