webForumDet fria alternativet

Sökning "Resultat från tabell (resultat) <- tabell <- tabell (match)"

Databaser & SQL

17 svar · 1 785 visningar · startad av prplxr

Medlem sedan juni 2012581 inlägg
Frågan#1

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)

[ tblstad ]
- stad [varchar] (1)
- lan_id [int] (2)

[ tbllan ]
- id [int] (2)
- lan [varchar] <-- Här vill jag kunna matcha

Medlem sedan juni 2012581 inlägg
#2

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å.

Såhär ser koden ut just nu:

$sql = "SELECT tblforetag.*, ";
$sql .= $locisset ? "tbllan.Lan, " : "";
$sql .=	"MATCH(tblforetag.foretag) AGAINST(:keywords) AS kr	FROM tblforetag ";
$sql .= $locisset ? ", tblstad, tbllan " : "";
$sql .=	"WHERE MATCH(tblforetag.foretag) AGAINST(:keywords) ";
$sql .= $locisset ? "AND (tblforetag.stad LIKE :location OR (tblforetag.stad = tblstad.Stad AND tblstad.Lan_id = tbllan.Id AND tbllan.Lan LIKE :location))" : "";
$sql .= " LIMIT $offset, $rpp";

$query = $conn->Prepare($sql);
$query->BindValue(':keywords', $keywords);
if($locisset) $query->BindValue(':location', "%$location%");
$query->Execute();

Söker jag på "testföretaget" utan att $location har något värde får jag, som förväntat, ett resultat. Allt i sin ordning.

Söker jag på "testföretaget" med $location = 'östergötland' får jag... drumroll... Ett resultat.

Söker jag på "testföretaget" med $location = 'norrköping' får jag... *suck*... 14 232 resultat...

Någon som har någon idé om vad det är jag gör som är så ruskigt fel?

Jag blir galen! x(

Medlem sedan juni 200032 967 inlägg
#3

Kan du inte göra en echo på $sql och posta här, så det blir lite lättare att läsa?

mvh

Medlem sedan juni 2012581 inlägg
#4

@nders skrev:

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
Medlem sedan juni 200032 967 inlägg
#5

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

Medlem sedan juni 2012581 inlägg
#6

@nders skrev:

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.

Medlem sedan juni 200032 967 inlägg
#7

Så länge koden är tydlig så ska du inte vara rädd för att dela upp det - om du tjänar något på det.

Det finns skäl att hålla ihop koden - men det är ju inget självändamål.

mvh

Medlem sedan juni 2012581 inlägg
#8

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!

Medlem sedan juni 200032 967 inlägg
#9

Kul att kunna hjälpa till så här på en solig fredag. :)

mvh

Medlem sedan juni 2012581 inlägg
#10

@nders skrev:

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)!

:i

Medlem sedan juni 2012581 inlägg
#11

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. :(

Medlem sedan juni 200032 967 inlägg
#12

Jag vet inte hur MATCH/AGAINST funkar (MySQL-specifikt månne?) - vad är det du söker på?

Och det här med LIKE - om du inte gör en wildcard-sökning, så använd =.

Jag ska fundera...

Medlem sedan juni 2012581 inlägg
#13

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.

Medlem sedan juni 200032 967 inlägg
#14

Jag gissade att det var så det låg till - och fulltextindexering är normalt sett effektivt. Vad är det i din fråga som tar tid? (EXPLAIN?)

Medlem sedan juni 2012581 inlägg
#15

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.

Medlem sedan mars 20034 471 inlägg
#16

prplxr skrev:

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
Medlem sedan juni 2012581 inlägg
#17

aasah skrev:

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!

Medlem sedan juni 2012581 inlägg
#18

Uppföljning!

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
132 ms — deklarationer (db)
0 ms — hämta statistik (cache)
164 ms — hämta tråd, inlägg och bilagor (db)
127 ms — ändringar (db)