Sql 如何以每个记录都与“上一个”记录联接的方式自联接表?

Sql 如何以每个记录都与“上一个”记录联接的方式自联接表?,sql,sql-server,performance,sql-server-2008,Sql,Sql Server,Performance,Sql Server 2008,我有一个MS SQL表,其中包含以下列的股票数据:Id、Symbol、Date、Open、High、Low、Close 我想自己加入这个表,这样我就可以每天为Close获得%的更改 我必须创建一个查询,该查询将以一种方式连接表本身,即每个记录也包含来自上一个会话的数据。请注意,我不能使用昨天的日期 我的想法是这样做: select * from quotes t1 inner join quotes t2 on t1.symbol = t2.symbol and t2.date = (selec

我有一个MS SQL表,其中包含以下列的股票数据:Id、Symbol、Date、Open、High、Low、Close

我想自己加入这个表,这样我就可以每天为Close获得%的更改

我必须创建一个查询,该查询将以一种方式连接表本身,即每个记录也包含来自上一个会话的数据。请注意,我不能使用昨天的日期

我的想法是这样做:

select * from quotes t1
inner join quotes t2
on t1.symbol = t2.symbol and
t2.date = (select max(date) from quotes where symbol = t1.symbol and date < t1.date)
DECLARE @Today DATETIME
SELECT @Today = DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP))

;WITH today AS
(
    SELECT  Id ,
            Symbol ,
            Date ,
            [OPEN] ,
            High ,
            LOW ,
            [CLOSE],
            DATEADD(DAY, -1, Date) AS yesterday 
    FROM quotes
    WHERE date = @today
)
SELECT *
FROM today
LEFT JOIN quotes yesterday ON today.Symbol = yesterday.Symbol
    AND today.yesterday = yesterday.Date
with OrderedQuotes as
(
    select 
        row_number() over(order by Symbol, Date) RowNum, 
        ID, 
        Symbol, 
        Date, 
        Open, 
        High, 
        Low, 
        Close
      from Quotes
)
select
    a.Symbol,
    a.Date,
    a.Open,
    a.High,
    a.Low,
    a.Close,
    a.Date PrevDate,
    a.Open PrevOpen,
    a.High PrevHigh,
    a.Low PrevLow,
    a.Close PrevClose,

    b.Close-a.Close/a.Close PctChange

  from OrderedQuotes a
  join OrderedQuotes b on a.Symbol = b.Symbol and a.RowNum = b.RowNum + 1
但是我不知道这是不是正确的/最快的方法。在考虑性能时,我应该考虑什么?例如,在符号、日期对上添加唯一索引是否会提高性能


该表中每年将有大约100000条新记录。我正在使用MS SQL Server 2008

您可以执行以下操作:

select * from quotes t1
inner join quotes t2
on t1.symbol = t2.symbol and
t2.date = (select max(date) from quotes where symbol = t1.symbol and date < t1.date)
DECLARE @Today DATETIME
SELECT @Today = DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP))

;WITH today AS
(
    SELECT  Id ,
            Symbol ,
            Date ,
            [OPEN] ,
            High ,
            LOW ,
            [CLOSE],
            DATEADD(DAY, -1, Date) AS yesterday 
    FROM quotes
    WHERE date = @today
)
SELECT *
FROM today
LEFT JOIN quotes yesterday ON today.Symbol = yesterday.Symbol
    AND today.yesterday = yesterday.Date
with OrderedQuotes as
(
    select 
        row_number() over(order by Symbol, Date) RowNum, 
        ID, 
        Symbol, 
        Date, 
        Open, 
        High, 
        Low, 
        Close
      from Quotes
)
select
    a.Symbol,
    a.Date,
    a.Open,
    a.High,
    a.Low,
    a.Close,
    a.Date PrevDate,
    a.Open PrevOpen,
    a.High PrevHigh,
    a.Low PrevLow,
    a.Close PrevClose,

    b.Close-a.Close/a.Close PctChange

  from OrderedQuotes a
  join OrderedQuotes b on a.Symbol = b.Symbol and a.RowNum = b.RowNum + 1
这样你就限制了你今天的成绩,如果这是一种选择的话

编辑:列为其他问题的CTE可能工作得很好,但在处理10万行或更多行时,我倾向于犹豫是否使用行数。如果前一天可能并不总是昨天,我倾向于在自己的查询中取出前一天的支票,然后将其用作参考:

DECLARE @Today DATETIME, @PreviousDay DATETIME
SELECT @Today = DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP));
SELECT @PreviousDay = MAX(Date) FROM quotes  WHERE Date < @Today;
WITH today AS
(
    SELECT  Id ,
            Symbol ,
            Date ,
            [OPEN] ,
            High ,
            LOW ,
            [CLOSE]
    FROM quotes 
    WHERE date = @today
)
SELECT *
FROM today
LEFT JOIN quotes AS previousday
    ON today.Symbol = previousday.Symbol
    AND previousday.Date = @PreviousDay

如果我正确理解您的需求,一种选择是使用递归cte:

WITH RNCTE AS (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date) rn
        FROM quotes
  ),
CTE AS (
  SELECT symbol, date, rn, cast(0 as decimal(10,2)) perc, closed
  FROM RNCTE
  WHERE rn = 1
  UNION ALL
  SELECT r.symbol, r.date, r.rn, cast(c.closed/r.closed as decimal(10,2)) perc, r.closed
  FROM CTE c 
    JOIN RNCTE r on c.symbol = r.symbol AND c.rn+1 = r.rn
  )
SELECT * FROM CTE
ORDER BY symbol, date

如果您需要每个符号的运行总数作为百分比变化,那么很容易为该金额添加一个额外的列-不完全确定您的意图是什么,所以上面只是将当前已结算金额除以以前的已结算金额。

您所拥有的一切都很好。我不知道将子查询转换为联接是否有帮助。但是,是您要求的,因此实现方法可能是再次将表连接到自身

select *
from quotes t1
inner join quotes t2
   on t1.symbol = t2.symbol and t1.date > t2.date
left outer join quotes t3
   on t2.symbol = t3.symbol and t2.date > t3.date
where t3.date is null
您可以使用选项和排名功能

 ;WITH cte AS
 (
  SELECT symbol, date, [Open], [High], [Low], [Close],
         ROW_NUMBER() OVER(PARTITION BY symbol ORDER BY date) AS Id
  FROM quotes
  )
  SELECT c1.Id, c1.symbol, c1.date, c1.[Open], c1.[High], c1.[Low], c1.[Close], 
         ISNULL(c2.[Close] / c1.[Close], 0) AS perc
  FROM cte c1 LEFT JOIN cte c2 ON c1.symbol = c2.symbol AND c1.Id = c2.Id + 1
  ORDER BY c1.symbol, c1.date
为了提高性能避免排序和RID查找,请使用此索引

CREATE INDEX ix_symbol$date_quotes ON quotes(symbol, date) INCLUDE([Open], [High], [Low], [Close])

上进行简单演示,您可以执行以下操作:

select * from quotes t1
inner join quotes t2
on t1.symbol = t2.symbol and
t2.date = (select max(date) from quotes where symbol = t1.symbol and date < t1.date)
DECLARE @Today DATETIME
SELECT @Today = DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP))

;WITH today AS
(
    SELECT  Id ,
            Symbol ,
            Date ,
            [OPEN] ,
            High ,
            LOW ,
            [CLOSE],
            DATEADD(DAY, -1, Date) AS yesterday 
    FROM quotes
    WHERE date = @today
)
SELECT *
FROM today
LEFT JOIN quotes yesterday ON today.Symbol = yesterday.Symbol
    AND today.yesterday = yesterday.Date
with OrderedQuotes as
(
    select 
        row_number() over(order by Symbol, Date) RowNum, 
        ID, 
        Symbol, 
        Date, 
        Open, 
        High, 
        Low, 
        Close
      from Quotes
)
select
    a.Symbol,
    a.Date,
    a.Open,
    a.High,
    a.Low,
    a.Close,
    a.Date PrevDate,
    a.Open PrevOpen,
    a.High PrevHigh,
    a.Low PrevLow,
    a.Close PrevClose,

    b.Close-a.Close/a.Close PctChange

  from OrderedQuotes a
  join OrderedQuotes b on a.Symbol = b.Symbol and a.RowNum = b.RowNum + 1

如果将最后一个连接更改为左连接,则会为每个符号的第一个日期获得一行,但不确定是否需要该行。

类似的内容在SQLite中适用:

SELECT ..
FROM quotes t1, quotes t2
WHERE t1.symbol = t2.symbol
    AND t1.date < t2.date
GROUP BY t2.ID
    HAVING t2.date = MIN(t2.date)
鉴于SQLite是一种最简单的类型,在MSSQL中,这也可能只需要很少的更改就可以工作。

对符号、日期进行索引


你能展示一些示例数据并把它放在小提琴里吗?1使用左连接而不是内部连接来处理新产品。然后是一个常规查询,它总是可以被过滤以排除右边为空的记录。@PieterGeerkens:我只对没有空值的行感兴趣,所以内部联接是故意存在的。左键联接是否会提高性能?我不这么认为……2012年的表现更容易/更好,但有滞后/LEAD@MichalB.:那很好;在你不得不违反DRY之前,因为你一开始没有提前考虑。我现在坚持使用2005,这就是为什么我要用dateadd来确定日期,在2008年和更高的时间有更简单的方法。这是正确的方向,但是你必须考虑到周末和假期。周末和假期需要一个日历表。这比编写代码来计算复活节要容易得多,例如,在任何给定的年份。如果股票在前一个交易日没有在市场上交易,这也将不起作用,而且这种情况经常发生。对于此类交易,应使用上一交易日(例如2天前)计算百分比变化。所以不幸的是,没有一天是所有股票的交易日…@MichalB。嗯,我想知道在这种情况下,更重要的是什么,寻找每个符号的最后交易日,或者查看任何符号的最后交易日,即如果A股昨天交易,B股没有交易,我是否仍然应该在昨天使用ISNULLs来反映B昨天没有变化?您可能希望也可能不希望通过T1中的字段分组来折叠结果。因为只有一个t1的结果和一个t2的结果,而且因为您想要昨天的值,所以可能不是。