2013-03-20 21 views
9

Ho una tabella MS SQL che contiene i dati di magazzino con le seguenti colonne: Id, Symbol, Date, Open, High, Low, Close.Come partecipare alla tabella di join in modo che ogni record sia unito al record "precedente"?

Vorrei partecipare autonomamente al tavolo, così posso ottenere una variazione giornaliera di Close.

Devo creare una query che si unirà alla tabella con se stessa in modo che ogni record contenga anche i dati della sessione precedente (attenzione, non posso usare la data di ieri).

La mia idea è quella di fare qualcosa di simile:

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) 

Tuttavia non so se questo è il modo corretto/più veloce. Che cosa dovrei prendere in considerazione quando si pensa alle prestazioni? (Ad esempio, l'inserimento dell'indice UNIQUE su una coppia (Symbol, Date) migliora le prestazioni?)

In questa tabella ci saranno circa 100.000 nuovi record all'anno. Sto usando MS SQL Server 2008

+3

si può mostrare alcuni dati di esempio e gettarlo in un violino? – Kermit

+0

(1) Utilizzare un join di sinistra invece di un join interno, per gestire i nuovi prodotti. Quindi hai una query generale, che può sempre essere filtrata per escludere i record con un NULL Right-Hand-Side. –

+1

@PieterGeerkens: Mi interessano solo le righe che non hanno valori NULL, quindi INNER JOIN è lì apposta. LEFT JOIN migliorerà le prestazioni? Io non la penso così ... –

risposta

0

Si potrebbe fare qualcosa di simile:

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 

questo modo di limitare i risultati "oggi", se questa è un'opzione.

MODIFICA: le CTE elencate come altre domande potrebbero funzionare correttamente, ma io tendo ad essere titubante nell'utilizzare ROW_NUMBER quando si gestiscono file da 100.000 o più. Se il giorno precedente non può essere sempre ieri, tendo a preferire per tirare fuori l'assegno per il giorno precedente nella sua propria query quindi utilizzarlo per riferimento:

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 
+0

Sono bloccato con il 2005 al momento, che è il motivo per cui ho ottenuto che dateadd per capire una data, ci sono modi più semplici nel 2008 e più in alto per farlo. –

+0

Questo è sulla strada giusta, ma è necessario tenere conto dei fine settimana e delle vacanze. –

+0

I fine settimana e i giorni festivi richiedono una tabella di calendario. È molto più facile popolarlo una volta che scrivere un codice che calcolerà la Pasqua, ad esempio, in un dato anno. –

9

Una possibilità è quella di utilizzare un CTE ricorsiva (se sto capire le vostre esigenze in modo corretto):

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 

SQL Fiddle Demo

Se avete bisogno di un totale parziale per ogni simbolo da utilizzare come variazione percentuale, quindi abbastanza facile aggiungere una colonna aggiuntiva per tale importo - non ero completamente sicuro di quali fossero le tue intenzioni w prima, quindi quanto sopra divide l'attuale importo chiuso per la precedente quantità chiusa.

0

Quello che hai è buono. Non so se la traduzione della sotto-query nel join aiuterà. Tuttavia, l'hai chiesto, quindi il modo per farlo potrebbe essere quello di unirsi al tavolo ancora una volta.

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 
+0

È possibile o meno che si desideri raggruppare in base ai campi in T1 per comprimere i risultati. Poiché c'è un solo risultato di t1 e un solo risultato di t2, e dal momento che vuoi i valori di ieri, forse no. –

0

È possibile utilizzare l'opzione con CTE e ROW_NUMBER funzione di rango

;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 

per migliorare le prestazioni (evitando di ordinamento e di ricerca RID) utilizzare questo indice

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

semplice demo su SQLFiddle

1

Tu fai qualcosa di simile:

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 

Se si modifica l'ultimo unirsi ad un LEFT JOIN si ottiene una riga per la prima data per ogni simbolo, non so se avete bisogno di questo.

1

Qualcosa di simile a questo lavoro guarderebbero in 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) 

Dato SQLite è un più semplice di un genere, forse in MSSQL questo funziona anche con modifiche minime.

0

Index sul (symbol, date)

SELECT * 
FROM quotes q_curr 
CROSS APPLY (
    SELECT TOP(1) * 
    FROM quotes 
    WHERE symbol = q_curr.symbol 
    AND date < q_curr.date 
    ORDER BY date DESC 
) q_prev 
Problemi correlati