Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [PHP][MSSQL] Kilka kategorii jednego artykułu
Forum PHP.pl > Forum > Przedszkole
Kshyhoo
Witam. Pytanie czysto teoretyczne: jak najlepiej trzymać kategorie artykułu przyjmując, że będzie go można przydzielić do kilku kategorii?
Naczytałem się trochę w Google, ale mam mątwik w głowie - wszystkie znalezione przeze mnie sposoby mają tylko wady a żaden ni jednej zalety (tak twierdzą postujący).
mortus
W osobnej tabeli, przy czym mogłaby ona posiadać dodatkową kolumnę mówiącą o priorytecie kategorii.
CATEGORIES: { id, name }
ARTICLES: { id, title, content }
ARTICLES_CATEGORIES: { article_id, category_id, category_priority }

EDIT: Jeśli nie ma jakiejś kategorii głównej dla danego artykułu, to ta trzecia kolumna jest zbędna.
Kshyhoo
To tej pory miałem tak dla kategorii:
  1. kat_id kat_kategoria

i dla artykułów (gdzie trzymałem ID kategorii:
  1. tut_id tut_kat_id)
mortus
Domyślam się. Musisz to zmienić, jeśli jeden artykuł może posiadać więcej niż jedną kategorię, to potrzebujesz dodatkowej tabeli realizującej relację wiele do wielu. W tej chwili masz relację jeden do wielu - jedna kategoria może być przypisana wielu artykułom.
Kshyhoo
Czyli trzymanie w tut_kat_id ID wielu kategorii, nie jest dobrym (optymalnym) rozwiązaniem?
mortus
Cytat(Kshyhoo @ 1.01.2012, 12:51:42 ) *
Czyli trzymanie w tut_kat_id ID wielu kategorii, nie jest dobrym (optymalnym) rozwiązaniem?

Jakimś rozwiązaniem jest, ale na pewno nieoptymalnym.

EDIT: Gwoli uzupełnienia. Jeśli każdy z artykułów może mieć - dajmy na to - trzy do pięciu kategorii, to i takie rozwiązanie przejdzie. Ale jeśli liczba artykułów i kategorii z biegiem czasu znacznie wzrośnie, podobnie jak i liczba użytkowników, to np. przeszukiwanie po kategorii będzie prawdziwą katorgą dla serwera baz danych.
Według mnie jedynym słusznym rozwiązaniem jest stworzenie dodatkowej tabeli, bowiem przy takiej strukturze bazy danych zbudowanie zapytania będzie dużo łatwiejsze, a samo manipulowanie danymi będzie bardziej wydajne.
Kshyhoo
Czy tak wystarczy, czy mam jakieś indeksy zakładać?
  1. CREATE TABLE `tut_kat` (
  2. `tut_id` INT NOT NULL ,
  3. `kat_id` INT NOT NULL ,
  4. `kat_poziom` INT NOT NULL
  5. ) ENGINE = MYISAM ;

Jak mam rozwiązać zapytania? Do tej pory miałem tak (kategorie):
  1. SELECT kat_kategoria, kat_ile, kat_id
  2. FROM kategorie
  3. LEFT JOIN (SELECT tut_kat_id,
  4. COUNT(*) AS kat_ile
  5. FROM tutoriale
  6. GROUP BY tut_kat_id) AS tutoriale
  7. ON kat_id = tut_kat_id
  8. ORDER BY kat_kategoria ASC
mortus
Zdaje się, że operacje na tabeli zdecydowanie przyspieszy PRIMARY KEY na dwie kolumny jednocześnie, czyli:
  1. CREATE TABLE `tut_kat` (
  2. `tut_id` INT NOT NULL ,
  3. `kat_id` INT NOT NULL ,
  4. `kat_poziom` INT NOT NULL,
  5. PRIMARY KEY `tut_kat_pk` (`tut_id`, `kat_id`)
  6. ) ENGINE = MYISAM ;


Co do zapytania, to jakoś tak:
  1. SELECT kat_kategoria, kat_ile, kat_id
  2. FROM kategorie
  3. LEFT JOIN (
  4. SELECT kat_id, COUNT(*) AS kat_ile
  5. FROM tutoriale
  6. GROUP BY kat_id) AS tutoriale
  7. USING (`kat_id`)
  8. ORDER BY kat_kategoria ASC

Choć to tak na szybko wymyślone i dopiero później mogę ewentualnie poprawić.
Kshyhoo
Przeglądając to, coś spłodziłem i nawet nieźle wygląda:
  1. CREATE TABLE `tut_kat` (
  2. `kat_id_1` int(11) NOT NULL,
  3. `kat_id_2` int(11) NOT NULL,
  4. `kat_poziom` int(11) NOT NULL,
  5. PRIMARY KEY (`kat_id_1`,`kat_id_2`),
  6. KEY `kat_id_2` (`kat_id_2`)
  7. ) ENGINE=MyISAM DEFAULT CHARSET=utf8;

gdzie dwie pierwsze kolumny to poziomy kategorii a trzecia to zagłębienie.
Mam teraz jedynie problem, ze zliczeniem artykułów w poszczególnych kategoriach. Przedtem działałem tak:
  1. SELECT kat_kategoria, kat_ile, kat_id
  2. FROM kategorie
  3. LEFT JOIN (SELECT tut_kat_id,
  4. COUNT(*) AS kat_ile
  5. FROM tutoriale
  6. GROUP BY tut_kat_id) AS tutoriale
  7. ON kat_id = tut_kat_id
  8. ORDER BY kat_kategoria ASC
mortus
A to zapytanie, które wyżej napisałem daje jakieś wyniki? Pisałem "na szybko", bo musiałem wyjść i nie wiem, czy działa i czy działa prawidłowo.
Kshyhoo
Zwraca błąd Unknown column 'kat_id' in 'field list' .

W sumie, ja nadal nie wiem, jak to trzymać w bazie - jak artykuł ma mieć wiele kategorii.
mortus
To może po polsku (kolumny przykładowe).
KATEGORIE: {
id_kategorii (klucz główny)
nazwa
}
ARTYKUŁY: {
id_artykułu (klucz główny)
tytuł,
data_utworzenia
}
KATEGORIE_ARTYKUŁÓW: {
id_kategorii (klucz obcy połączony z tabelą KATEGORIE kolumną id_kategorii)
id_artykułu (klucz obcy połączony z tabelą ARTYKUŁY kolumną id_artykułu)
ważność (kolumna dodatkowa określająca, która z kategorii jest ważniejsza - kolumnę tę można pominąć)
}

Nie do końca wiem, co ma robić to zapytanie. Ma liczyć liczbę artykułów/tutoriali w każdej z kategorii? Czy może ma liczyć do ilu kategorii należy dany artykuł/tutorial?

Z tymi kategoriami jest dokładnie tak samo, jak z tagami (słowami kluczowymi) opisującymi dany artykuł.
Kshyhoo
Czyli, jak dobrze rozumiem, KATEGORIE_ARTYKUŁÓW ma wiązać całość:
Kod
id_artykułu | id_kategorii
     1      |      1
     1      |      4
     1      |      5
     2      |      1
     2      |      2
     2      |      4
     2      |      6

Czyli, że artykuł ID=1 należy do kategorii ID=1, 4, 5 a artykuł ID=2, do kategorii ID=1, 2, 4, 6...
mortus
Dokładnie, wtedy zapytaniem:
  1. SELECT `K`.`id_kategorii`, `K`.`nazwa`, COUNT(*) AS `liczba_artykułów` FROM `KATEGORIE` `K` LEFT JOIN `KATEGORIE_ARTYKUŁÓW` `K_A` USING (`id_kategorii`) GROUP BY `K`.`id_kategorii`

wyciągniesz identyfikatory, nazwy oraz liczbę artykułów dla każdej z kategorii.
Kshyhoo
Mam problem jedynie z kluczami obcymi, bo baza w MyISAM.
No i muszę znaleźć sposób, na wybór i zapis (również edycję) wpisów w bazie poprzez PHP.

No i pobierałem artykuł tak:
  1. $zapytanie1 = "SELECT * FROM tutoriale LEFT JOIN kategorie ON (kat_id = tut_kat_id) WHERE tut_id='$tut_id'";

Teraz trzeba będzie to zmienić.
mortus
Nie ma znaczenia, czy to MyISAM, czy InnoDB. Najwyżej nie założysz kluczy obcych, a poza tym i tak wszystko w PHP musisz zrealizować. Wybór to proste potrójne złączenie, a zapis to po prostu dwa zapytania w tym MULTIINSERT.
Kshyhoo
Jak mniemam, moje zapytanie o pojedynczy rekord powinno wyglądać tak:
  1. $zapytanie1 = "SELECT * FROM tut_kat, tutoriale, kategorie
  2. WHERE tut_kat.id_tut=tutoriale.tut_id AND tut_kat.id_kat=kategorie.kat_id
  3. AND tut_kat.id_tut='$tut_id'";


Zmieniłem nieco moje zapytanie i wygląda ono obecnie tak:
  1. $zapytanie1 = "SELECT tut_id, tut_nr, tut_obraz, tut_tytul, tut_autor, kat_kategoria, tut_data, tut_zrodlo, tut_notatki, tut_moderator, tut_czytano, tut_stan, tut_psd, tut_tresc
  2. FROM tut_kat, tutoriale, kategorie
  3. WHERE tut_kat.id_tut=tutoriale.tut_id
  4. AND tut_kat.id_kat=kategorie.kat_id
  5. AND tut_kat.id_tut='$tut_id'";

Jednak, gdy artykuł ma kilka kategorii, w phpMyAdmin mam kilka takich samych wyników, różniących się jedynie ID kategorii. Jaki mam zastosować warunek, żeby tego uniknąć?
mortus
Ta nadmiarowość jest jak najbardziej zrozumiała (i nawet do wybaczenia), ale można się jej pozbyć łącząc wszystkie kategorie za pomocą GROUP_CONCAT:
  1. SELECT tut_id, tut_nr, tut_obraz, tut_tytul, tut_autor, GROUP_CONCAT(kat_kategoria) kategorie, tut_data, tut_zrodlo, tut_notatki, tut_moderator, tut_czytano, tut_stan, tut_psd, tut_tresc
  2. FROM tut_kat, tutoriale, kategorie
  3. WHERE tut_kat.id_tut=tutoriale.tut_id
  4. AND tut_kat.id_kat=kategorie.kat_id
  5. AND tut_kat.id_tut='{$tut_id}'
  6. GROUP BY tut_id
thek
Ja bym się już zastanawiał nad silnikiem. Liczba artykułów z czasem rośnie. Modyfikacje tabeli złączeniowej będą może nie tak częste, ale w MyISAM głupi update czy insert robią lock na całą tabelę :/ Lepiej przejść na InnoDB w przypadku złączeniówki i mieć tylko lock na konkretne wiersze. Ogólnie mortus dobrze radzi i sam identycznie postępuję. Co do edycji lub dodania to masz zapewne MySQL, więc nie widze problemu:
1) Dodajesz nowy artykuł: INSERT zwróci Ci id nowo dodanego. Tylko go podstaw do złączeniówki smile.gif
2) Uaktualniasz artykuł: Najprościej DELETE istniejących aktualnie i insert wybranych przez usera. W wersji inteligentniejszej porównujesz stare i nowe. Usuwasz to, które są w starej wersji i dodajesz te, których w starej nie ma. Plus jest taki, że może Ci się zredukować całość do tylko usuwania albo tylko dodania. Minus? Zapewne usunięcie i dodanie według pierwszej metody będą szybsze niż po stronie php z kombinowaniem co ma być dodane, a co usunięte smile.gif
Kshyhoo
Ufff, udało mi się wymodzić takiego potwora:
  1. $kat_list = ""; $sel = "";
  2. $result = dbquery("SELECT kat_id, kat_kategoria FROM kategorie WHERE kat_cat='0' ORDER BY kat_order");
  3. if (dbrows($result) != 0) {
  4. while ($data = dbarray($result)) {
  5. $result2 = dbquery("SELECT kat_id, kat_kategoria FROM kategorie WHERE kat_cat='".$data['kat_id']."' ORDER BY kat_order");
  6. if (dbrows($result2) != 0) {
  7. $kat_list .= "<optgroup label='".$data['kat_kategoria']."'>\n";
  8. while ($data2 = dbarray($result2)) {
  9. if ($_GET['kat_id'] == $data2['kat_id']) { $sel = " selected"; } else { $sel = ""; }
  10. $kat_list .= "<option value='".$data2['kat_id']."'$sel>".$data2['kat_kategoria']."</option>\n";
  11. }
  12. $kat_list .= "</optgroup>\n";
  13. }
  14. }
  15. }
  16. echo '<select name="kat_id[]" style="width:150px;" size="5" multiple>\n'.$kat_list.'</select>

Odebrać mógłbym tak:
  1. if (isset($_POST['kat_id'])) {
  2.  
  3. if (count($_POST['kat_id'])==0) echo "Musisz wybrać!";
  4. else echo "Wybrane kategorie: ";
  5.  
  6. for ($i=0;$i<count($_POST['kat_id']);$i++) echo $_POST['kat_id'][$i]."; ";
  7.  
  8. }

I działa. Nie wiem teraz tylko, jak mam do zapisać do bazy, gdzie mam takie pola
  1. id_tut id_kat

czyli muszę zapisać, kilka id_kat z jednym id_tut

Podpowie mi ktoś? Da się to zrobić na raz, czy pętlą?

PS. Oczywiście, brak filtrowania (nie bijcie za mocno)...

EDIT. Coś Geshi się buntuje, całe szczęście miałem kopię wink.gif
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.
Invision Power Board © 2001-2025 Invision Power Services, Inc.