Mam tabelę, która zawiera kolumnę wartości dziesiętnych, takich jak ta:
id value size
-- ----- ----
1 100 .02
2 99 .38
3 98 .13
4 97 .35
5 96 .15
6 95 .57
7 94 .25
8 93 .15
To, co muszę osiągnąć, jest trochę trudne do opisania, więc proszę o wyrozumiałość. To, co próbuję zrobić, to utworzyć zagregowaną wartość size
kolumny, która zwiększa się o 1 za każdym razem, gdy poprzednie wiersze sumują się do 1, gdy w porządku malejącym zgodnie z value
. Wynik wyglądałby mniej więcej tak:
id value size bucket
-- ----- ---- ------
1 100 .02 1
2 99 .38 1
3 98 .13 1
4 97 .35 1
5 96 .15 2
6 95 .57 2
7 94 .25 2
8 93 .15 3
Moja naiwna pierwsza próba polegała na utrzymywaniu działania, SUM
a następnie na CEILING
tej wartości, jednak nie dotyczy to przypadku, w którym niektóre rekordy size
przyczyniają się w sumie do dwóch oddzielnych segmentów. Poniższy przykład może to wyjaśnić:
id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
1 100 .02 .02 1 .02 1
2 99 .38 .40 1 .40 1
3 98 .13 .53 1 .53 1
4 97 .35 .88 1 .88 1
5 96 .15 1.03 2 .15 2
6 95 .57 1.60 2 .72 2
7 94 .25 1.85 2 .97 2
8 93 .15 2.00 2 .15 3
Jak widać, gdybym po prostu używać CEILING
na crude_sum
płycie # 8 będzie przypisany do wiadra 2. Jest to spowodowane przez size
zapisów # 5 i # 8 rozdzielona na dwa wiadra. Zamiast tego idealnym rozwiązaniem jest resetowanie sumy za każdym razem, gdy osiągnie 1, co następnie zwiększa bucket
kolumnę i rozpoczyna nową SUM
operację, rozpoczynając od size
wartości bieżącego rekordu. Ponieważ kolejność rekordów jest ważna dla tej operacji, dołączyłem value
kolumnę, która ma być sortowana w kolejności malejącej.
Moje pierwsze próby obejmowały wielokrotne przekazywanie danych, raz, aby wykonać SUM
operację, raz jeszcze CEILING
, itd. Oto przykład tego, co zrobiłem, aby utworzyć crude_sum
kolumnę:
SELECT
id,
value,
size,
(SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
table t1
Który został użyty w UPDATE
operacji, aby wstawić wartość do tabeli do późniejszej pracy.
Edycja: Chciałbym wziąć kolejny kłopot z wyjaśnieniem tego, więc proszę bardzo. Wyobraź sobie, że każdy rekord jest przedmiotem fizycznym. Ten przedmiot ma powiązaną z nim wartość i rozmiar fizyczny mniejszy niż jeden. Mam serię wiader o pojemności dokładnie 1 i muszę określić, ile z tych wiader potrzebuję i które wiadro zawiera każdy element zgodnie z wartością przedmiotu, posortowane od najwyższej do najniższej.
Przedmiot fizyczny nie może istnieć w dwóch miejscach jednocześnie, więc musi znajdować się w jednym lub drugim wiadrze. Dlatego nie mogę wykonać działającego CEILING
rozwiązania total + , ponieważ pozwoliłoby to rekordom na zwiększenie ich rozmiaru do dwóch segmentów.
distinct_count
komplikuje rzeczy. Aaron Bertrand ma świetne podsumowanie twoich opcji na SQL Server dla tego rodzaju okienkowania. Użyłem metody „dziwacznej aktualizacji” do obliczeniadistinct_sum
, którą można zobaczyć tutaj na SQL Fiddle , ale jest to niewiarygodne.Odpowiedzi:
Nie jestem pewien, jakiego rodzaju wydajności szukasz, ale jeśli CLR lub aplikacja zewnętrzna nie jest opcją, kursor pozostaje. Na moim starym laptopie przechodzę przez 1 000 000 wierszy w około 100 sekund przy użyciu następującego rozwiązania. Zaletą jest to, że skaluje się liniowo, więc spędziłbym około 20 minut, aby przejść przez całą rzecz. Przy przyzwoitym serwerze będziesz szybszy, ale nie o rząd wielkości, więc ukończenie go zajmie jeszcze kilka minut. Jeśli jest to jednorazowy proces, prawdopodobnie możesz sobie pozwolić na powolność. Jeśli musisz regularnie uruchamiać to jako raport lub podobny, możesz przechowywać wartości w tej samej tabeli i aktualizować je wraz z dodawaniem nowych wierszy, np. W wyzwalaczu.
W każdym razie oto kod:
Porzuca i odtwarza tabelę MyTable, wypełnia ją 1000000 wierszami, a następnie przystępuje do pracy.
Kursor kopiuje każdy wiersz do tabeli tymczasowej podczas wykonywania obliczeń. Na koniec select zwraca obliczone wyniki. Możesz być trochę szybszy, jeśli nie kopiujesz danych, ale zamiast tego dokonujesz aktualizacji w miejscu.
Jeśli masz opcję uaktualnienia do SQL 2012, możesz spojrzeć na nowe obsługiwane przez bufor okna ruchome agregaty okien, które powinny zapewnić lepszą wydajność.
Na marginesie, jeśli masz zainstalowany zestaw z uprawnieniem zestaw_zestawu = bezpieczny, możesz zrobić więcej złych rzeczy na serwerze ze standardowym T-SQLem niż z zestawem, więc kontynuowałbym pracę nad usunięciem tej bariery - masz dobry użytek sprawa tutaj, gdzie CLR naprawdę by ci pomógł.
źródło
W przypadku braku nowych funkcji okienkowania w programie SQL Server 2012 skomplikowane okienkowanie można wykonać za pomocą rekurencyjnych CTE. Zastanawiam się, jak dobrze poradzi sobie z milionami wierszy.
Poniższe rozwiązanie obejmuje wszystkie opisane przypadki. Możesz zobaczyć to w akcji tutaj na SQL Fiddle .
Teraz weź głęboki oddech. Istnieją tutaj dwa kluczowe CTE, każde poprzedzone krótkim komentarzem. Reszta to po prostu „czyszczenie” CTE, na przykład, aby wyciągnąć odpowiednie rzędy po ich uszeregowaniu.
To rozwiązanie zakłada, że
id
jest to sekwencja bez przerw. Jeśli nie, musisz wygenerować własną sekwencję bez przerw, dodając na początku dodatkowy CTE, który numeruje wierszeROW_NUMBER()
zgodnie z pożądaną kolejnością (npROW_NUMBER() OVER (ORDER BY value DESC)
.).Fankly, to jest dość gadatliwe.
źródło
crude_sum
zdistinct_sum
powiązanymibucket
kolumnami, aby zobaczyć, co mam na myśli.To wydaje się głupie rozwiązanie i prawdopodobnie nie będzie dobrze skalować, więc przetestuj dokładnie, jeśli go używasz. Ponieważ główny problem wynika z „przestrzeni” pozostawionej w segmencie, najpierw musiałem utworzyć rekord wypełniający, aby połączyć dane z danymi.
http://sqlfiddle.com/#!3/72ad4/14/0
źródło
Oto kolejne rekurencyjne rozwiązanie CTE, chociaż powiedziałbym, że jest prostsze niż sugestia @ Nicka . W rzeczywistości jest bliżej kursora @ Sebastiana , tylko ja użyłem różnic biegowych zamiast sum całkowitych. (Na początku nawet myślałem, że odpowiedź @ Nicka będzie zgodna z tym, co tutaj sugeruję, i dopiero po dowiedzeniu się, że jego pytanie było w rzeczywistości zupełnie innym pytaniem, które postanowiłem zaoferować).
Uwaga: to zapytanie zakłada, że
value
kolumna składa się z unikalnych wartości bez przerw. Jeśli tak nie jest, musisz wprowadzić obliczoną kolumnę rankingu na podstawie malejącej kolejnościvalue
i użyć jej w rekurencyjnym CTE zamiastvalue
łączyć część rekurencyjną z kotwicą.Demo SQL Fiddle dla tego zapytania można znaleźć tutaj .
źródło
size
zroom_left
) wydawało się łatwiejsze / bardziej naturalne, niż porównywanie pojedynczej wartości z wyrażeniem (1
zrunning_size
+size
). Na początku nie użyłemis_new_bucket
flagi, ale kilkaCASE WHEN t.size > r.room_left ...
(„kilka”, ponieważ również obliczałem (i zwracałem) całkowity rozmiar, ale potem pomyślałem przeciwko temu ze względu na prostotę), więc pomyślałem, że będzie bardziej elegancki w ten sposób.