SQL nehéz lekérés
A felhasználók között szeretnék úgy keresni, hogy azokat találja, akikkel közösek a céljaink.
Táblák:
- users (id,name)
- goals (célok) (-id,name)
- user_goals (id, user_id,goal_id)
Felveszünk felhasználókat, és célokat, a felhasználók pedig jelölgetnek maguknak célokat. Mindenkinek különböző fajta és mennyiségű céljai vannak.
Azokat keresem, akiknek hasonló (legalább 1 megfelelés) céljai vannak, mint nekem.
■ Táblák:
- users (id,name)
- goals (célok) (-id,name)
- user_goals (id, user_id,goal_id)
Felveszünk felhasználókat, és célokat, a felhasználók pedig jelölgetnek maguknak célokat. Mindenkinek különböző fajta és mennyiségű céljai vannak.
Azokat keresem, akiknek hasonló (legalább 1 megfelelés) céljai vannak, mint nekem.
MySQL?
Ha a felhasználó felől nézzük, akkor összekapcsolandó két tábla. Az eredmény azonban szorzat lenne, ezért az egyedi eseteket szűrni kell a "distinct" függvénnyel:
WHERE users.id = goals.user_id
Ha van találat, akkor legalább egy célja van a felhasználónak.
Ez a másik minta arra lenne példa, ha egyrészt elég csak egy-egy "id", másrészt célok száma szerint is szűrni akarsz ( mondjuk az érdekes akinek több mint 1 célja van ):
SELECT user_id, count( goal_id ) AS darab FROM user_goals
GROUP BY user_id ) AS tmp
WHERE darab > 1
Valamiért a MySQL-nél nem lehet közvetlenül lekérdezni az ilyesmit. Összetett "select" kell hozzá.
Jo
Tehát ha vannak 1 , 2, 3, 4, 5, 6, 7, 8 as célok.
Nekem van 3, 4, 5, ös cél, akkor olyat keresek,
Akinek van legalább 3as, 4es, vagy 5 ös célja,
Vagy max mind a 8 db célja, akkor is megtalálja, mert benne az én 3 fajta célom.
Amolyan legkisebb közös többszörös keresése.
Ja, és usersből elég az id. A while ciklusban majd ujrakérem a többi adatot.
Nagyon remélem, hogy abban a
jaj
pl a felhasználónak csak a nevére csináltam 1 függvényt, és
ahol user_id van, csak beszúrom ezt a függvényt és átláthatóbb.
Sokszor ez ciklusba esik.
Akkor ezek szerint ezt nem kellene:)
Erre gondoltál?
Mindjárt kipróbálom, ez mit
ez mit jelent: user_id <> sajat_id ?
Ezt jelenti: user_id != sajat_id ?
igen
Kicsit továbbgondolva:SELECT
Nem tudom ez mennyire optimális, kicsit megkoptak az SQL szkilljeim az utóbbi időben :). Megfelelő indexekkel jónak kéne lennie.
nem jo.
A user célja: 16
Nincs egyezés, mégis kidobja az azonosítóját.
én postgresql-t használok de ez szabvány sql utasításokkal megol
az itt megkapott user_id-kat lekérdezed a users táblából, kizárva természetesen a saját
felhasználódat (nem tudom ez szükséges-e), ezt persze a users tábla where záradékában is meg lehet tenni.
where users.id in (select user_id from user_goals where goal_id in
(select goal_id from user_goals where user_id = a_te_felhasznalo_id_d)
and user_goals.user_id <> a_te_felhasznalo_id_d)
Elkelne egy kis pontosítás ...
A két fogalmazás között jelentős különbség van.
Melyiket akarod lekérdezni, egy tetszőleges elemszámú metszetet, vagy teljes részhalmazt?
Utóbbi az egyszerűbb.
Kezdetnek itt ez a lekérdezés, többet ad vissza mint kell, viszont ellenőrzésre pont jó:
( SELECT * FROM user_goals WHERE user_id = 'ÉN') AS tmp
WHERE user_goals.user_id <> 'ÉN' and user_goals.goal_id = tmp.goal_id
ORDER BY user_goals.user_id, user_goals.goal_id
Bővítve, már csak a keresettek azonosítóját kapod meg:
( SELECT * FROM user_goals WHERE user_id = 'ÉN') AS tmp
WHERE user_goals.user_id <> 'ÉN' and user_goals.goal_id = tmp.goal_id
ORDER BY user_goals.user_id
Természetesen nincs olyan táblám mint neked, hasonlón azért kipróbáltam a mintákat.
én céljaim legyenek részhalmazai bárki máséinak.
Nagyon jól megfogalmaztad, ezt akartam írni:)
Köszi szépen mindegyik verzió végre működik. Mindet kipróbáltam.
A lekéréssel csak az a gond, hogy ha én 1 gyakori célt bejelölök, ami megvan mindenki másnak, a lekérés igazábol nem ér semmit.
Ha még lehetne bonyolítani a lekérést, azt hogy tudom lekérdezni, hogy nem legalább 1 célom legyen részhalmaza bárki másnak, hanem a legtöbb.
Nyilván arra az emberre vagyok kiváncsi, akivel a legtöbb közös célunk van.
Az már tényleg lehetetlen, hogy ráadásul eszerint rakja sorrendbe:)
Jogos
A teljes részhalmazra még nincs ötletem.
kb
SELECT DISTINCT ug2.id
FROM user_goals AS ug1 INNER JOIN user_goals AS ug2
ON ug1.user_id = sajat_user_id AND
ug1.goal_id = ug2.goal_id AND
ug1.user_id <> ug2.user_id
Ez már valami hasonló ...
Ahogy "szabo.b.gabor" utalt rá, a "COUNT()" függvény segíthet neked.
FROM user_goals,
( SELECT * FROM user_goals WHERE user_id = 'ÉN') AS tmp
WHERE user_goals.user_id <> 'ÉN' and user_goals.goal_id = tmp.goal_id
GROUP BY user_goals.user_id
miért kell beágyazott
Miért beágyazott?
Személyes preferencia, vagy korlát, ahogy tetszik:
- Ugyanazon tábla elemeivel végzünk műveletet, olyan módon mintha két tábláról lenne szó. A beágyazott "SELECT" a második táblát szimulálja.
- A "JOIN" alapú szintaxis számomra idegen, ilyet csak legvégső esetben írok le. Mondjuk akkor, ha kapcsolatból kimaradó elemeket keresek a "LEFT JOIN"-nal.
nem hiszem, hogy teljesítmény
Hit
Bár, ha foglalkozni akarsz ezzel, vagy van valami tapasztalatod, akkor írd meg.
Hit alapon nem érdemes, az nem bizonyíték alapú konstrukció.
Beágyazott lekérdezés
nem teljesen jó.
csak az abban a táblában lévő, userhez tartozó rekordok számát adja vissza, de az nem azt jelenti, hogy azok egyeznek is az én céljaimmal.
tehát a count visszaadja, hogy 1 ik usernak 3 célja van, de abból csak 1 a közös, még akinek csak 2 célja van, lehet hogy abból mind2 egyezik velem, tehát elé kellene, hogy kerüljön.
Két lekérdezés
Azt javaslom, két lekérdezéssel oldd meg a problémádat.
Elsőnek tudd meg hány célja van a kiválasztott felhasználónak ("ÉN"). Ez hamar lefut, nem sokat ront a futási időn.
FROM user_goals WHERE user_id = 'ÉN'
GROUP BY user_id
Másodszorra, sorba rendezett találati listát kérj le. Ez nem optimális valószínűleg, de működik. Ha kiadtad az utasítást, akkor csak azokat a sorokat vedd figyelembe, ahol a "talalt_celok" = "celok_szama"
FROM user_goals,
( SELECT * FROM user_goals WHERE user_id = 'ÉN') AS tmp
WHERE user_goals.user_id <> 'ÉN' and user_goals.goal_id = tmp.goal_id
GROUP BY user_goals.user_id
ORDER BY talalat DESC
Metszetek
Ha jól értem a kezdőkérdést, illetve az azóta folyó diskurzust, téged az érdekel, hogy kik azok akik a lehető legnagyobb átfedésben vannak a useredhez tartozó "goal_id" listával.
Ezt alapvetően a user_goals tábla önmagával vett descartes-szorzatából ki lehet számolni a következőképpen (nagyjából a 13-as hozzászólásban található lekérdezésnek felel meg):
Egy kis magyarázat a dolog elemeiről:
Az összesítés lelke a user_goals tábla saját magához csatolása a goal_id-k mentén, ezzel egy olyan relációt hozunk éltre aminek soraiban szerepel 1 goal és 2 user ID.
Na most ez a lekérdezés egy kellően nagy adatbázison alapvetően nem lesz nagyon gyors user_id illetve goal_id oszlopokra egyaránt érdemes lehet indexet tenni. Egyik megfontolandó továbblépési pont, hogy az összes variáció előre kiszámolására általánosítani a lekérdezést és adatok változásakor csak adott user-hez tartozó dolgokat újraszámolni. Valami ilyesmit képzelnék el:
Az egyetlen új elem ami talán magyarázatra szorulhat az a második feltétel a WHERE -ben:
B user -> A user párokat. Viszont ha rendezést adunk meg a két user ID között akkor csak ezek közül az egyik lehet igaz. Cserébe persze lekérdezéskor egy UNION segítségére lesz szükségünk, mivel keresett user ID-ja lehet mindkét oldalon.
Egy másik talán fontosabb probléma a módszerrel, hogy nem tud különbséget tenni goal és goal között. A hozzászólások között felmerült, hogy vannak goal-ok amiket "mindenki bejelöl" ezeket célszerű volna kevésbé értékesnek jelölni mint azokat amikre csak páran iratkoznak fel. Ehez maguknak a goal-oknak kellene valamilyen súlyozást adni, majd összesítéskor hozzácsatolva a (goal_id, t1.user_id, t2.user_id) sorokhoz a COUNT() által visszaadott értéket módosítani. Például ritkább goal-ok nagyobb pontszámosak mint gyakoriak, pontszámok SUM() + COUNT() -ja adja az egyezőségi pontszámot. Ez esetben persze a korábban felvetett előre kiszámolást nehéz hatékonyan végezni mivel ha valaki felvesz egy új goalt magához az potenciálisan mindenki mást is érinthet, értelmetlenné válik előre kiszámolni bármit is.
A korábbi példát kiegészítve egy score mezővel a goal táblán (SQLFiddle):
Látható, hogy 2-es ID-jú user két goal-ban is osztozik velünk, de azok együtt se olyan izgalmasak mint 4 ID-jú user akivel egy 5 pontos goal-on van egyezés.
Ez az egész persze csak a "Recommender systems" nevű jéghegy csúcsa, érdemes a témakör további felderítésének kiindulópontjaként ajánlom a wikipedia egy, talán ide vonatkozó szócikkét: Collaborative filtering.
Ez igen
szép
Az meg, hogy scoret adjak a célokhoz, hát sosem gondoltam volna ilyet.. Jó tudni erről, bár gyakorlati szerepe itt nem lesz, mivel ha még csak 1-2 célom van, úgysem keresek egyező embereket, ha pedig 10-15 célom lesz, akkor önmagában érdekes 8-10 egyezőség.
Eddig ezek működtek, nagyon jó, megvan egy sor user_id-m.
De ebből hogy lesz user_name listám?
Ha ezt az egészet beteszem egy
WHERE users.id IN ()-be, akkor a matchcount-ot elvesztem.
Vagy olyat tudnék, hogy array_push-al tömbbe rakom, foreach-el körbejárom és a ciklusban kérdem le a neveket.
Szóval hogy lehetne a user_neveket a matchcount alapj'n sorba rakni?:)
JOINs all the way down
kösz
Kösz mindenkinek aki segített, az összes hozzászólásból tanultam valamit,
külön köszi complex, hogy magyarázatot is írtál.
Ez a Collaborative filtering is nagyon tetszik.
Ebben a weboldalban még nem csalódtam.:)