Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [SQL] Najbardziej podobna grupa rekordów
Forum PHP.pl > Forum > Bazy danych > MySQL
markuz
Witam,

Posiadam tabelę np.

chars
- id
- char

sums
- id
- char_id
- sum

Gdzie dla każdego wpisu w chars istnieje 10 rekordów w sums dla danego char. Np.

chars:
- 1
- 'A'

sums:
- 1
- 1
- 235

Teraz potrzebne mi optymalne zapytanie które na podstawie 10 liczb wyszuka najbliższy znak (chars.char) dla najbardziej podobnej sumy (sums.sum)

Ma ktoś pomysł jak to zrobić?
trueblue
A kiedy suma będzie najbardziej podobna do znaku?
markuz
Cytat
A kiedy suma będzie najbardziej podobna do znaku?

Wtedy gdy sumy wejściowe (10 liczb) będą najbliższe odpowiednim sumą dla danego znaku (biorąc pod uwagę kolejność tzn. dla 1 liczby wejściowej porównujemy tylko 1 sumy znaków).

Np. mamy w bazie 2 znaki 'A' oraz 'B'

Sumy dla 'A' i 'B' (dla uproszczenia tylko 3 sumy):

'A' 'B'
52 8
123 115
15 21

Dane wejściowe (sumy) :
38, 120, 8

Znak wynikowy: 'A'

Głównie chodzi o to, że mam obrazki dla każdego znaku. Obrazek jest przerobionym dźwiękiem na png, a następnie ten png jest przetwarzany na 2 kolorowy (czarny i biały). Następnie wykonuję 10 próbek dla każdego znaku i obliczam ilość białych pikseli w każdej z nich.

Znalazłem skrypt ale tylko dla porównania 1 sumy, nie wiem jak tu wkleić optymalnego join`a:

  1. SELECT top 4 Number
  2. FROM (
  3. SELECT Number
  4. FROM NumberTable
  5. WHERE number BETWEEN 1009-100 AND 1009+100
  6. )
  7. WHERE number <> 1009
  8. ORDER BY abs(Number - 1009)


Gdzie Number to sums.sum, 1009 to suma której szukamy a 100 to zakres.
trueblue
Jeśli dobrze zrozumiałem.

  1. SELECT tmp.char
  2. FROM
  3. ((SELECT c.char,MIN(ABS(s.sum-38)) AS diff FROM chars AS c,sums AS s
  4. WHERE c.id=s.char_id
  5. GROUP BY c.char
  6. ORDER BY diff LIMIT 0,1)
  7. UNION
  8. (SELECT c.char,MIN(ABS(s.sum-120)) AS diff FROM chars AS c,sums AS s
  9. WHERE c.id=s.char_id
  10. GROUP BY c.char
  11. ORDER BY diff LIMIT 0,1)
  12. UNION
  13. (SELECT c.char,MIN(ABS(s.sum-8)) AS diff FROM chars AS c,sums AS s
  14. WHERE c.id=s.char_id
  15. GROUP BY c.char
  16. ORDER BY diff LIMIT 0,1)
  17. ) AS tmp
  18. GROUP BY tmp.char
  19. ORDER BY COUNT(tmp.char) DESC LIMIT 0,1
Pyton_000
On wprowadza 10 liczb haha.gif
trueblue
Ale dla przykładu podał 3 wejściowe. Rozbuduje sobie.
markuz
@trueblue Wydaje mi się, że to zapytanie nie rozwiąże mojego problemu.

Przebudowałem nieco strukturę tabel :

chars
- id
- char ('A', 'B' itd.)

samples
- id
- char_id (chars.id)
- part (od 0 do 9)
- sum (od 0 do 99999)

Myślałem o rozbiciu tego na np. 10 zapytań dla każdego part, i w wyniku otrzymamy 10 najbardziej podobnych znaków dla każdego partu osobno np. 'A', 'B', 'C', 'A', 'D', 'A' ... 'A' i wtedy wybiorę ten znak który występuje najczęściej - ale to rozwiązanie także nie jest do końca satysfakcjonujące ponieważ niektóre ze znaków naprawdę mają podobne part`y.

Zawsze mogę pobrać wszystkie znaki i party a następnie dla każdego obliczać coś ala 'stopień zgodności' - i znak z największym stopniem zgodności byłby wybierany - ale to wiąże się z pobraniem kilku k rekordów za każdym razem i foreachem po nich. Hmm.
trueblue
Dlaczego dodałeś kolumnę part? Czy odpowiada ona numerowi próbki?
markuz
Dokładnie. I porównujemy tylko te same party ze sobą.

Aktualnie zrobiłem tak, że pobieram wszystkie znaki wraz z partami do tablicy - robię foreach po tej tablicy i dla każdego partu każdej litery/znaku sumuję "różnicę". Potem sprawdzam gdzie różnica wynosiła najmniej i wybieram ten znak - skuteczność 99% z wyjątkami - problemem jest to, że przetwarzam wszystko po stronie PHP a wolałbym po stronie MySQL (wiadomo - szybciej).

To jest moja metoda która na wejściu dostaje tablice próbek (partów) a na wyjściu dopasowany znak.

  1.  
  2. public function matchChar($s)
  3. {
  4. if (!is_array($s))
  5. return false;
  6.  
  7. $char = '';
  8. $samples = array();
  9. $differences = array();
  10. $chars = $this->db->selectAssocs("SELECT * FROM chars");
  11.  
  12. foreach($chars as $i => $char)
  13. $samples[$i] = $this->db->selectAssocs("SELECT * FROM samples WHERE char_id = " . $char['id']);
  14.  
  15. foreach($chars as $i => $char)
  16. {
  17. $difference = 0;
  18. foreach($samples[$i] as $part => $sample) {
  19. if(!isset($differences[$i])) $differences[$i] = 0;
  20. $differences[$i] += abs($s[$part] - $sample['sum']);
  21. }
  22. }
  23.  
  24. $i = array_keys($differences, min($differences));
  25. $i = $i[0];
  26. $char = $chars[$i]['char'];
  27.  
  28. return $char;
  29. }
  30.  
trueblue
No, tak. Mój SQL tego nie uwzględniał, tj. porównania N-tej próbki wejściowej z N-tą próbką w tabeli.

  1. SELECT tmp.char
  2. FROM
  3. ((SELECT c.char,MIN(ABS(s.sum-38)) AS diff FROM chars AS c,sums AS s
  4. WHERE c.id=s.char_id AND s.part=0
  5. GROUP BY c.char
  6. ORDER BY diff LIMIT 0,1)
  7. UNION
  8. (SELECT c.char,MIN(ABS(s.sum-120)) AS diff FROM chars AS c,sums AS s
  9. WHERE c.id=s.char_id AND s.part=1
  10. GROUP BY c.char
  11. ORDER BY diff LIMIT 0,1)
  12. UNION
  13. (SELECT c.char,MIN(ABS(s.sum-8)) AS diff FROM chars AS c,sums AS s
  14. WHERE c.id=s.char_id AND s.part=2
  15. GROUP BY c.char
  16. ORDER BY diff LIMIT 0,1)
  17. ) AS tmp
  18. GROUP BY tmp.char
  19. ORDER BY COUNT(tmp.char) DESC LIMIT 0,1
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.