Nie prowadzę bloga, więc wrzucam na forum, może się komuś przyda.
Czasem jest tak że chcielibyśmy się pozbyć duplikatów z bazy. Język SQL nie posiada żadnej funckji/procedury która pozwala na wyszukiwanie rekordów na podstawie duplikatów, przydała by się jakaś odwrotnosc DISTINCT albo DISTINCTROW. Niestety musimy sobie jakos radzić sami. Jak to osiągnąć?
Są dwa sposoby:
Sposób 1:
SELECT * FROM tabela WHERE kolumna IN (SELECT kolumna FROM tabela GROUP BY kolumna HAVING COUNT(*) > 1)
Bardzo niewydajny (5 tys rekordów, bez indeksów… po 3 minutach odpuściłem sobie), operacja “SELECT kolumna FROM tabela GROUP BY kolumna HEVING COUNT(*) > 1” będzie wykonywana na każdym wierszu w tabeli. Można oczywiście rozbić to na dwa zapytania, pierwsze pobierające wszystkie zduplikowane wartości, drugie pobierające wszystkie rekordy ze zduplikowaną wartościa:
@duplicates = ActiveRecord::Base.connection.select_all("SELECT kolumna FROM tabela GROUP BY kolumna HAVING COUNT(*) > 1").map { |r| r["kolumna"] }
Model.find(:all, :conditions => ["kolumna IN(?)",@duplicates.join(",")])
Pomimo tego sposób jest nadal bardzo nieelegandzki i niewydajny.
Sposób 2:
SELECT tabela.* FROM tabela INNER JOIN
(
SELECT kolumna FROM kolumna
GROUP BY kolumna
HAVING count(*) > 1
) duplicates on tabela.kolumna = duplicates.kolumna
Tutaj jeśli chodzi o wydajność sprawa ma się znacznie lepiej. Nasze zapytanie grupujące wykonane jest tylko raz. Na 5 tys rekordów przeszukanie bazy zajmuje 0.03 sec (bez indeksów). Najciekawszą rzeczą w 2 sposobie jest chyba użycie go w metodzie find, dzięki :joins jest to bardzo łatwe:
Model.find(:all, :joins => "INNER JOIN (SELECT kolumna FROM tabela GROUP BY kolumna HAVING COUNT(*) > 1) duplicates on duplicates.kolumna = models.kolumna")
W przypadku Oracla istnieje chyba jeszcze możliwość wykorzystania OVER i PARTITION BY, niestety ja oracla nie używam, pozatym sposób drugi zadziała szybko praktycznie na każdej badzie danych sql, a przedewszystkim łatwo się integruje z ActiveRecord.