[MySQL] Rekursive Abfrage: Katerogien/Unterkategorien optisch unterscheiden

splat

Erfahrenes Mitglied
Hi,

ich bin gerade am basteln einer Funktion zum generieren einer Preisliste.
Es sollen hierbei sämtliche Artikel aus allen Katergorien und Unterkategorien ausgegeben werden. Die Abfrage funktioniert dank einem Freund jetzt auch wunderbar :)
Das Problem ist jetzt nur: Durch die lange Liste verliert man schnell den Überblick. Die Haupt- sowie die Unterkategorien werden nicht unterschieden und sehen gleich aus.. diese würde ich gerne unterschiedlich kennzeichnen. Entweder farbig, oder eben durch einrücken.. das wäre sogar noch besser.
Wie könnte man so etwas denn realisieren?
Die Idee war zuerst, eine Variable $level mitlaufen zu lassen, die bei jedem erneuten Aufruf der Funktion um 1 erhöht wird. Das funktioniert aber so leider auch nicht..
Hat jemand noch eine Idee oder einen Denkanstoss wie man so etwas vielleicht noch lösen könnte? :(


Die Datenbank sieht grob folgendermaßen aus:

Code:
categories:
id | parent_id | name

products:
id | name

products_categories:
product_id | category_id


Es gibt eine Kategorie "Home" mit der id 1. Alle Hauptkategorien haben unter parent_id eine 1 eingetragen. Einige Hauptkategorien haben mehrere Unterkategorien/Ebenen, andere haben keine Unterkategorien, sondern enthalten direkt die Artikel. Wie bereits erwähnt: Die Abfrage funktioniert einwandfrei, es geht jetzt nur noch um die Kennzeichnung der verschiedenen Ebenen.

preisliste.php
PHP:
function generiere_preisliste($category_id) {

	$sql= "SELECT id, parent_id, name AS kategoriename FROM categories WHERE parent_id = ".$category_id." AND id != ".$category_id.";
	$result = db_query($sql);
	
	if(mysql_num_rows($result) > 0) {
	
		while(FALSE !== ($array = mysql_fetch_array($result, MYSQL_ASSOC))) {
			
			echo '<p>'.$array['kategoriename'].'</p>';
						
			$sql_2 = "SELECT p.artikelnr, p.name AS produktname, p.preis FROM products_categories AS pc, products AS p WHERE pc.category_id = ".$array['id']." AND pc.product_id = p.id;
			$result_2 = db_query($sql_2);
			while(FALSE !== ($array_2 = mysql_fetch_array($result_2, MYSQL_ASSOC))) {
				echo '<p>'.$array_2['artikelnr'].' / '.$array_2['produktname'].' / '.$array_2['preis'].'</p>
			}
			
		generiere_preisliste($array['id']);
		}
	}
}

Ich bin für jeden Ratschlag dankbar ;)

Gruß,
Marc
 
Ich kann grad die Anordnung nicht ganz nachvollziehen, wie du das haben möchtest.

categories
|​
+ -------- products​
|​
+ -------- products_categories​

Mal eine kleine grafische Anordnung. Kommt die deinen Vorstellungen gleich?
 
die tabelle 'products_categories' enthält nur die Zuordnungen welche Artikel sich in welchen Kategorien befinden.
Das habe ich so gewählt, um ein Produkt auch mehreren Kategorien zuordnen zu können.


Code:
categories:
-------------------------
id  |  parent_id  | name
-------------------------
1   |  0          | home
3   |  1          | kat1
4   |  3          | kat2



products_categories:
------------------------
product_id | category_id
------------------------
200        | 3
300        | 3



products:
------------------------
id  | name
------------------------
200 | prod1
300 | prod2


Das ganze ist dann in etwa so angeordnet:
Code:
Home 
  |
  +-----kat1
  |       |
  |       |
  |       +----kat2
  |       |      |
  |       |      +-----prod1
  |       |            prod2
  |       |              ...
 
die preisliste sieht momentan schlicht und einfach so aus:

Code:
Home 
kat1
kat2
prod1
prod2
...

und sollte durch die unterscheidung der ebenen dann so aussehen:
Code:
Home 
   kat1
      kat2
         prod1
         prod2
   kat3
      prod3
      prod4
   kat4
...
 
Hi splat,
ich habe dazu eine kleine Beispielfunktion geschrieben:
PHP:
<?php
function generateList($root_id = 0, $indent = 0) {
	global $db;

	// überprüfen ob die Kategorie untergeordnete Kategorien hat...
	$sql = "SELECT
			cat_id,
			cat_name
		FROM
			test_products_categories
		WHERE
			cat_parent_id = " . $root_id;

	$res =& $db->query($sql);
	if (PEAR::isError($res)) {
		echo 'Standard Message: ' . $res->getMessage() . "\n";
		echo 'Standard Code: ' . $res->getCode() . "\n";
		echo 'DBMS/User Message: ' . $res->getUserInfo() . "\n";
		echo 'DBMS/Debug Message: ' . $res->getDebugInfo() . "\n";
		exit;
	}
	
	$out = '';

	if ($res->numRows() > 0) {
		// wenn ja, dann gucken ob wir in der Root-Ebene sind... 
		
		$roots = $db->getAll($sql);

		if ($root_id == 0) {
			// wenn ja, dann fangen wir so an:
			/*
			  <span>Kategoriename</span>
			  <ul>
				<!-- für jede Kategorie rufen wir hier generateList wieder auf -->
				
			  </ul>
			*/
			foreach ($roots as $root) {
				$head = str_repeat("\t", $indent) . "<span>" . $root['cat_name'] . "</span>\n";
				$head .= str_repeat("\t", $indent) . "<ul>\n";
				$tail = str_repeat("\t", $indent) . "</ul>\n";

				$out .= $head . generateList($root['cat_id'], $indent+2) . $tail;
			}
		} else {
			// wenn wir nicht in der Root-Ebene sind, siehts so aus:
			/*
			  <li>
			   <span>Kategoriename</span>
			   <ul>
				
				<!-- für jede weitere untergeordnete Kategorie rufen wir wiedermals generateList auf
			
			   </ul>
			  </li> 
			*/
			foreach ($roots as $root) {
				$head  = str_repeat("\t", $indent) . "<li>\n";
				$head .= str_repeat("\t", $indent+1) . "<span>" . $root['cat_name'] . "</span>\n";
				$head .= str_repeat("\t", $indent+1) . "<ul>\n";
				$tail  = str_repeat("\t", $indent+1) . "</ul>\n";
				$tail .= str_repeat("\t", $indent) . "</li>\n";

				$out .= $head . generateList($root['cat_id'], $indent+2) . $tail;
			}
		}
		$out = $list . $out;
		return preg_replace('#(\t+<ul>[\n\t]+</ul>\n)#si', '', $out);
		
	} else {
		// wenn nicht, dann Produkte der aktuelle Kategorie auflisten
	
		$sql = "SELECT
				product_id,
				product_name
			FROM
				test_products_categories_values AS pcv,
				test_products as p
			WHERE
				val_category = " . $root_id . "
			AND
				product_id = val_product";
				
		$res =& $db->query($sql);
		if (PEAR::isError($res)) {
			echo 'Standard Message: ' . $db->getMessage() . "\n";
			echo 'Standard Code: ' . $db->getCode() . "\n";
			echo 'DBMS/User Message: ' . $db->getUserInfo() . "\n";
			echo 'DBMS/Debug Message: ' . $db->getDebugInfo() . "\n";
			exit;
		}

		if ($res->numRows() > 0) {
			$out = '';
			while ($res->fetchInto($row)) {
				$out .= str_repeat("\t", $indent) . '<li><a href="script.php?pid=' . $row['product_id'] . '">' . $row['product_name'] . "</a></li>\n";
			}
			return $out;
		} else {
			return '';
		}
	}
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
<head>
 <title>Title</title>
 <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
 <meta name="language" content="de" />
 <meta name="author" content="Marvin Schmidt" />
</head>
<body>
<div>
<?php
	echo generateList();
?>
</div>
</body>
</html>

Die Tabellenstruktur (mit der ich die Funktion getestestet habe) sieht wiefolgt aus:
SQL:
-- 
-- Tabellenstruktur für Tabelle `test_products`
-- 

CREATE TABLE `test_products` (
  `product_id` smallint(5) unsigned NOT NULL auto_increment,
  `product_name` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`product_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 ;

-- 
-- Daten für Tabelle `test_products`
-- 

INSERT INTO `test_products` (`product_id`, `product_name`) VALUES (1, 'Produkt Nr. 1'),
(2, 'Produkt Nr. 2');

-- --------------------------------------------------------

-- 
-- Tabellenstruktur für Tabelle `test_products_categories`
-- 

CREATE TABLE `test_products_categories` (
  `cat_id` tinyint(3) unsigned NOT NULL auto_increment,
  `cat_parent_id` tinyint(3) unsigned NOT NULL,
  `cat_name` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`cat_id`)
) ENGINE=MyISAM AUTO_INCREMENT=11 ;

-- 
-- Daten für Tabelle `test_products_categories`
-- 

INSERT INTO `test_products_categories` (`cat_id`, `cat_parent_id`, `cat_name`) VALUES (1, 0, 'Kategorie 1'),
(2, 1, 'Kategorie 1.1'),
(3, 2, 'Kategorie 1.1.1'),
(4, 0, 'Kategorie 2'),
(5, 1, 'Kategorie 1.2'),
(6, 4, 'Kategorie 2.1'),
(7, 4, 'Kategorie 2.2'),
(8, 7, 'Kategorie 2.2.1'),
(9, 8, 'Kategorie 2.2.1.1'),
(10, 9, 'Kategorie 2.2.1.1.1');

-- --------------------------------------------------------

-- 
-- Tabellenstruktur für Tabelle `test_products_categories_values`
-- 

CREATE TABLE `test_products_categories_values` (
  `val_id` smallint(5) NOT NULL auto_increment,
  `val_product` smallint(5) unsigned NOT NULL,
  `val_category` tinyint(3) unsigned NOT NULL,
  PRIMARY KEY  (`val_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 ;

-- 
-- Daten für Tabelle `test_products_categories_values`
-- 

INSERT INTO `test_products_categories_values` (`val_id`, `val_product`, `val_category`) VALUES (1, 1, 3),
(2, 2, 3);

Bin dabei davon ausgegangen, dass die Hauptkategorien die cat_parent_id Null haben, sowie du's in deinem zweiten Beitrag dargestellt hast.

Desweiteren ist es so ausgerichtet, dass eine Kategorie entweder Unterkategorien hat oder Produkte umfasst, jedoch nicht beides, falls Kategorien weitere Kategorien und Produkte beinhalten können sollen, dann musst du das noch darauf abstimmen.

$db ist eine Instanz der PEAR-Datenbank-Klasse DB

Ich hoffe ich konnte dir damit helfen.

Gruß
Marvin
 
Zuletzt bearbeitet:
Hi Marvin!

Erstmal vielen Dank für deine Hilfe und für die von dir geschriebene Funktion! :)

Jedoch kämpfe ich nun mit der Installation von pear bei meinem Hoster.
Irgendwie will das ganze wohl noch nicht so ganz... Ich habe es installiert und die php.ini um folgende Zeile erweitert:
Code:
include_path = ".:/usr/local/lib/php:/mein-serverpfad/mein-pfad-zum-pearordner"

Beim Ausführen deiner Funktion bekomme ich aber nach wie vor immernoch folgenden Fehler:

Fatal error: Call to a member function on a non-object in /.../test.php on line 14

PHP:
    $res =& $db->query($sql);

am Skript liegt das ja weniger, oder?
Bin da noch nicht so ganz durchgestiegen.. ;)

Gruß,
Marc
 
Hi Marc,
es ist nicht zwingend notwendig PEAR zu installieren und dann die PEAR-Datenbank-Klasse zu verwenden. Das ist dir überlassen. Ich habe das nur erwähnt, damit man weiß was für ein Objekt $db ist und man weiß, wo man die Methoden nachschlagen kann.

Wenn du es weiterhin mit PEAR arbeiten willst, dann sei dir gesagt, dass der Fehler auftritt, weil $db bei dir kein Objekt ist. Ich hatte das außerhalb der Funktion gemacht, weswegen ich es dann mit global $db innerhalb der Funktion verfügbar gemacht habe.

Mein Connect sieht so aus:
PHP:
<?php
require_once 'DB.php';
require_once 'include/config.inc.php'; // enthält die Variablen $dbuser, $dbpass, $dbhost & $dbname mit den entsprechenden Werten...

$dsn = array(
        'phptype'       => 'mysql',
        'username'      => $dbuser,
        'password'      => $dbpass,
        'hostspec'      => $dbhost,
        'database'      => $dbname
);

$db =& DB::connect($dsn);
if (DB::isError($db)) {
        die($db->getMessage());
}
$db->setFetchMode(DB_FETCHMODE_ASSOC);
?>

Eine andere Möglichkeit wäre sich das auf seine bevorzugte Art der Datenbankanbindung umzuschreiben, z.B. könntest du die Verbindung mit [phpf]mysql_connect[/phpf] & [phpf]mysql_select_db[/phpf] herstellen und dann anstatt:
PHP:
$res =& $db->query($sql);
dies benutzen:
PHP:
$res = mysql_query($sql);

Gibt da mehrere Wege, die Wahl liegt bei dir ;)

Gruß
Marvin
 
Zurück