Bednarus3
5.09.2014, 15:10:41
W SQL mam tablicę zawierającą dwie kolumny: id_kategorii oraz id_rodzica. Jeżeli id_rodzica wynosi 0 to jest to kategoria na poziomie 1, a późniejsze podkategorie mają id_rodzica w zależności w której kategorii się znajdują. Chcę to wyświetlić w postaci listy tak, że główne kategorie będą całkiem po lewej, pod nią będą podkategorie przesunięte lekko w prawo i tak dalej. Koncepcja jest prosta. Wyszukuje kategorie z id_rodzica = 0. Potem wyszukuję kategorie, które w id_rodzica mają nr kategorii nadrzędnej i tak w głąb. Gdy dojdziemy do kategorii, która nie ma potomka. Wracamy o poziom wyżej, aż do zera. Nie mam pomysłu jak to zaimplementować.
Turson
5.09.2014, 15:12:23
Rekurencja ?
nospor
5.09.2014, 15:17:17
Zacznij od implementacji porządnie bazy. Zainteresuj sie strukturami drzewiastymi, np. drzewka IP. Wowcza zycie bedzie piękniejsze.
Jesli jednak nie zamierzasz tego poprawiac, to pozostaje ci to co napisał Turson: rekurencja i zazynanie bazy bezsensownymi zapytaniami
golabow
5.09.2014, 19:06:30
Napisałem taki skrypt, który może być przykładem rozwiązania z rekurencją:
<?php
class Tree {
private $id_kategorii;
private $id_rodzica;
// tablica z obiektami ktore sa dziecmi danego wezla
private $dzieci;
public function __construct( $id_kategorii, $id_rodzica )
{
$this->id_kategorii = $id_kategorii;
$this->id_rodzica = $id_rodzica;
// zmienna $db to uchwyt do twojej bazy danych
$q = "SELECT * FROM tree WHERE id_rodzica=$this->id_kategorii"; // tree to tablica z wezlami
$queryResult = $db->query( $q );
while ( $row = $queryResult->fetch_assoc() )
{
$this->dzieci[] = new Tree( $row['id_kategorii'], $row['id_rodzica'] );
}
}
public function getHtml( $html = '' )
{
if ( !empty( $this->dzieci ) ) {
$html .= '<li>'.$this->id_kategorii.'<ul>';
}
else
{
$html .= '<li>'.$this->id_kategorii.'</li>';
return $html;
}
foreach( $this->dzieci as $dziecko )
{
$html = $dziecko->getHtml( $html );
}
$html .= '</ul></li>';
return $html;
}
}
$ob = new Tree(1, 0);
$htmlTree = $ob->getHtml();
?>
<ul>
</ul>
Tworzysz obiekt klasy podając id węzła i jego ojca od którego ma być generowane drzewo. Tutaj założyłem że domyślną id_rodzic każdego obiektu w bazie jest wartość 0 która oznacza że nie ma ojca, możliwe że wartość -1 byłaby bardziej jasna. W każdym bądź razie tutaj podaje id pierwszego elementu zbioru który jest korzeniem drzewa.
Bednarus3
9.09.2014, 14:14:39
nospor, nie mogę zaimplementować od nowa bazy, bo to co robię jest rozbudowaniem, do własnych potrzeb, innego projektu, a dokładnie OscGold.
Jako, że jestem słabiutki z programowania obiektowego nie wykorzystałem tego co napisał kolega
golabow, bo po prostu nie rozumiem jak to działa. Za to, poprzez analogię, stworzyłem własną funkcję rekurencyjną, która robi to o co mi chodziło.
db_connect();
echo '<select name="nazwa" multiple="multiple">'; tree(0);
db_close();
function tree($category_id)
{
$result = mysql_query("SELECT Count(`categories_id`) FROM `categories` WHERE `parent_id` = '{$category_id}'"); if($row['Count(`categories_id`)'] == 0)
{
}
else
{
$result = mysql_query("SELECT `categories_id` FROM `categories` WHERE `parent_id` = '{$category_id}'"); {
{
$result_name = mysql_query("SELECT `categories_name` FROM `categories_description` WHERE `categories_id`=".$row['categories_id']); echo '<option>'.$row_name['categories_name'].'</option>'; tree($row['categories_id']);
}
}
}
}
Mam jeszcze jeden problem. Chcę zrobić, żeby każdy poziom w menu miał wcięcie od lewej strony. Im niższy poziom, tym większe wcięcie. Kombinowałem z <optgroup>, ale rozjeżdżało mi się.
Turson
9.09.2014, 14:17:32
Z każdym kolejnym zagłębieniem przesyłaj zmienną jako argument funkcji, a zmienną inkrementuj. Potem ile wynosi zmienna tyle px wcięcia np.
nospor
9.09.2014, 14:19:06
Pierwsze zapytanie w funkcji, ktore liczy liczbe rekordow jest niepotrzebne, bo tak czy siak potem tę liczbę liczysz.
Co do wcięć, to dodaj drugi parametr dla funkcji
function tree($category_id, $level)
Pierwsze wywolanie: tree(0,0)
Kolejne wywolania: tree($row['categories_id'], $level+1);
Zas tutaj
echo '<option>'.$row_name['categories_name'].'</option>';
dodawaj przed nazwą tyle spacji jaką wartosc ma zmienna $level. Możesz też tę ilosc przemnozyc przez 2 czy 3 dla wiekszego wcięcia.
Bednarus3
18.09.2014, 09:26:50
Dzięki nospor. Osiągnąłem dokładnie to co chciałem. Tak myślałem, żeby wcięcia zrobić w oparciu o inkrementowaną zmienną, ale bardzo długo bym kombinował, żeby przekazać ją jako argument funkcji.
Edit:
Jeszcze jedna sprawa. Chcę dodawać do bazy nową kategorię. Dane kategorii rozbite są na dwie tabele. W jednej znajduję się ID_kategorii(auto_increment) oraz adres_obrazka, w drugiej ID_kategorii oraz nazwa_kategorii. Czy da się zrobić inaczej, żeby wprowadzić dane odpowiednio do tych dwóch tabel, niż wprowadzenie danych do pierwszej tabeli, pobranie z niej wartości auto_increment, następnie wpisanie tej wartości, do drugiej tabeli, jako ID_kategorii, oraz odpowiedniej dla niej nazwa_kategorii
Turson
18.09.2014, 09:29:26
Masz trochę dziwnie rozwiązane. Czemu auto increment nie jest druga tabela a pierwsza?
Wstawiasz do pierwszej, bierzesz lastInsertId i wstawiasz do drugiej.
nospor
18.09.2014, 09:43:36
Cytat
Czy da się zrobić inaczej, żeby wprowadzić dane odpowiednio do tych dwóch tabel, niż wprowadzenie danych do pierwszej tabeli, pobranie z niej wartości auto_increment, następnie wpisanie tej wartości, do drugiej tabeli, jako ID_kategorii, oraz odpowiedniej dla niej nazwa_kategorii
Nie, musisz robić tak jak wlasnie napisales.
@Turson przeciez wyraznie w drugim poscie napisal, ze to nie jest jego struktura. On tylko z niej korzysta
Bednarus3
18.09.2014, 09:51:37
Piszę skrypt ułatwiający obsługę oscGold do własnych potrzeb. Bazę nie ja tworzyłem tylko jest gotowa dla oscGold. Dlatego muszę operować na tym co mam. W zapytaniu chodzi o to, że jak wprowadzę dane do pierwszej tabeli, następnie użyję lastInsertId lub odczytam wartość auto_increment z tabeli information_schema.TABLES, to może się zdarzyć, że między wprowadzeniem danej, a oczytaniem jej ID ktoś inny wprowadzi dane do tabeli i w drugiej tabeli będę miał ID wyższe i zostaną zapisane błędne dane, które powinny być zapisane dla niższego ID.
nospor
18.09.2014, 10:07:52
Korzystaj z LAST_INSERT_ID.
A jesli baaaaaaardzo sie boisz, ze w tym ulamku setnej sekundy ktos ci sie wstrzeli ze swoim ID, to ustaw LOCK na tabelach
Turson
18.09.2014, 10:10:32
Jeżeli mówimy o PHP MySQLowym last insert id, to on przechowuje id tego samego połączenie, wiec jak ktos ma sie wstrzelic?
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę
kliknij tutaj.