Oj, den där rubriken blev lurig. Jag har en tabell med företag, en tabell med städer och en tabell med län. Jag har skrivit en ganska enkel, men sofistikerad sökmotor för tabellen med företag. I tabellen med företag finns även en kolumn innehållande vilken stad företaget är verksamt i.
Sökformuläret har två inputfält, ett för företagsnamn/generell sökterm och ett för stad.
Nu vill jag även kunna söka på län i samma sökfält som man nu söker på stad. Detta har visat sig klurigare än jag tidigare trott.
Just nu har jag följande kod:
$sql = "
SELECT *,
MATCH(foretag) AGAINST(:keywords) AS kr
FROM tblforetag
WHERE MATCH(foretag) AGAINST(:keywords)
";
$sql .= $locisset ? "AND stad LIKE :location" : "";
$sql .= " LIMIT $offset, $rpp";
$query = $conn->Prepare($sql);
$query->BindValue(':keywords', $keywords);
if($locisset) $query->BindValue(':location', "%$location%");
$query->Execute();
Jag har prövat med multipla joins men inte riktigt fått till det. Har ni någon idé om hur jag kan gå vidare med detta?
Tabellerna är länkade på följande vis:
[ tblforetag ] <-- Det är härifrån jag vill ha resultat
- foretag [varchar]
- stad [varchar] (1)
Många verkar intresserade, men inget förslag på lösning har trillat in. Här kommer en uppdatering!
Jag har - utöver den enorma mängd företag som faktiskt existerar - lagt in ett företag som jag döpt till "Testföretaget AB" med mitt eget nummer, adress och så.
Kan du inte göra en echo på $sql och posta här, så det blir lite lättare att läsa?
mvh
Absolut, @nders!
Här kommer den med något angivet i $location:
SELECT tblforetag.*, tbllan.Lan, MATCH(tblforetag.foretag) AGAINST(:keywords) AS kr
FROM tblforetag , tblstad, tbllan
WHERE MATCH(tblforetag.foretag) AGAINST(:keywords) AND
(tblforetag.stad LIKE :location OR (tblforetag.stad = tblstad.Stad
AND tblstad.Lan_id = tbllan.Id AND tbllan.Lan LIKE :location)) LIMIT 0, 25
...och här kommer den utan något angivet i $location:
SELECT tblforetag.*, MATCH(tblforetag.foretag) AGAINST(:keywords) AS kr
FROM tblforetag WHERE MATCH(tblforetag.foretag) AGAINST(:keywords) LIMIT 0, 25
Jag skulle nog gjort två olika SQL-frågor (för tydlighetens och enkelhetens skull) - en där $locisset är true och en där inte.
Nåt sånt härnt hade jag gjort med den svårare av de två:
SELECT tblforetag.*, tbllan.Lan, MATCH(tblforetag.foretag) AGAINST(:keywords) AS kr
FROM tblforetag
INNER JOIN tblstad
ON tblforetag.stad = tblstad.Stad
LEFT JOIN tbllan
ON tblstad.Lan_id = tbllan.Id AND tbllan.Lan LIKE :location
WHERE MATCH(tblforetag.foretag) AGAINST(:keywords)
AND (tblforetag.stad LIKE :location
OR tbllan.Lan IS NOT NULL)
LIMIT 0, 25
Det här förutsätter att alla städer i företagstabellen finns i stadtabellen i exakt en post. Det är lite svårt att se helheten när jag inte helt och hållet känner till hur din datastruktur ser ut, men ovan kod kanske är något att testa i alla fall.
Jag skulle nog gjort två olika SQL-frågor (för tydlighetens och enkelhetens skull) - en där $locisset är true och en där inte.
Nåt sånt härnt hade jag gjort med den svårare av de två:
SELECT tblforetag.*, tbllan.Lan, MATCH(tblforetag.foretag) AGAINST(:keywords) AS kr
FROM tblforetag
INNER JOIN tblstad
ON tblforetag.stad = tblstad.Stad
LEFT JOIN tbllan
ON tblstad.Lan_id = tbllan.Id AND tbllan.Lan LIKE :location
WHERE MATCH(tblforetag.foretag) AGAINST(:keywords)
AND (tblforetag.stad LIKE :location
OR tbllan.Lan IS NOT NULL)
LIMIT 0, 25
Det här förutsätter att alla städer i företagstabellen finns i stadtabellen i exakt en post. Det är lite svårt att se helheten när jag inte helt och hållet känner till hur din datastruktur ser ut, men ovan kod kanske är något att testa i alla fall.
mvh
Det har var vad jag hade i åtanke först och det jag experimenterade en hel del med med varierande men aldrig tillfredsställande resultat. Ska testa din metod här. Och, ja, alla städer finns i stadtabellen och är unika där.
Angående att göra två olika SQL-frågor så var det något jag verkligen försökte undvika, men jag antar att det av praktiska skäl kanske inte är det mest optimala.
Din SQL fungerade superbt! Nu återstår bara ett problem jag har i PHP. Av någon anledning ger det inte några resultat där, men det är ju bara frågan om att hitta något litet skitfel som har letat sig in någonstans i koden. Tusen tack, @anders!
Kul att kunna hjälpa till så här på en solig fredag. :)
mvh
Visst är det trevligt att solen tittar fram!? Det känns att det blir ljusare nu. Den första förnimmelsen om att våren faktiskt är på väg!
Det tog inte lång tid innan jag hittade mina små tabbar jag gjort när jag skrev om din SQL till att passa min kod. Nu är det fixat och din fråga fungerar utmärkt! Tack än en gång! Du har sparat mig mycket tid (och hår)!
Uppföljning och slutsats; det här var inte snällt mot servern. Antalet poster i tblforetag är groteskt - sexsiffrigt, och jag är långt ifrån klar. Det KOMMER vara över miljonen poster och då kommer det här alltså inte vara hållbart. :(
Jag gör naturligtvis en wildcard-sökning, men med PDO måste man skicka med wildcardsen i strängen, såhär:
$query->BindValue(':location', "%$location%");
MATCH/AGAINST är väldigt effektivt. Det är ett fulltext index den gör uppslag mot och om jag inte tar med någon geografisk sökterm (den som kör LIKE-satsen) så går en sökning blixtsnabbt.
Att leta söka efter resultat med LIKE och wildcard, dvs ":location". Om jag ej har med det i frågan går det kanonfort.
Jag är inte bra på PDO i MySQL, så ta detta med en nypa salt.
I MSSQL kan ett OR i frågan ibland ställa till att queryn struntar i index och gör en tabellscan istället. Testa om det här går fortare:
SELECT F.*, L.Lan, MATCH(F.foretag) AGAINST(:keywords) AS kr
FROM
tblforetag F
LEFT JOIN tblstad S -- Hitta staden om det var en stad
ON F.stad = S.Stad AND
S.Stad LIKE :location
LEFT JOIN tbllan L -- Hitta lanet om det var ett lan
ON S.Lan_id = L.Id AND
L.Lan LIKE :location
WHERE
MATCH(F.foretag) AGAINST(:keywords)
AND COALESCE(S.Id, L.Id) IS NOT NULL -- Antingen fanns staden eller lanet (eller båda)
LIMIT 0, 25
Jag är inte bra på PDO i MySQL, så ta detta med en nypa salt.
I MSSQL kan ett OR i frågan ibland ställa till att queryn struntar i index och gör en tabellscan istället. Testa om det här går fortare:
SELECT F.*, L.Lan, MATCH(F.foretag) AGAINST(:keywords) AS kr
FROM
tblforetag F
LEFT JOIN tblstad S -- Hitta staden om det var en stad
ON F.stad = S.Stad AND
S.Stad LIKE :location
LEFT JOIN tbllan L -- Hitta lanet om det var ett lan
ON S.Lan_id = L.Id AND
L.Lan LIKE :location
WHERE
MATCH(F.foretag) AGAINST(:keywords)
AND COALESCE(S.Id, L.Id) IS NOT NULL -- Antingen fanns staden eller lanet (eller båda)
LIMIT 0, 25
Tusen tack för tipset! Ny hostinglösning är på tapeten så jag ska se hur snabb min fråga är där när det är igång, men jag ska kika på detta också! Igen, tusen tack!
Efter flytt till nya hostinglösningen går sökningarna på ganska exakt en tiondel av tiden det tog förut. Jag tror inte att tunga frågor blir något problem! :-)
300 ms totalt · 4 externa anrop · v20260731065814-full.a51de22e