Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [MySQL]Zdublowane wyniki zapytania
Forum PHP.pl > Forum > Przedszkole
alex19
Zbudowałem prostą wyszukiwarkę, wszystko wydaje się być OK, ale w pewnych przypadkach baza zwraca zdublowane wyniki i nie mam pojęcia dlaczego.
Cały bajer polega na tym, że jest sobie tabela z klientami. Każdy z nich ma jakiś nick (unikatowy) oraz jakieś nieistotne w tym przypadku dane. Jest też druga tabela z aliasami, w której są dodatkowe nicki dla klienta.
Wyszukiwarka ma za zadanie wyszukiwać klientów w obydwu tabelach na raz.

Zapytanie, które to realizuje wygląda tak:
  1. SELECT *, klient.id_klient AS id_klient FROM klient
  2. LEFT JOIN alias ON klient.id_klient = alias.id_klient
  3. WHERE id_user IS NOT NULL
  4. AND ( LOWER(nick) LIKE LOWER('%$nick%') OR LOWER(alias) LIKE LOWER('%$nick%') )

Wszystko wydaje się być OK, ale trafił się dziwny przypadek.
Mam klienta o bazowym nicku "car-ledam", czyli to jest nick z tabeli klienci. Ten sam klient ma tez dwa aliasy przypisane w drugiej tabeli "car_ledam" oraz "carledam".
Teraz gdy do zapytania podstawimy nick "carledam" otrzymamy jeden wynik co jest zgodne z założeniami, ale gdy tylko podstawimy "car-ledam" lub "car_ledam" zawsze wynik jest zdublowany i pokazują się dwa rekordy (te same oczywiście).

Czy ktoś może mi wytłumaczyć dlaczego się tak dzieje?
trueblue
W MySQL "_" to wildcard zastępujący dokładnie jeden znak.
alex19
Ooooooooooo!
To trochę rozjaśnia sprawę, ale co z "-"?

Jak można w takim razie uchronić się przed "uruchomieniem" wildcarda i szukać nie znaku, a "_"?
trueblue
Spróbuj escapować poprzez \
http://dev.mysql.com/doc/refman/5.0/en/str...-functions.html
A minus...hm....
alex19
OK, escape przez "\" dziala bez problemu więc przepuszczenie zmiennej przez funkcje mysql_real_escape_string() załatwi problem z "_".
Niestety escapeowanie "-" nic nie zmienia sad.gif
trueblue
A możesz pokazać co dostajesz w zmiennej $nick zatwierdzając formularz?
alex19
Cytat(trueblue @ 28.03.2014, 18:10:05 ) *
A możesz pokazać co dostajesz w zmiennej $nick zatwierdzając formularz?


Nie wink.gif, bo zapytanie w "gołym" SQLu z reki generuje takie wyniki. Wyniki są takie same zarówno w mojej aplikacji jaki przy ręcznym pisaniu zapytań w czymkolwiek.
Problem na pewno nie leży w PHP tylko w samym SQLu sad.gif.
trueblue
Rozumiem.
To może zrzut struktury tabeli alias?
alex19
Cytat(trueblue @ 28.03.2014, 19:19:59 ) *
Rozumiem.
To może zrzut struktury tabeli alias?


Hehehehe... Nic twórczo niestety sad.gif
  1. +-----------+-------------+------+-----+---------+-------+
  2. | FIELD | Type | NULL | KEY | DEFAULT | Extra |
  3. +-----------+-------------+------+-----+---------+-------+
  4. | alias | varchar(45) | NO | PRI | NULL | |
  5. | id_klient | int(11) | NO | PRI | NULL | |
  6. +-----------+-------------+------+-----+---------+-------+

trueblue
Chciałem zobaczyć kodowanie tabeli i pól:)
Używasz SQLyog'a? W zakładce Info jest CREATE tabeli.
alex19
Kodowanie to utf8_bin
trueblue
Mam wrażenie, że 4 godziny siedzimy niepotrzebnie, ale może się mylę.
Czy: WHERE id_user IS NOT NULL dotyczy prawidłowe kolumny?

  1. SELECT *, klient.id_klient AS id_klient FROM klient
  2. LEFT JOIN alias ON klient.id_klient = alias.id_klient
  3. WHERE
  4. ( LOWER(nick) LIKE LOWER('%car-ledam%') OR LOWER(alias) LIKE LOWER('%car-ledam') )

To jest zapytanie bez tego warunku.
Dla "car-ledam" następuje spełnienie lewej części OR przez co są wybierane dwa powiązane rekordy z tabeli alias.
alex19
I na to pytanie nie mogę odpowiedzieć. Niestety zasymulowałem u siebie na wersji developerskiej taka sytuację i problemu nie ma. Do bazy w wersji produkcyjnej niestety dostęp będę miał dopiero w poniedziałek ;(

W teorii zapytanie powinno zwrócić idklenta i jego alias, bo takowy w bazie jest na pewno. Nie ma też duplikatu klienta.

Cytat(trueblue @ 28.03.2014, 19:41:54 ) *
Mam wrażenie, że 4 godziny siedzimy niepotrzebnie, ale może się mylę.
Czy: WHERE id_user IS NOT NULL dotyczy prawidłowe kolumny?

To jest zapytanie bez tego warunku.
Dla "car-ledam" następuje spełnienie lewej części OR przez co są wybierane dwa powiązane rekordy z tabeli alias.


Tak, kolumna jest OK. To jest trochę bezsensowny warunek, ale on jest tylko po to żeby można było budować zapytanie dodając do niego kolejne warunki bez kombinowania, tak że zawsze doklejane jest " and jakiś_warunek" smile.gif
Ogólnie rzecz biorąc wyszukiwarka działa dobrze, ale z tym minusem coś jest tylko nie tak.

Jak się mocniej zastanowić, to chyba coś nie gra z samym znakiem "-", bo gdy użyjemy znanego mi już wildcarda, to też są dwa wyniki, a i tak powinien być tylko jeden.

Zapytam przekornie i ciutkę inaczej. A co w teorii powinno się wyświetlić gdybyśmy szukali tak:
  1. SELECT *, klient.id_klient AS id_klient FROM klient
  2. LEFT JOIN alias ON klient.id_klient = alias.id_klient
  3. WHERE
  4. ( LOWER(nick) LIKE LOWER('%car%ledam%') OR LOWER(alias) LIKE LOWER('%car%ledam%') )
trueblue
Powinno wyświetlić 2 rekordy, bo warunek jest spełniony zarówno dla nick, jak i obydwu aliasów.

P.S. 1=1 AND smile.gif
alex19
A nie przypadkiem trzy?
trueblue
Nie:)
Jeden - nick: "car-ledam" i alias: "car_ledam"
Drugi - nick: "car-ledam" i alias: "carledam"
Nawet jak nie będzie warunku na id_klient, to iloczyn kartezjański zwróci 2 rekordy (1x2).
Dlatego obstaję, że nie potrzebnie siedzimy od 5 godzin i zapytanie z "-" działa poprawnie.
alex19
No dobrze.
Skoro zapytanie bez znaku po środku zwraca jeden rekord, z "\_" zwraca jeden rekord, to co może powodować, że "-" zwraca dwa rekordy?

Jak już napisałem wcześniej u mnie na bazie developerskiej nie ma tego problemu. Wygląda na to jakby ludki w jakiś magiczny sposób dodały "-" i to powodowało problem.
Pytanie tylko jak to znaleźć?
Potestuję i poszukam jeszcze w poniedziałek.
trueblue
I to mnie dziwi, bo "_" powinno zwrócić true dla dla obydwu warunków, czyli dla klient gdzie "car-ledam" i alias "car_ledam", co wygeneruje dwa rekordy.
alex19
Dobra, mamy poniedziałek i mam dostęp do bazy.
Właśnie zauważyłem, że u mnie jest ten sam problem, ale działa inaczej, bo źle wpisałem podstawową nazwę klienta.

Mamy tak:
-dla wyszukiwania "%car\_ledam%" - jeden wynik << to jest alias
-dla wyszukiwania "%carledam%" - dwa wyniki << to jest alias!!!
-dla wyszukiwania "%car-ledam%" - dwa wyniki << to jest bazowa nazwa klienta

Na mojej developerskiej wersji jako nazwę bazową dałem "carledam", a reszta reszta to aliasy i jest podobnie ale inaczej, bo
-dla wyszukiwania "%car-ledam%" - jeden wynik
-dla wyszukiwania "%car\_ledam%" - jeden wynik
-dla wyszukiwania "%carledam%" - dwa wyniki exclamation.gif!
trueblue
W pierwszym przykładzie, punkt 2, u mnie zwraca 1 wynik.
Zobacz czy w którejś tabeli nie masz powtórzeń.

W drugim przykładzie punkt 3, zwraca prawidłowo. Warunek dla nick w klient jest prawidłowy i spięło po kluczu id_klient z dwoma rekordami w tabeli alias.
alex19
Cytat(trueblue @ 31.03.2014, 11:40:30 ) *
W pierwszym przykładzie, punkt 2, u mnie zwraca 1 wynik.
Zobacz czy w którejś tabeli nie masz powtórzeń.


Nie ma żadnych powtórzeń :/

Cytat(trueblue @ 31.03.2014, 11:40:30 ) *
W drugim przykładzie punkt 3, zwraca prawidłowo. Warunek dla nick w klient jest prawidłowy i spięło po kluczu id_klient z dwoma rekordami w tabeli alias.


OK, ale jak się można przed tym uchronić i otrzymać tylko jeden wynik zapytania?
trueblue
Cytat(alex19 @ 31.03.2014, 11:54:11 ) *
OK, ale jak się można przed tym uchronić i otrzymać tylko jeden wynik zapytania?

Zależy jakie informacje chcesz otrzymać w odpowiedzi. Jeśli interesują Cię tylko informacje z tabeli klient, to można tak:
  1. SELECT klient.* AS id_klient FROM klient
  2. LEFT JOIN alias ON klient.id_klient = alias.id_klient
  3. WHERE LOWER(alias) LIKE LOWER('%$nick%')
  4. UNION
  5. SELECT klient.* AS id_klient FROM klient
  6. LEFT JOIN alias ON klient.id_klient = alias.id_klient
  7. WHERE LOWER(nick) LIKE LOWER('%$nick%')

Zwróci jeden rekord pod warunkiem, że nie będziesz mieć w tabeli alias aliasu identycznego z nickiem klienta, oraz duplikatów nicków lub aliasów w którejś z tabel.

Możesz również w swoim zapytaniu ograniczyć wyniki poprzez klauzulę: LIMIT 0,1
alex19
Cytat(trueblue @ 31.03.2014, 12:03:06 ) *
Zależy jakie informacje chcesz otrzymać w odpowiedzi. Jeśli interesują Cię tylko informacje z tabeli klient, to można tak:

Zwróci jeden rekord pod warunkiem, że nie będziesz mieć w tabeli alias aliasu identycznego z nickiem klienta, oraz duplikatów nicków lub aliasów w którejś z tabel.

Możesz również w swoim zapytaniu ograniczyć wyniki poprzez klauzulę: LIMIT 0,1


Nie, nie, nie... rozwiązania odpadają, bo
To co pokazałem to tylko środek zapytania i mogą być do niego doklejone potem kolejne warunki, a początek jest brany zupełnie skądinąd więc zapytanie musi mieć stałą konstrukcję i oczywiście jest jeszcze pozałączane czasem z innymi tabelami.

LIMIT nie wchodzi w grę, bo to jest wyszukiwarka i w gruncie rzeczy musi pokazywać więcej niż jedną pozycje, a do tego na koniec jest doklejana paginacja smile.gif
trueblue
To może GROUP BY klient.id_klient? Ale wtedy z zapytania (w wyborze pól) muszą wypaść te dotyczące tabeli alias.
alex19
GROUP BY działa.

Czy group by musi być prawie na końcu zapytania czy może pozostać gdzieś w środku? Mam tu na myśli taką sytuację że na końcu tego co tu przedstawiałem dodaje group by, ale do tego doklejam jeszcze np "and cos>10 and cośtam"
trueblue
Zależy czego dotyczą warunki.
Przykład, który podajesz może dotyczyć rekordów przed grupowaniem, lub po (wtedy po GROUP BY klauzula HAVING).
Podejrzewam jednak, że u Ciebie będą to warunki przed grupowaniem.

Może rozjaśnij. Czego będą dotyczyć warunki?
alex19
Innych pól z tabeli, ale przed chwilą patrzyłem w kod i nie ma to znaczenia, bo akurat w tym miejscu jestem wstanie wstawic "group by" po wszystkich warunkach i będzie dobrze.

Dziękuję za pomoc!
mmmmmmm
Sprawdz zapytanie:
  1. SELECT klient.* FROM klient
  2. LEFT JOIN alias ON klient.id_klient = alias.id_klient
  3. WHERE INSRT(alias, '$nick')>0 OR INSTR(nick, '$nick')>0
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.