09. oktober 2002 - 10:37Der er
34 kommentarer og 1 løsning
Left join i MySQL
Jeg vil gerne have hjælp til en left-join. Denne query viser de poster, der har count>0 (altså hvor join passer og der er records på begge sider). Jeg vil også gerne have de records, der giver 0 på den anden side. Det vil sige, hvor s.sitename har 0 d.urlid.
select s.sitename, count(d.urlid) from start_urls s, urls_done d where s.urlid=d.profid and d.time>'20021009' group by s.sitename
I lang tid har samarbejdsbranchen fokuseret på at forbedre enhedsfunktioner – bedre kameraer, klarere lyd og smartere software. Men den virkelige forvandling handler ikke om funktioner.
start_urls er lille. Men forespørgslen returerer få svar, for datoen (d.time) gælder jo kun én dag. Resultatet bliver 327 records, der rummer et navn og et tal.
Den SQL-streng, jeg skrev i toppen, returnerer de 198 records fra start_urls, der har records fra i dag af i urls_done (og viser totallen). Det, som jeg er ude efter, er de resterende 129 records fra start_urls, der har 0 records fra i dag i urls_done.
Er dette svaret: If there is no matching record for the right table in the ON or USING part in a LEFT JOIN, a row with all columns set to NULL is used for the right table. You can use this fact to find records in a table that have no counterpart in another table: mysql> SELECT table1.* FROM table1 -> LEFT JOIN table2 ON table1.id=table2.id -> WHERE table2.id IS NULL;
This example finds all rows in table1 with an id value that is not present in table2 (that is, all rows in table1 with no corresponding row in table2). This assumes that table2.id is declared NOT NULL, of course. http://www.mysql.com/doc/en/JOIN.html
SELECT start_urls.sitename FROM start_urls LEFT JOIN urls_done ON start_urls.urlid = urls_done.profid WHERE urls_done.profid IS NULL AND urls_done.time > '20021009' GROUP BY start_urls.sitename;
Hvad giver denne? SELECT start_urls.sitename FROM start_urls LEFT JOIN urls_done ON start_urls.urlid = urls_done.profid WHERE urls_done.profid IS NULL;
Alternativt kan du gøre det via 2 selects, som hvis du har store tabeller vil være hurtigere. 1. select henter de id'er som skulle være gennemført. 2. select henter de id'er som ikke blev gennemført.
Udfra kode jeg har lanceret i mindre fora, burde følgende virke SELECT s.sitename, count(d.urlid) FROM start_urls AS s LEFT JOIN urls_done AS d WHERE s.urlid = d.profid && d.time > '20021009' GROUP BY s.urlid
men uden at kunne teste på reelt indhold er det selvfølgeligt svært
hvilken mysql version kører du med ? jeg bruger en let omskrevet version på en 3.23.51a på FreeBSD platform. Der virker AS navngivningen fint - også && fremfor AND, gammel vane jeg har
Flere garvede MySQL-brugere har hjulpet mig med dette, og de siger nu, at der IKKE er noget galt med SQL-syntaksen. Problemet må ligge i MySQL. Det er en LEFT JOIN lige efter lærebogen, den virker bare ikke.
Jeg har prøvet at indeksere felterne i urls_done, og det giver ingen forskel.
Jeg tror, at problemet er, at urls_done.time kan ikke være større end '20021009' samtidig med, at urls_done.profid er NULL.
Derfor virker JFL's svar fra 12:37:30. Det SQL viser de sitenames, der aldrig har haft records i urls_done. Sætter man dato-begrænsning på, giver SQL-forespørgslen ikke mening.
Du skal have index på profid, da den indgår i din join. Jo, det med dato og null er i sig selv ikke et problem - nu skal vi lige have den til at give et resultat først.
Ja.. Det er desværre et system i produktion, og de ovenstående forespørgsler får reelt serveren til at gå i knæ med 100 % cpu udnyttelse. Derfor er jeg ikke glad for eksperimenter. Men jeg prøver selv at skabe disse tabeller i et testmiljø og lægge en håndfuld rækker ind.
select s.sitename from start_urls s left join urls_done d on s.urlid=d.profid where d.profid IS NULL
Virker fint. Den returnerer de rækker, der aldrig har haft tilhørende rækker i urls_done. Derfor virker JFL's svar fra 12:37:30. Men det løser naturligvis ikke problemet, for jeg er kun interesseret i data fra sidste døgn.
Men hvis jeg sætter dato-delen ind, så fejler forespørgslen. Den returnerer hverken rækker eller en fejl. Det vil sige "AND d.time>'20021009'". Det har ingen betydning, om time kommer før profid.
Problemet er, som jeg skrev tidligere, at urls_done.time kan ikke være større end '20021009' samtidig med, at urls_done.profid er NULL. Derfor kan problemet slet ikke løses med en LEFT JOIN. Man skal bruge SUBSELECT, og det understøtter MySQL ikke endnu.
Nu er det jo en del nemmere at sige end gennemføre, men med så mange rækker begynder tankerne at svirre hen mod større db-systemer - måske man skulle kigge lidt på PostgreSQL der også er opensource.
Da opgaven lyder mere administrativ end noget der kører i drift, vil en hack-løsning eventuelt kunne løses med http://www.mysql.com/doc/en/INSERT_SELECT.html i forbindelse med en temporær tabel, men det er noget hack-værk
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.