Grupowanie wyników

2009-03-17 17:52:55
 

Dosyć często spotykam się z pytaniem, jak napisać grupowanie wyników, czyli np. mamy produkty które należą do kategorii i jak wyświetlić wszystkie kategorie wraz z należącymi do nich produktami. Początkujący programiści albo nie wiedzą jak to zrobić, albo robią to przy użyciu wielu zagnieżdżonych zapytań, co niezbyt dobrze wpływa na wydajność aplikacji.

Rozwiązanie problemu opiszę na dwóch przykładach. Działanie obydwu będzie bardzo podobne: najpierw jednym zapytaniem pobieramy wszystkie dane, następnie w php grupujemy je według potrzeb i na koniec tworzymi stronę wynikową.

Przykład 1
Mamy kategorie. Do każdej kategorii może należeć kilka produktów. Chcemy wyświetlić wszystkie kategorie wraz z produktami jakie do nich należą.

Zaczniemy od utworzenia tabel i wypełnienia ich danymi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- utworzenie tabel
CREATE TABLE `category` (
  `ID` int unsigned NOT NULL auto_increment,
  `NAME` varchar(32) NOT NULL ,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `NAME` (`NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `product` (
  `ID` int unsigned NOT NULL auto_increment,
  `FK_CATEGORY` int unsigned NOT NULL ,
  `NAME` varchar(32) NOT NULL ,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `NAME` (`NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- zasilenie tabel danymi
insert  into `category` (`ID`,`NAME`) values 
(1,'kategoria 1'),
(2,'kategoria 2'),
(3,'kategoria 3'),
(4,'kategoria bez produktów');

insert  into `product` (`ID`,`FK_CATEGORY`,`NAME`) values 
(1,1,'Produkt1 z kategori 1'),
(2,1,'Produkt2 z kategori 1'),
(3,2,'Produkt1 z kategori 2'),
(4,3,'Produkt1 z kategori 3'),
(5,3,'Produkt2 z kategori 3'),
(6,3,'Produkt3 z kategori 3');

Teraz skrypt, który pobierze dane z bazy i utworzy odpowiednią tablicę:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mysql_connect('localhost','root','') or die(mysql_error());
mysql_selectdb('test') or die(mysql_error());

//pobranie wszystkich danych
$sql 'select p.NAME PNAME,p.ID PID, c.ID CID, c.NAME CNAME from category c '.
  
'left join product p on p.FK_CATEGORY = c.ID '.
  
'order by c.NAME asc, p.NAME asc'//sortujemy po nazwie kategorii i po nazwie produktu

//jeśli chcemy pobrać tylko kategorie z produktami to zmieniamy trochę zapytanie:
//$sql = 'select p.NAME PNAME,p.ID PID, c.ID CID, c.NAME CNAME from product p '.
//  'left join category c on p.FK_CATEGORY = c.ID '.
//  'order by c.NAME asc, p.NAME asc'; //sortujemy po nazwie kategorii i po nazwie produktu

$res mysql_query($sql) or die(mysql_error());

//najpierw pozyskamy niezbędne dane
$categories = array();
while (
$row mysql_fetch_array($res)){
  
$cid $row['CID'];
  
  
//jeśli nie było jeszcze danej kategorii, to ją tworzymy
  
if (!isset($categories[$cid]))
    
$categories[$cid] = array('name' => $row['CNAME'], 'products' => array());
  
  
//dodajemy do kategorii kolejne produkty
  
if (!empty($row['PNAME'])) //jeśli istnieje produkt
    
$categories[$cid]['products'][] = array('name' => $row['PNAME'], 'id'=>$row['PID']);
}
//print_r($categories); //do obejrzenia jak wygląda wygenerowana tablica

W wyniku tego skryptu otrzymamy taką tablicę:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Array
(
    [1] => Array
        (
            [name] => kategoria 1
            [products] => Array
                (
                    [0] => Array
                        (
                            [name] => Produkt1 z kategori 1
                            [id] => 1
                        )

                    [1] => Array
                        (
                            [name] => Produkt2 z kategori 1
                            [id] => 2
                        )

                )

        )

    [2] => Array
        (
            [name] => kategoria 2
            [products] => Array
                (
                    [0] => Array
                        (
                            [name] => Produkt1 z kategori 2
                            [id] => 3
                        )

                )

        )

    [3] => Array
        (
            [name] => kategoria 3
            [products] => Array
                (
                    [0] => Array
                        (
                            [name] => Produkt1 z kategori 3
                            [id] => 4
                        )

                    [1] => Array
                        (
                            [name] => Produkt2 z kategori 3
                            [id] => 5
                        )

                    [2] => Array
                        (
                            [name] => Produkt3 z kategori 3
                            [id] => 6
                        )

                )

        )

    [4] => Array
        (
            [name] => kategoria bez produktów
            [products] => Array
                (
                )

        )

)

Jedyne co należy teraz zrobić to na podstawie uzyskanej tablicy wygenerować stronę:

1
2
3
4
5
6
7
8
9
10
11
//a teraz wygenerujemy stronę na podstawie uzyskanych danych
echo '<ul>';
foreach (
$categories as $idCat => $category){ //petla, która leci po kategoriach
  
echo '<li>'.$category['name'].' (liczba produktow: '.count($category['products']).')<ul>';
  foreach (
$category['products'] as $product){ //pętla, która leci po produktach w kategorii
    
echo '<li>Produkt o nazwie <b>'.$product['name'].'</b> i id <b>'.$product['id'].'</b></li>';
  }
  echo 
'</ul></li>';
}
echo 
'</ul>';

Oto wynik:

  • kategoria 1 (liczba produktow: 2)
    • Produkt o nazwie Produkt1 z kategori 1 i id 1
    • Produkt o nazwie Produkt2 z kategori 1 i id 2
  • kategoria 2 (liczba produktow: 1)
    • Produkt o nazwie Produkt1 z kategori 2 i id 3
  • kategoria 3 (liczba produktow: 3)
    • Produkt o nazwie Produkt1 z kategori 3 i id 4
    • Produkt o nazwie Produkt2 z kategori 3 i id 5
    • Produkt o nazwie Produkt3 z kategori 3 i id 6
  • kategoria bez produktów (liczba produktow: 0)

    Przykład 2
    Mamy listę miast. Chcemy wyświetlić te miasta pogrupowane względem liter, od których się zaczynają.
    Przykładowe dane:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    -- utworzenie tabel
    CREATE TABLE `city` (
      `ID` int unsigned NOT NULL auto_increment,
      `NAME` varchar(32) NOT NULL ,
      PRIMARY KEY  (`ID`),
      UNIQUE KEY `NAME` (`NAME`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    -- zasilenie tabel danymi
    insert  into `city` (`ID`,`NAME`) values 
    (1,'Białystok'),
    (2,'Olsztyn'),
    (3,'Białowieża'),
    (4,'Augustów'),
    (5,'Warszawa'),
    (6,'Olecko'),
    (7,'Wrocław');

    Skrypt, który pobierze wszystkie miasta i utworzy właściwą tablicę:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    mysql_connect('localhost','root','') or die(mysql_error());
    mysql_selectdb('test') or die(mysql_error());

    //pobranie wszystkich danych
    $sql 'select * from city '.
      
    'order by NAME asc'//sortujemy po nazwie
    $res mysql_query($sql) or die(mysql_error());

    //najpierw pozyskamy niezbędne dane
    $cities = array();
    while (
    $row mysql_fetch_array($res)){
      
    $name $row['NAME'];

      
    //jeśli będziemy w bazie mieli miasta z nazwami zaczynającymi
      //się od polskich liter należy użyć modułu mb_string
      
    $firstLetter strtoupper(substr($name,0,1));
      
      
    //jeśli nie było jeszcze danej litery, to ją tworzymy
      
    if (!isset($cities[$firstLetter]))
        
    $cities[$firstLetter] = array();
      
      
    //dodajemy kolejne miasta do danej litery
      
    $cities[$firstLetter][] = $name;
    }
    //print_r($cities); //do obejrzenia jak wygląda wygenerowana tablica

    W wyniku otrzymamy tablicę:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Array
    (
        [A] => Array
            (
                [0] => Augustów
            )

        [B] => Array
            (
                [0] => Białowieża
                [1] => Białystok
            )

        [O] => Array
            (
                [0] => Olecko
                [1] => Olsztyn
            )

        [W] => Array
            (
                [0] => Warszawa
                [1] => Wrocław
            )

    )

    Teraz wystarczy już tylko dane wyświetlić:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //a teraz wygenerujemy stronę na podstawie uzyskanych danych
    echo '<ul>';
    foreach (
    $cities as $letter => $_cities){ //petla, która leci po literach
      
    echo '<li>'.$letter.'<ul>';
      foreach (
    $_cities as $city){ //pętla, która leci po miastach w literze
        
    echo '<li>'.$city.'</li>';
      }
      echo 
    '</ul></li>';
    }
    echo 
    '</ul>';

    Oto wynik:

    • A
      • Augustów
    • B
      • Białowieża
      • Białystok
    • O
      • Olecko
      • Olsztyn
    • W
      • Warszawa
      • Wrocław



    Edit
    Na uwagę matipl zmodyfikowałem przykład 1 o kod kładący większy nacisk na udział bazy danych w obliczeniach:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <?php
    mysql_connect
    ('localhost','root','') or die(mysql_error());
    mysql_selectdb('test') or die(mysql_error());

    //pobranie wszystkich danych i ich pogrupowanie
    $sql 'select GROUP_CONCAT(p.NAME order by p.NAME asc SEPARATOR \';;;\') PRODUCTS, '//łączymy produkty poprzez ;;;. Po tym będziemy później dzielić
      
    'c.ID CID, c.NAME CNAME from category c '.
      
    'left join product p on p.FK_CATEGORY = c.ID '.
      
    'group by c.ID '.
      
    'order by c.NAME asc'//sortujemy po nazwie kategorii

    $res mysql_query($sql) or die(mysql_error());

    //najpierw pozyskamy niezbędne dane
    //teraz dostaniemy tylko tyle rekordów ile jest kategorii
    $categories = array();
    while (
    $row mysql_fetch_array($res)){
      
    $cid $row['CID'];
      
      
    $categories[$cid] = array('name' => $row['CNAME'], 'products' => array());
      
      if (!empty(
    $row['PRODUCTS'])) { //jesli są produkty dla tej kategorii
        //rozbicie tekstu z produktami na tablicę produktów 
        
    $products explode(';;;',$row['PRODUCTS']);
        foreach (
    $products as $product){
          
    $categories[$cid]['products'][] = $product;
        }
      }
    }
    //print_r($categories); //do obejrzenia jak wygląda wygenerowana tablica

    //a teraz wygenerujemy stronę na podstawie uzyskanych danych
    echo '<ul>';
    foreach (
    $categories as $idCat => $category){ //petla, która leci po kategoriach
      
    echo '<li>'.$category['name'].'<ul>';
      foreach (
    $category['products'] as $product){ //pętla, która leci po produktach w kategorii
        
    echo '<li>'.$product.'</li>';
      }
      echo 
    '</ul></li>';
    }
    echo 
    '</ul>';?>

    Powiedzmy, że teraz baza nie będzie nam zwracała nadmiarych danych w postaci powtarzanych informacji o kategoriach.
    Do złączenia produktów użyłem ;;;. Jeśli jakimś cudem w nazwie waszego produktu pojawi się też ;;; to na wyniku otrzymacie o jeden produkt więcej, powstały z podziału tego "felernego" produktu na dwa. Miejcie to na uwadze
    Jako ćwiczenie dla Was pozostawiam zmianę przykładu 2.

     

    Komentarze

     

    2009-03-21 01:24:19 gość_LBO

     
    Hej nosporze

    Na Twoim miejscu przykład zaimplementowałbym przy użyciu PDO. Posiada ono bardzo fajny modyfikator PDO::FETCH_GROUP.
    Z jego pomocą byłoby znacznie mniej kodu, a także mógłbyś pokazać przeniesienie części odpowiedzialności na bazę (litery w pierwszej kolumnie).

    Pozdrawiam

    2009-03-21 10:17:22 nospor

     
    @LBO po części masz rację.
    Wolałem jednak pokazać to od podstaw, by osoby początkujące zobaczyły jak to się robi tak na "sucho". Pozatym wielu "czytelników" nie używa PDO.

    2009-03-28 08:49:29 gość_m4tryk5

     
    A tu użyjemy funkcji substring i zwalimy troszkę większe obciążenie na bazę danych.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <? 
    mysql_connect('localhost','root','') or die(mysql_error()); 
    mysql_selectdb('test') or die(mysql_error()); 

    //pobranie wszystkich danych 
    $sql 'select ID, NAME, substring(NAME,1,1) as FIRSTLETTER from city order by NAME asc'//sortujemy po nazwie 
    $res mysql_query($sql) or die(mysql_error()); 

    //najpierw pozyskamy niezbędne dane 
    $cities = array(); 
    while (
    $row mysql_fetch_array($res)){ 
      
    $name $row['NAME']; 

      
    //jeśli będziemy w bazie mieli miasta z nazwami zaczynającymi 
      //się od polskich liter należy użyć modułu mb_string 
      
    $firstLetter $row['FIRSTLETTER']; 
       
      
    //jeśli nie było jeszcze danej litery, to ją tworzymy 
      
    if (!isset($cities[$firstLetter])) 
        
    $cities[$firstLetter] = array(); 
       
      
    //dodajemy kolejne miasta do danej litery 
      
    $cities[$firstLetter][] = $name

    print_r($cities); //do obejrzenia jak wygląda wygenerowana tablica 

    2009-03-28 13:49:28 nospor

     
    Eeeee tam, jakie to obciążenie pobrać pierwszą litere wyrazu
    Oczywiście Twój sposób jest równie dobry co i mój z małym ale:
    zapomniałeś z litery zrobić dużą

    2009-03-29 09:37:57 gość_m4tryk5

     
    Z tą dużą literą to fakt. Chciałem tylko pokazać, że niektóre rzeczy da się robić w SQL. Jakby ktoś się interesował Blind SQL Injection to substirng i benchmark są mu bardzo potrzebne (do tego jeszcze char i ord aby zobaczyć wielkość litery).

    2009-03-30 09:09:48 gość_matipl

     
    Że jak? Spodziewałem się, że zaprezentujesz rozwiązanie za pomocą SQL (group by), a nie prymitywne składanie w PHP z zastosowaniem jeszcze left join.
    Jeśli masz 10 produktów, wyłącznie 1-1 to spoko...Ale jak już to lepiej w 2 zapytaniach zrobić, jeśli logika ma być wyłącznie w PHP.

    2009-03-30 09:36:50 nospor

     
    Ale jak już to lepiej w 2 zapytaniach zrobić, jeśli logika ma być wyłącznie w PHP.

    Że jak? Hihi, zrób mi to na dwóch zapytaniach. Nawet jeśli jakimś cudem ci się uda (za bardzo nie wiem jak - nie myl dwóch zapytań od dwóch tekstów zapytań, z czego jeden jest wykonywany w pętli) to niby czemu to mam slużyć?
    ps: no można niby dwoma zapytaniami:
    jednym pobierasz wszystkie dane z jednej tabeli, drugim wszystkie dane z drugiej tabeli. W czym to ma być lepsze?
    ps2: chcąc zrobić to głównie na bazie, musiałbym sie bawić w CONCAT i temu podobne. Nie koniecznie byłoby to super fajne, szczególnie przy większej ilości informacji jakie chciałbyś w ten sposób uzyskać.
    Napisałem ten artykuł z myślą o osobach, które nagminnie stosują tutaj zapytania w pętli.

    2009-03-30 10:04:14 gość_matipl

     
    To za 2 zapytaniami, na odczepnego napisałem, że ostatecznie można jakoś w PHP to ująć, podobnie jak Ty to zrobiłeś...

    Mimo wszystko jak mówimy o grupowaniu, powinniśmy starać się jak najbardziej przerzucić logikę na bazę. Do pierwszego przykładu pasuje jak ulał...

    2009-03-30 10:07:23 nospor

     
    No ok, masz rację. Nie chciałem jednak zaciemniać kodu. Jak pisałem chodziło mi tu bardziej o pokazanie alternatywy dla x zapytań wykonywanych w pętli.
    Jednak ruszyło mnie sumienie po twoim komentarzu i podałem rozwiązanie operujące w dużej mierze na bazie danych. Mam nadzieję, iż teraz jest lepiej

    Nadal jednak nie jestem przekonany nad "wyższością" tego rozwiązania. Jeśli byśmy chcieli pobrać nie tylko nazwy produktów, ale też 10 innych pól z tabeli produktów, to rozwiązanie to staje się poprostu upierdliwe, gdyż zmusza nas do ciągłych CONCAT a później ich rozbijaniu w php.
    Rozwiązanie, z zostawieniem wszystkiego na php, jest przyjaźniejsze. Nie będziemy też tutaj wyświetlać milion rekordów by miało to mieć jakiś wpływa na wydajność.

    No chyba, że widzisz inny sposób na pogrupowanie i pobranie wszystkich danych, to chętnie posłucham.

    2009-04-16 16:04:51 gość_Sekwer

     
    Bardzo wartościowy artykuł. Przyznam, że kiedyś rozwiązywałem to w inny sposób, który bardziej obciążał bazę danych. Dzięki...

    2010-11-21 08:54:42 gość_Patryk

     
    Hej, a w jaki sposób wyciągnąć tylko X rekordów z danej kategorii? Czyli tworzymy normalnie drzewko, ale w tablicy products są tylko 5 rekordów (sortowanie według czasu dodania) - i tak w każdej tablicy...

    2010-11-22 07:57:42 nospor

     
    Musisz zrobić podzapytanie z głównego, a następnie pobierać tylko x rekordów z tego podzapytania. Podam ci inny przykład, z którego korzystam (chwilowo brak czasu)
    1
    2
    set @row = 0,@tmp=0;

    1
    2
    3
    4
    SELECT * from (
      SELECT *, IF( @tmp <> ID_KAT, @row := 1, @row := @row + 1 ) AS row_number, @tmp := ID_KAT from table) t
    WHERE t.row_number <= 5;

    ID_KAT określa jakąś rzecz po której "grupujesz".

    2010-11-22 17:19:08 gość_Patryk

     
    Houston mamy problem! Zapytanie nie spełnia warunku "<= 5", otóż zwraca więcej jak 5 wyników.
    1
    2
    3
    4
    5
    set @row = 0,@tmp=0;
    SELECT * from (
      SELECT *, IF( @tmp <> forum_id, @row := 1, @row := @row + 1 ) AS row_number, @tmp := forum_id from phpbb_topics) t
    WHERE t.forum_id in(30,31) and t.row_number <= 5


    Zapytanie związane z pobraniem tematów z kategorii (forum_id) o identyfikatorze 30,31.
    Zrzut: http://img228.imageshack.us/img228/4305/beztytuumjm.png - Wszystkie rekordy dla forum_id = 30, i tylko jeden dla 31 (bo tylko jeden temat mam w dziale 31)

    2010-11-22 19:58:50 nospor

     
    1) IN daj w podzapatyniu. Szanuj mysql swego jak siebie samego
    2) Zapomniałem dodać sortowania
    1
    2
    3
    4
    5
    set @row = 0,@tmp=0
    SELECT * from ( 
      SELECT *, IF( @tmp <> forum_id, @row := 1, @row := @row + 1 ) AS row_number, @tmp := forum_id from phpbb_topics where forum_id in(30,31)  order by forum_id asc) t 
    WHERE t.row_number <= 5

    2010-11-23 13:21:43 gość_Patryk

     
    Świetnie, dziękuje za pomoc nospor. Pozdrawiam.

    2010-12-03 14:08:50 gość_adam

     
    Może się komuś sprzyda.

    Pierwsze zapytanie ma jedną wadę, otóż subselect musi pobrać wszystkie rekordy i ponumerować od 1 do n, dopiero w selekcie zewnętrznym są brane interesujące nas wyniki w naszym przypadku do 2 odpowiedzi na posta.

    mysql> SELECT forumId, parentForumId from (
    -> SELECT forumId, parentForumId, IF( @tmp <> parentForumId, @row := 1, @row := @row + 1 ) AS row_number, @tmp := parentForumId from `groupForum` where parentForumId in(30,31) order by parentForumId asc) t
    -> WHERE t.row_number <= 2;
    +---------+---------------+
    | forumId | parentForumId |
    +---------+---------------+
    | 106 | 30 |
    | 134 | 30 |
    | 632 | 31 |
    | 638 | 31 |
    +---------+---------------+
    4 rows in set (0.00 sec)

    mysql> set @row = 0,@tmp=0;
    Query OK, 0 rows affected (0.00 sec)

    Wynik można uzyskać przenosząc liczenie odpowiedzi do where, zysk mamy taki że jeżeli @row przekracza 2 pomijane są następne rekordy aż napotkamy następny post i liczenie zacznie się od nowa

    mysql> SELECT forumId, @tmp := parentForumId as parentForumId from `groupForum`
    -> where parentForumId in(30,31) and IF( @tmp <> parentForumId, @row := 1, @row := @row + 1 ) <=2 order by parentForumId asc;
    +---------+---------------+
    | forumId | parentForumId |
    +---------+---------------+
    | 106 | 30 |
    | 134 | 30 |
    | 632 | 31 |
    | 638 | 31 |
    +---------+---------------+
    4 rows in set (0.00 sec)

    Ten sposób można wykorzystać zamiast GROUP BY lub DISTINCT i całkiem dobrze sobie to radzi

    2011-01-20 19:36:23 gość_oj

     
    A jak to zrobić na PDO?

    2011-01-20 20:23:32 nospor

     
    Dokładnie tak samo. Tylko zamiast mysql_query używać składni PDO

    2011-06-06 10:31:01 gość_Andrzej

     
    a co jak mam jedną tabelę
    1
    2
    3
    4
    5
    6
    7
    id|strona|tytul|id_rodzica
    1|strona1.html|Tytul strony 1|0
    2|strona2.html|Tytul strony 2|0
    3|strona3.html|Tytul strony 3|0
    4|strona4.html|Tytul strony 4|2
    5|strona5.html|Tytul strony 5|0


    i chce taki efekt uzyskać w kodzie wynikowym:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <ul>
    <li><a href="strona1.html">Tytul strony 1</a></li>
    <li><a href="strona2.html">Tytul strony 2</a>
    <ul>
    <li><a href="strona4.html">Tytul strony 4</a></li>
    </ul>
    </li>
    <li><a href="strona3.html">Tytul strony 3</a>
    <ul>
    <li><a href="strona5.html">Tytul strony 5</a></li>
    </ul>
    </li>
    <ul>


    Mogę prosić o podpowiedz jak za pomocą tego kodu ze strony to osiągnąć?
    Ten przykład z artykułu pokazuje działanie na 2 tabelach a ja mam jedną.

    2011-06-06 11:23:33 nospor

     
    Robisz dokładnie tak samo, z tym że Twoją drugą tablicą jest pierwsza
    1
    2
    3
    4
    select t2.strona t2strona,t2.id t2id,t2.tytul t2tytul, t1.id t1id, t1.strona t1strona, t1.tytul t1tytul from tabela t1 
      left join tabela t2 on t2.id_rodzica = t1.id 
      where t1.id_rodzica=0 order by t1.tytul asc, t2.tytul asc

    2011-06-06 22:25:44 gość_Artur

     
    OOO w końcu znalazłem coś co prawie idealnie robi to co bym chciał, dlaczego w przykładzie który podałeś jak mam tylko jeden id_rodzica większy od 0 to wyświetla się dobrze a jak już dam tak jak poniżej to nie mam id 4 w drzewie, czemu tak się dzieje?
    1
    2
    3
    4
    5
    1|strona1.html|Tytul strony 1|0
    2|strona2.html|Tytul strony 2|1
    3|strona3.html|Tytul strony 3|0
    4|strona4.html|Tytul strony 4|2
    5|strona5.html|Tytul strony 5|0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $sql 'select t2.strona t2strona,t2.id t2id,t2.tytul t2tytul, t1.id t1id, t1.strona t1strona, t1.tytul t1tytul from tabela t1 
      left join tabela t2 on t2.id_rodzica = t1.id 
      where t1.id_rodzica=0 order by t1.tytul asc, t2.tytul asc'
    //sortujemy po nazwie kategorii i po nazwie produktu

    $res mysql_query($sql) or die(mysql_error());

    //najpierw pozyskamy niezbędne dane
    $categories = array();
    while (
    $row mysql_fetch_array($res)){
      
    $cid $row['t1id'];
      
      
    //jeśli nie było jeszcze danej kategorii, to ją tworzymy
      
    if (!isset($categories[$cid]))
        
    $categories[$cid] = array('tytul' => $row['t1tytul'], 'tytul' => $row['t1tytul'], 'tytul' => $row['t1tytul'], 'child_nodes' => array());
      
      
    //dodajemy do kategorii kolejne produkty
      
    if (!empty($row['t2tytul'])) //jeśli istnieje produkt
        
    $categories[$cid]['child_nodes'][] = array('tytul' => $row['t2tytul'], 'id'=>$row['t2id']);

    to dostaje:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    Array
    (
        [1] => Array
            (
                [tytul] => Tytuł 1
                [child_nodes] => Array
                    (
                        [0] => Array
                            (
                                [tytul] => Tytuł 2
                                [id] => 2
                            )
                    )
            )
        [3] => Array
            (
                [tytul] => Tytuł 3
                [child_nodes] => Array
                    (
                    )
            )
        [5] => Array
            (
                [tytul] => Tytuł 5
                [child_nodes] => Array
                    (
                    )
            )
    )

    2011-06-07 06:59:41 nospor

     
    Bo to, co ci tu pokazałem, działa przy założeniu, że jest tylko jeden poziom dzieci. Ty natomiast u siebie masz rodzica, dziecko i wnuczka: 1->2->4

    2011-06-07 07:43:38 gość_Artur

     
    rano na to też wpadłem, a jak duży to problem rozbudować do powiedzmy 5 poziomów?
    jeszcze jedno: dodałem do tej przykładowej tabeli pole 'rola', i do where and t1.rola='wartosc' ale to mi chyba wyszuka z rola ='wartosc' dla 1 poziomu, a co zrobic aby szukał we wszystkich poziomach?

    2011-06-07 08:12:37 nospor

     
    and t2.rola='wartosc'

    Co do pięciu poziomów to trzeba by to już w ogóle inaczej napisać oraz zaprząc do tego prawdopodobnie rekurencję.

    2011-09-21 23:47:09 gość_Łukasz Tkacz

     
    A co, jeżeli pobieram te same dane z jednej tabeli mam sporo pól (wymienianie wszystkiego nie ma sensu). Poziom ogranicza się do rodzica i dzieci, wygląda to tak:

    1
    2
    3
    4
    SELECT tp.*, tch.* 
    FROM table1 tp 
    LEFT JOIN table1 tch ON tch.parent = tp.id
    ORDER BY tp.name ASC, tch.name ASC


    Wyniki wydają się zgadzać, ale kolumny nazywają się tak samo, a więc w php nic rozdzielić nie mogę. Czy dla pobierania wszystkich wyników mogę jakąś "z automatu" zmodyfikować nazwy kolumn jednej z nich, np. dopisek "parent_"? Jak do tej pory realizowałem to przez dwa zapytania - z pierwsze w php tworzyło tablicę z kategoriami, po drugim jest ona wypełniana danymi.

    2011-09-22 08:41:39 nospor

     
    Musisz tworzyć aliasy, np
    ...... tch.pole1 TCHPOLE1, tch.pole2 TCHPOLE2,.....

    2011-09-22 10:21:02 gość_Łukasz Tkacz

     
    No to mam dylemacik... w sumie wynikający z lenistwa. Gdy jest sporo pól, to nadzwyczajnie nie che mi się czegoś takiego robić. Poza tym dochodzi drugi aspekt przy sprawdzeniu z EXPLAIN - gdy mam dwa zapytania oba używają WHERE (pole parent, indeks), oba są sortowane. Gdy mam jedno zapytanie, dodatkowo tworzona jest tablica tymczasowa. Sumarycznie czas jednego zapytania jest jednak krótszy niż tamtych dwóch.
    No to nic, na coś będę musiał się zdecydować, dziękuję za informację i interesujący wpis.

    2011-09-22 10:39:43 nospor

     
    W Twoim zapytaniu brakuje jeszcze jednej ważnej rzeczy. Teraz pobierasz kilkukrotnie te same dane z pierwszej i z drugiej tabeli. Z pierwszej tabeli powinieneś brać tylko te dane, które nie mają rodzica, czyli coś takiego:
    1
    2
    3
    4
    5
    6
    SELECT tp.*, tch.*  
    FROM table1 tp  
    LEFT JOIN table1 tch ON tch.parent = tp.id 
    where tp.parent=0
    ORDER BY tp.name ASC, tch.name ASC

    założyłem, że gdy nie ma rodzica to jest ustawione 0.

    Co do dużej ilości aliasów - no niestety, tego się nie przeskoczy. Lepiej jednak raz to napisać i mieć szybki sql niż w pętli zażynać bazę.

    2011-12-08 16:10:31 gość_Rafik89

     
    Dzięki, bardzo mi pomogłeś, nie chciałem umieszczać już zapytania w zapytaniu

    Dodaj komentarz

     

    Dostępne bbcode: b, u, i, color, size, quote, img, url, list, il (tylko w list), code, php, css, html, sql, js