[MySQL] Spara många kopplingar mellan olika tabeller på bästa sätt?

Permalänk
Medlem

[MySQL] Spara många kopplingar mellan olika tabeller på bästa sätt?

Hej!

Jag har ett litet problem som jag inte vet hur jag ska lösa på bästa sättet. För att förenkla förklarningen gör jag en liknelse
Jag har följande två tabeller

--------------
Person
--------------
ID | Namn
--------------
1 | Anna
2 | Karl
3 | Robert

--------------
Frukt
--------------
ID | Namn
--------------
1 | Äpple
2 | Apelsin
3 | Päron
4 | Ananas

och jag vill på smidigaste möjligt sätt spara informationen att Anna gillar frukterna Äpple, Päron och Ananas.
Jag ska sen på lätt sätt kunna sortera vilka frukter en viss person gillar och även vilka personer som gillar en viss frukt.
Det jag själv kommer på är en tredje tabell "Kopplingar" som sparar "PersonID" och "FruktID" på detta sätt

--------------
Kopplingar
--------------
PersonID| FruktID
--------------
1 | 1
1 | 3
1 | 4

men i slutändan kommer över 200 personer att gilla runt 300 frukter vardera så med denna lösning rör det sig om över 60 000 rader vilket kanske inte är den mest optimala lösningen?
Notera att detta bara är en liknelse och i själva fallet inte handlar om vilka personer som gillar vilka frukter

Tacksam för svar!
/André

Permalänk
Medlem

Du är helt inne på rätt lösning. Det du försöker göra är en många till många-relation. Dvs en person kan gilla flera frukter, och en frukt kan bli gillad av flera personer. Efter en sökning på google hittade jag exempelvis denna länk där du kan läsa mer om relationer, primärnyckar, sekundärnycklar etc: SQL Table Relations, Primary and Foreign Keys, and Normalization. 60 000 rader i en databas är inget att oroa sig för.

Permalänk
Medlem

Bra inlägg av BigBen, vill bara tillägga att indexering av rätt kolumner gör underverk gällande prestanda.

Permalänk
Medlem

Hur blir det om man i en kolumn för personer sparar ex GillarFruktID [1, 2, 3], det kanske är långsammare att exekvera sedan?

Visa signatur

i7 920 | 12GB DDR3 | GTX 480 | GA-X58A-UD7 | 160GB SSD X25-M G2 | 1TB F3 HD103SJ | W7 64-bit | Mac Mini
Webb: bluekitestudios.com

Permalänk
Medlem
Skrivet av save:

Hur blir det om man i en kolumn för personer sparar ex GillarFruktID [1, 2, 3], det kanske är långsammare att exekvera sedan?

Problemet som uppstår är att man inte kan plocka ut folk som specifikt gillar päron utan att först plocka ut alla registrerade personer och sedan programmatiskt söka igenom deras GillarFrukt-array efter ett visst värde.

Genom att separera (normalisera) varje N:N (många till många)-förhållande till två stycken 1:N (ett till många) går man runt detta på ett smidigt sätt:

SELECT Person.Namn FROM Person, Frukt, Kopplingar WHERE Kopplingar.PersonID = Person.ID AND Kopplingar.FruktID = Frukt.ID AND Frukt.Namn = 'Päron'

(går att göra snyggare med JOINs.)

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av Teknocide:

Problemet som uppstår är att man inte kan plocka ut folk som specifikt gillar päron utan att först plocka ut alla registrerade personer och sedan programmatiskt söka igenom deras GillarFrukt-array efter ett visst värde.

Genom att separera (normalisera) varje N:N (många till många)-förhållande till två stycken 1:N (ett till många) går man runt detta på ett smidigt sätt:

SELECT Person.Namn FROM Person, Frukt, Kopplingar WHERE Kopplingar.PersonID = Person.ID AND Kopplingar.FruktID = Frukt.ID AND Frukt.Namn = 'Päron'

(går att göra snyggare med JOINs.)

Tack! Så att i detta fallet försöka spara plats i databasen är inte att föredra. Har aldrig jobbat med normalisering, databasnovis som man är.

Visa signatur

i7 920 | 12GB DDR3 | GTX 480 | GA-X58A-UD7 | 160GB SSD X25-M G2 | 1TB F3 HD103SJ | W7 64-bit | Mac Mini
Webb: bluekitestudios.com

Permalänk
Medlem

60000 rader är småpotatis. Men när det börjar närma sig hundratals miljoner rader så kommer man vilja flytta lasten från läsning (join) till skrivning och faktiskt lägga ihop varja användares frukter i en egen struktur vid varje förändring. Det är bla här "NoSQL" kommer in.

Permalänk
Medlem
Skrivet av save:

Tack! Så att i detta fallet försöka spara plats i databasen är inte att föredra. Har aldrig jobbat med normalisering, databasnovis som man är.

Precis.
Faktum är att man ofta gräver en grop åt sig själv när man försöker göra det enkelt och slå ihop tabeller. Databasens främsta egenskap är, om den är korrekt utformad, att sökningar är okomplicerade och snabba att genomföra.

Man skulle exempelvis klara sig hjälpligt med en enda tabell Fruktälskare med kolumnerna ID, Namn och GillarFrukt. Problemet som uppstår i detta läge kallas redundans. Om Kalle gillar Äpplen och Päron måste hans namn återfinnas på två rader i databasen. Vill man exempelvis plocka ut alla unika personer ur en sådan tabell måste databasen arbeta igenom en massa redundant information som berättar att Kalle gillar Äpplen, Päron, Bananer, Meloner, Avokados och Rambutaner. Jämför detta med att ha en Person-tabell som bara innehåller unika ID:n så står det klart vilket som är att föredra, även om konceptet med många olika tabeller, privata och främmande nycklar och constraints kan kännas väldigt avskräckande till en början.

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem

Tack för alla svar! Ska läsa på lite mer om Joins
Men på vilket sätt underlättar indexering och hur funkar det?

Permalänk
Medlem
Skrivet av drewi:

Tack för alla svar! Ska läsa på lite mer om Joins
Men på vilket sätt underlättar indexering och hur funkar det?

Här är den "snyggare" JOIN-satsen jag pratade om..

SELECT Person.Namn AS Person, Frukt.Namn AS Fruktnamn FROM Person INNER JOIN Kopplingar USING (PersonID) INNER JOIN Frukt USING (FruktID) WHERE Fruktnamn = 'Päron'

Det finns en quirk här som på sätt och vis uppmuntrar ett strikt namngivningsschema: när man använder USING() kombineras rader med det inom-parenteser-angivna kolumnnamnet. Detta innebär alltså att du i Kopplingar behöver ha kolumnerna [PersonID, FruktID] och att dessa även måste återfinnas som PersonID och FruktID i de relaterade tabellerna Person och Frukt. Det kan tyckas lite omständligt, jag har faktiskt inte beslutat mig för vad jag gillar mest; Person.ID = Kopplingar.PersonID ser tydligt ut för mig.

edit
För att svara på din fråga:
Ett index är en mekanism som optimerar ett attribut för sökning. Som en bieffekt av detta brukar INSERTs eller UPDATEs på det indexerade fältet bli långsammare. Primärnyckeln (eller -nycklen som det kallas i Skåne) är en mer eller mindre mandatorisk indexeringsfunktion som vanligtvis läggs på ett fält med automatiskt tilldelat heltalsvärde. Detta fält uppdateras sällan och INSERTs sker förutsägbart, varvid den potentiella prestandaförlusten hålls till ett minimum.

Mer info här: Index (database) - Wikipedia, the free encyclopedia

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av Teknocide:

Här är den "snyggare" JOIN-satsen jag pratade om..

SELECT Person.Namn AS Person, Frukt.Namn AS Fruktnamn FROM Person INNER JOIN Kopplingar USING (PersonID) INNER JOIN Frukt USING (FruktID) WHERE Fruktnamn = 'Päron'

Det finns en quirk här som på sätt och vis uppmuntrar ett strikt namngivningsschema: när man använder USING() kombineras rader med det inom-parenteser-angivna kolumnnamnet. Detta innebär alltså att du i Kopplingar behöver ha kolumnerna [PersonID, FruktID] och att dessa även måste återfinnas som PersonID och FruktID i de relaterade tabellerna Person och Frukt. Det kan tyckas lite omständligt, jag har faktiskt inte beslutat mig för vad jag gillar mest; Person.ID = Kopplingar.PersonID ser tydligt ut för mig.

Tack, nu förstår jag joins bättre Har en fråga angående optimeringen nu bara, i detta exempel ser tabellerna ut såhär:

-------------- Person -------------- PersonID | Namn -------------- 1 | Anna 2 | Karl 3 | Robert -------------- Frukt -------------- FruktID | Namn -------------- 1 | Äpple -------------- Kopplingar -------------- PersonID | FruktID -------------- 1 | 1 2 | 1 3 | 1

Jag vill nu ta fram namn på alla personer som gillar fruktID 1 och få reda på vad fruktID 1 är för frukt
Det går att göra med denna kod

SELECT person.namn AS person, frukt.namn AS fruktnamn FROM frukt INNER JOIN kopplingar USING (fruktID) INNER JOIN person USING (personID) WHERE fruktID = 1

vilket returnerar

person | fruktnamn -------------- Anna | Äpple Karl | Äpple Robert | Äpple

Nackdelen är ju då att fruktnamn skrivs ut på varenda rad, är det bättre att göra 2 olika SQL-frågor där jag i den ena hämtar enbart fruktnamnet och i den andra alla som gillar frukten?

Permalänk
Medlem

Ta bort Frukt.Namn ur "SELECT Person.Namn, Frukt.Namn FROM ... "'

edit: scratch that. I en relationsdatabas ser precisa selects ut på det viset. Om du bara hade haft fruktnamn på en rad så hade de andra inte varit knutna till något. Hur du presenterar informationen du hämtar ur databasen är oberoende av tabellstrukturen.

Kort och gott, att göra två SELECTs är bara onödigt. Vill du ändå greja med det så borde det gå att slå ihop namnen och gruppera på frukten, på något sätt. Jag är inte så bra på inbyggda SQL-funktioner..

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem

Jo men hur får man fruktnamn att bara vara på en rad? :/ Tror jag kör på två olika frågor istället

Permalänk
Medlem

Vad menar du med att du vill bara ha fruktnamnet på en rad? group by kan du ju alltid använda ifall du bara vill visa en del frukter som överensstämmer med ditt attribut.

Men vad vill du åstadkomma egentligen?

Visa signatur

//clooak Mer CO-OP åt folket!

Permalänk
Legendarisk

Om du vill ha alla som gillar en viss frukt (och de inte är för många) i samma fält kan du göra så här:

mysql> SELECT f.Namn AS fruktnamn, GROUP_CONCAT(p.Namn) AS person FROM Frukt AS f JOIN Kopplingar AS k ON k.FruktID=f.FruktID JOIN Person AS p ON p.PersonID=k.P ersonID GROUP BY f.FruktID; +-----------+------------------+ | fruktnamn | person | +-----------+------------------+ | Apple | Anna,Karl,Robert | +-----------+------------------+ 1 row in set (0.00 sec)

MySQL :: MySQL 5.0 Reference Manual :: 11.11.1 GROUP BY (Aggregate) Functions

Om det är rätt eller inte beror dock på vad du har för mål med det hela, oftast kommer du antagligen vilja ha mer information om personerna och då är det inte så praktiskt att slå ihop raderna iaf.

Visa signatur

Abstractions all the way down.

Permalänk
Medlem
Skrivet av Tunnelsork:

Om du vill ha alla som gillar en viss frukt (och de inte är för många) i samma fält kan du göra så här:

mysql> SELECT f.Namn AS fruktnamn, GROUP_CONCAT(p.Namn) AS person FROM Frukt AS f JOIN Kopplingar AS k ON k.FruktID=f.FruktID JOIN Person AS p ON p.PersonID=k.P ersonID GROUP BY f.FruktID; +-----------+------------------+ | fruktnamn | person | +-----------+------------------+ | Apple | Anna,Karl,Robert | +-----------+------------------+ 1 row in set (0.00 sec)

MySQL :: MySQL 5.0 Reference Manual :: 11.11.1 GROUP BY (Aggregate) Functions

Om det är rätt eller inte beror dock på vad du har för mål med det hela, oftast kommer du antagligen vilja ha mer information om personerna och då är det inte så praktiskt att slå ihop raderna iaf.

Instämmer precis som du säger så tycker jag inte att det är så praktiskt, Om man kollar på det ur optimeringssynpunkt så bör nog det bästa vara att bara skapa en vy gör alla ihopkopplingar med den. Selektera sedan på vilkoret...

Känns som om det är den bästa lösningen som jag kan komma på om det blir en stor databas och om det skall köras ofta.
Alltså slippa uppdelningarna som man måste göra senare för att ta ut datat igen och man kommer säkert stöta på en hel del problem ifall man bestämmer sig för att ändra i applikationen.

Visa signatur

//clooak Mer CO-OP åt folket!