mboost-dp1

"Simpel" mange til mange relation


Gå til bund
Gravatar #1 - Cyrack
30. mar. 2009 16:47
Hej mednørder, nu har det her problem irriteret mig det meste af en dag:
Jeg har et simpelt tagging system, hvor man skal kunne søge på entities der har alle søgeord tilknyttet (layout).
Umiddelbart en simpel søgning, indtil det går op for en at en join giver alle rows fra Doc tabellen der blot har ét eller flere søgeord.
Næste forsøg bliver en inner join på en inner join på en (.. se eksemple her ..) hvilket en MySQL server har det fint med, indtil man laver et par hundredtusinde relationer og søger på en femten - tyve ord :-/

Er der nogen der har en idé til hvordan man løser dette problem? Har været det meste af google igennem, og umiddelbart er jeg ikke den eneste der har dette problem :-/
Gravatar #2 - Mort
30. mar. 2009 19:28
Jeg har ikke så stor erfaring med MySQL, så jeg ved ikke om det samme kan lade sig gøre der, men hvis jeg havde samme problem på en SQL Server, så ville jeg forsøge mig med at lave et indexed view, altså et view på Doc-Rel-Tag relationen og et indeks på Doc.Content og Tag.Tag attributterne.

Indexet ville fylde en hel del på disken, men ville spare en masse tid på dit lookup da du ville kunne undvære at joine alle data for hver query og kun gøre det når indexet bliver oprettet eller opdateret.
Gravatar #3 - arne_v
30. mar. 2009 19:44
#1

Har du index på:
Doc.ID
Rel.DocID
Rel.TagId
Tag.TagId
?
Gravatar #4 - arne_v
30. mar. 2009 19:46
#2

MySQL har ikke materialized views (som er hvad et SQLServer indekseret view reelt er).
Gravatar #5 - mindzero
2. apr. 2009 13:02
SELECT d.*
FROM Rel r, Doc d, Tag t
WHERE r.TagId = t.TagId
AND (t.Tag IN ('ord1', 'ord2', 'ord3'))
AND d.ID = r.DocID
GROUP BY d.ID
HAVING COUNT( d.ID )=3


Ved ikke om det virker sådan? Fandt bare hurtigt et eksempel og omskrev det til dine navne (håber at jeg fik ændret dem rigtigt).

Kig her, på "Toxi":
http://www.pui.ch/phred/archives/2005/04/tags-data...
Gravatar #6 - Cyrack
2. apr. 2009 17:05
Dj Mortar:

En Propel udvikler anbefalede denne:


$tblAlias = self::getRndTblAlias();
$sql .= 'SELECT DISTINCT '.$tblAlias.'.DocumentId FROM'."\n";
foreach ($tokens as $token) {
$token = mysql_real_escape_string($token);
if ($firstToken) {
$sql .= '(SELECT DocumentId FROM Support_Document_Tag JOIN (SELECT Id FROM Support_Tag WHERE Tag LIKE \''.$token.'\') Tags ON Tags.Id = TagId) '.$tblAlias."\n";
$firtToken = false;
} else {
$tblAliasPrev = $tblAlias;
$tblAlias = self::getRndTblAlias();
$sql .= 'JOIN (SELECT DocumentId FROM Support_Document_Tag JOIN (SELECT Id FROM Support_Tag WHERE Tag LIKE \''.$token.'\') Tags ON Tags.Id = TagId) '.$tblAlias.' ON '.$tblAlias.'.DocumentId = '.$tblAliasPrev . '.DocumentId' . "\n";
}
}


Dette giver desværre en del flere joins, men tilgengæld kan man optimere koden, så de tokens der er færrest dokumenter der har kommer først, og derved reducere antallet af opslag betragteligt.

I din løsning er det desuden en fordel at ændre

HAVING COUNT( d.ID )=3

til

HAVING COUNT( d.ID ) >= 3

herved kan man også bruge wildcard søgninger (fx. vil en søgning på 'data%' og 'database' fejle uden '>').

@arne_v:
Yep, der er index på alle primær nøgler og foreignkeys

Og jeg siger mange tak til jer alle. Næste gang vælger jeg en DB der kan lave INTERSECT :-)
Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login