2015-07-24 6 views
9

Obecnie mam tych tabel:SQL Dołącz na najbliższym dostępnym terminie

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) 

chcę mieć czyste tabelę, która pokazuje mi identyfikator zabezpieczeń, położenie bezpieczeństwa i najnowszą dostępną cenę tego zabezpieczenia, gdy data jest przekazywane.

Teraz, kiedy należy wykonać następujące czynności:

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 

uzyskać poprawny wynik

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

Jednakże mogą być indywidualne okoliczności, w których cena zapasów nie jest dostępna. W związku z tym chciałbym móc uzyskać najnowszą dostępną cenę.

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 

zwraca 0 wierszy z powodu braku danych, a robi

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 powielać wiersze.

Próbowałem mnóstwo różnych metodologii i po prostu nie mogę uzyskać wyjścia, które chcę. Co więcej, chciałbym również móc uzyskać jedną kolumnę z ceną najbliższą danej dacie (nazwać ją START_DATE) i jedną kolumnę z ceną najbliższą drugiej dacie (pod to END_DATE) i jedną kolumną, która ma być pozycją [email protected]_DATE - [email protected]_DATE . Cena jest zawsze pobierana z tego samego #SECURITY_TEMP_PRICE_HISTORY.

Jednak moja wiedza SQL jest po prostu kłopotliwa i nie mogłem wymyślić na nią skutecznego sposobu. Każda pomoc będzie doceniona. Należy również pamiętać, że tabela #SECURITY_PRICE_HISTORY table may contain more securities than the #SECURITY_POST.

+1

Dziękuję wszystkim za oczyszczenie mojego posta! – buellboy

Odpowiedz

12

To powinno wystarczyć. OUTER APPLY jest operatorem łączenia, który (podobnie jak CROSS APPLY) pozwala wyprowadzonej tabeli na odwołanie zewnętrzne.

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 
; 

Można również rozważyć słabnącym cen papierów wartościowych, które są bardzo stare, lub ograniczenie odnośnika do najnowszego bezpieczeństwa na pewien okres (tydzień lub miesiąc albo coś).

Upewnij się, że tabela historii cen zawiera indeks z numerem (ID, PriceDate), aby wyszukiwanie podkwerendy mogło korzystać z wyszukiwania zakresów, a wydajność może być dobra. Upewnij się, że wykonujesz jakąkolwiek matematykę daty w tabeli bezpieczeństwa, a nie w tabeli historii, lub zmusisz podkwerendę wyszukiwania ceny jako niesympatyczną, co byłoby niekorzystne dla wydajności, ponieważ szukany zakres nie byłby możliwy.

Jeśli dla zabezpieczenia nie zostanie znaleziona żadna cena, OUTER APPLY nadal będzie zezwalać na istnienie wiersza, więc cena będzie wyświetlana jako NULL. Jeśli chcesz, aby papiery wartościowe nie były pokazywane, gdy nie znaleziono odpowiedniej ceny, użyj CROSS APPLY.

Dla drugiej części pytania, można to zrobić z dwóch OUTER APPLY operacji, tak jak poniżej:

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 
; 

z danymi Daje następujący zestaw wyników:

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 

ostatni chociaż nie wszyscy się z tym zgadzają, zachęcam do nadania nazwom kolumn nazwy ID z nazwą tabeli jak w SecurityID zamiast ID. Z mojego doświadczenia wynika, że ​​używanie ID prowadzi tylko do problemów.

Uwaga: istnieje sposób rozwiązania tego problemu za pomocą funkcji okienkowania Row_Number(). Jeśli masz stosunkowo mało punktów w porównaniu do liczby akcji, a szukasz większości cen w tabeli historii, możesz uzyskać lepszą wydajność dzięki tej metodzie. Jeśli jednak istnieje duża liczba punktów cenowych na jeden magazyn lub filtrujesz tylko kilka zasobów, możesz uzyskać lepszą wydajność dzięki metodzie, którą ci pokazałem.

+0

Dzięki v. Dużo za ten ErikE. Udaje mi się zastosować metodologię cross apply/outer apply i zastosowałem ją (naprawdę przepraszam za to!) Do zapytania SQL, nad którym pracowałem od miesięcy, mogłem dodać wszystkie rodzaje cross/apply z innych tabel z wynikowym zapytaniem działającym około 5 razy szybciej niż stara metodologia, z której korzystałem. – buellboy