Отображение таблицы в PHP с повторяющимися столбцами

У меня есть следующие данные в базе данных MySQL:

Autonum ID Name MetaValue 1 1 Rose Drinker 2 1 Rose Nice Person 3 1 Rose Runner 4 2 Gary Player 5 2 Gary Funny 

Я работаю в PHP сейчас, но я столкнулся с этой проблемой несколько раз, используя C #, Java и другие языки.

Теперь моя цель в прошлом и настоящем состояла в том, чтобы отобразить данные в следующем формате:

 <table> <thead> <th>Name</th> <th>MetaValue1</th> </thead> <tbody> <tr> <td>Rose</td> <td> <ul> <li>Drinker</li> <li>Nice Person</li> <li>Runner</li> </ul> </td> </tr> <tr> <td>Gary</td> <td> <ul> <li>Player</li> <li>Funny</li> </td> </tr> </tbody> </table> 

Я решил эту проблему раньше, создав класс, который представляет мою таблицу SQL. Затем я создал Dictionary для хранения EmployeeId и класса.

 Dictionary<string,MyTable> MyData = new <string,MyTable>(); Table MyMetaData = new Table(); MyMetaData SomeMetaData=getMetaValueList();//imagine a web service that does that MyData.add(EmployeeId,SomeMetaData); 

Я пропускаю шаги, но надеюсь, вы поняли мою точку зрения. Мне, вероятно, просто нужны ключевые слова, чтобы назвать этот тип проблемы. Каков предпочтительный способ сделать это?

Мне не нравится SQL-решение по одной причине. Вы просите механизм базы данных присоединиться к строке, а затем используете PHP для его разделения. Это похоже на повторную работу. Другая сторона – это то, что, если metavalue содержит символ seperator.

Мой код просто демонстрирует, как вы можете легко группировать данные с 2-мерным массивом в PHP, и до кодера решать, какой метод извлекает данные из базы данных либо унаследованные mysql_ *, либо PDO.

 // get result $result = mysql_query("SELECT autonum,id,name,metavalue FROM table"); // loop through each row while ($row = mysql_fetch_assoc($result)) { $table[$row['name']][] = $row['metavalue']; } // print it out print_r($table); 

Нет необходимости временно хранить весь набор результатов в массиве. Вы можете выполнить итерацию по первым данным сортировки по тому полю, в котором вы хотите группировать свои данные и отслеживать состояние.

Тем не менее, есть две причины, почему я предпочитаю этот стиль, и это личное предпочтение (это не обязательное решение):

  1. Обычно каждый хочет разделить логику и презентацию. Хранение данных в массиве можно легко передать в механизм шаблонов. Я предпочитаю использовать PHP-шаблон, потому что его легко реализовать, и он работает на скорости света.

  2. Я бы предпочел обменять небольшую скорость работы и использование памяти для удобства чтения и обслуживания. Алгоритм короткий и простой для понимания. Если производительность – ваш главный приоритет, вы всегда можете использовать кеш, например, хранить в файле, хранить в memcached или хранить вычисленный результат в базе данных.

Правда, мы группируем данные с разных сторон, но вы предполагаете, что metavalue не содержит разделителя (который в вашем коде равен |). Его можно легко зафиксировать, выбрав разделитель, который почти невозможно использовать при metavalue . Во всяком случае, ваше решение будет отлично работать практически в каждом случае, потому что метаобъявление, показанное metavalue , никогда не будет содержать символ |.

Проблема не в коде php / java / c #, а в конце концов SQL.

 SELECT ID AS id, Autonum AS num, Name AS name, GROUP_CONCAT ( ORDER BY MetaValue DESC SEPARATOR '|' ) AS list FROM Things GROUP BY Things.ID 

Таким образом, вы получите список значений name , и все связанные с ним мета-значения для этого элемента, вы просто разделили list на '|' ,

В PHP вы делаете это с помощью String.Split() explode() , в C # с String.Split() и, я полагаю, вы сможете найти что-то подобное для любого языка.

Поколение HTML становится довольно тривиальным.

Вот пример PHP (с использованием API соединений PDO):

 $statement = $pdo->query( $sql ); if ( $statement->execute() ) { $data = $statement->fetchAll(PDO::FETCH_ASSOC); foeach ( $data as $row ) { echo '<tr><td>' , $row['name'] , '</td>'; echo '<td><ul><li>' , str_replace( '|', '</li><li>' , $row['list']) ; echo '</li></ul></td></tr>'; } } 

Это приведет к созданию содержимого <tbody> из вашего примера.

Пожалуйста, ознакомьтесь с вашими данными, они отсортированы и заказаны:

 Autonum ID Name MetaValue 1 1 Rose Drinker 2 1 Rose Nice Person 3 1 Rose Runner 4 2 Gary Player 5 2 Gary Funny 

Итак, что вы можете сделать, так это решить эту проблему:

  • Каждый раз, когда ID / Name изменяется, у вас есть новая строка таблицы.
  • Каждый раз, когда MetaValue изменяется, у вас есть новая запись списка.

Все, что вам нужно сделать, – это взглянуть на следующий элемент, чтобы увидеть изменение. Это означает, что вы заранее итерации (предварительно рассчитанная итерация) или вы выполняете кэшированную итерацию (например, с CachingIterator в PHP).

Если вы затем отслеживаете состояние, вы можете указать для каждой записи:

  • Перед этим нужно открыть строку таблицы.
  • После этого строка таблицы должна быть закрыта.

На самом деле это даже намного проще, здесь пример итерации строк на основе массива ( Demo ):

 $rows = array( array(1, 1, 'Rose', 'Drinker'), array(2, 1, 'Rose', 'Nice Person'), array(3, 1, 'Rose', 'Runner'), array(4, 2, 'Gary', 'Player'), array(5, 2, 'Gary', 'Funny'), ); echo "<table>\n <thead>\n <th>Name</th>\n <th>MetaValue1</th>\n </thead>\n <tbody>\n"; $events = new Events(); $last = array(2 => null); foreach($rows as $row) { if ($last[2] !== $row[2]) { $events->newRow($row[2]); } $events->newItem($row[3]); $last = $row; } $events->closeRow(); echo " </tbody>\n</table>"; 

Не так много кода для решения вашей проблемы, потому что сама проблема – всего лишь немного состояния внутри данных.

То, что было описано как события здесь, можно инвертировать, создав свой собственный итератор, который можно передать в систему шаблонов, которая поддерживает итераторы, например, ветку или усы. Полный приведенный пример этого показан в предыдущих моих ответах и ​​внутри сообщения в блоге:

  • Вставка вложенной модели набора в скрытые «закрытые» поддеревья
  • Как преобразовать серию отношений родитель-ребенок в иерархическое дерево?
  • Некоторые PHP Итератор Fun

По сравнению с приведенным выше примером он может выглядеть не так просто, однако, надеюсь, он дает достаточно кода, чтобы вы решили, куда идете. Это довольно расширяемо.

Вот решение, оно похоже на @ invisal в том, как мы создаем массив на основе свойства таблицы для группировки результатов вместе.

 <?php $mysqli = new mysqli("localhost", "uname", "pword", "wordpress"); if ($mysqli->connect_errno) { die("Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error); } $res = $mysqli->query("SELECT * FROM wp_postmeta"); // array that will hold the final results $results = array(); // while we have more rows while ($row = $res->fetch_assoc()) { // check to see if we already have this ID if (!array_key_exists($row['ID'], $results)) { // if not create a new index for it in $results, which holds an array $results[$row['ID']] = array(); } // create new stdClass object that holds the row data $obj = new stdClass; $obj->Name = $row['Name']; $obj->MetaValue = $row['MetaValue']; // append this data to our results array based on the ID $results[$row['ID']][] = $obj; } ?> <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> <?php foreach($results as $id => $values): ?> <tr> <td style="vertical-align: top"><?php echo $values[0]->Name ?></td> <td> <ul> <?php foreach($values as $value): ?> <li><?php echo $value->MetaValue ?></li> <?php endforeach; ?> </ul> </td> </tr> <?php endforeach; ?> </tbody> </table> 

Это, по-видимому, дает желаемые результаты. Надеюсь, это поможет.

Я бы решил это по-другому. Прямо сейчас у вас проблемы, потому что схема базы данных не нормализована. Вместо этого я начну с изменения схемы. Прямо сейчас у вас есть это:

 CREATE TABLE foo { `autonum` INT(12) NOT NULL AUTO_INCREMENT, `id` INT(12) NOT NULL, `name` VARCHAR(255) NOT NULL, `metaValue` VARCHAR(255) NOT NULL, PRIMARY KEY(autonum) ) 

Вместо этого я разделил бы его на две таблицы:

 CREATE TABLE user { `id` INT(12) NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL, PRIMARY KEY(id) ) CREATE TABLE user_meta { `user_id` INT(12) NOT NULL, `value` VARCHAR(255) NOT NULL ) 

Игнорируя тот факт, что это выглядит как таблица EAV (чего нет, мета просто неправильно названа из-за именования OP), должно быть ясно, как это проще. Итак, давайте посмотрим на вашу запись и создадим таблицу:

 #User id | name 1 | Rose 2 | Gary #User Meta user_id | value 1 | drinker 1 | Nice Person 1 | Runner 2 | Player 2 | Funny 

Теперь, когда у нас есть данные, давайте посмотрим, как мы его извлечем. Предположим, что мы всегда хотим распечатать всех пользователей (ограничение было бы простым, но для ясности мы можем просто сделать все на данный момент). Итак, я бы установил простой класс вокруг каждой таблицы, кэшируя данные в один запрос.

 class User implements IteratorAggregate { protected $data = array(); public function __construct() { $query = mysql_query("SELECT id, name FROM user"); while ($row = mysql_fetch_assoc($query)) { $this->data[$row['id']] => $row['name']; } } public function getIterator() { return new ArrayObject($this->data); } } class User_Meta { protected $data = array(); public function __construct() { $query = mysql_query("SELECT user_id, value FROM user_meta"); while ($row = mysql_fetch_assoc($query)) { if (empty($this->data[$row['user_id']])) { $this->data[$row['user_id']] = array(); } $this->data[$row['user_id']][] = $row['value']; } } public function getDataForUserId($id) { return empty($this->data[$id]) ? array() : $this->data[$id]; } } 

Теперь построение вывода становится тривиальным:

 $users = new User; $meta = new User_Meta; echo "<table> <thead> <th>Name</th> <th>MetaValue1</th> </thead> <tbody>"; foreach ($users as $id => $name) { echo "<tr> <td>".htmlspecialchars($name)."</td> <td> <ul>"; foreach ($meta->getDataForUserId($id) as $metaValue) { echo "<li>" . htmlspecialchars($metaValue) . "</li>"; } echo " </ul> </td> </tr>"; } echo "</tbody></table>"; 

Теперь, со всем сказанным, я бы лично реализовал его немного по-другому, используя для этого первоклассные бизнес-объекты и карты данных.

И я совершил несколько уголовных преступлений здесь, но я сделал это для простоты. Вот что я сделал бы по-другому:

  1. Не выполнять логику в конструкторе объекта. Вместо этого я бы ввел его или использовал фабрику для предварительного заполнения данных.
  2. Проанализируйте концепцию отношений на отдельные бизнес-объекты.
  3. Отделите логику выталкивания от логики отображения.
  4. Назовите вещи лучше (metavalue не является хорошим именем для поля).

Но это действительно зависит от точных требований приложения. Нет ни одного (или двух) правильных способов сделать это. У каждого из возможных способов есть плюсы и минусы. Научитесь взвешивать компромиссы, чтобы вы могли выбрать правильный для своих требований …

Вы спросили, как назвать этот тип проблемы: его названная группировка. Одна из возможностей заключается в том, чтобы разрешить ее в SQL с помощью предложения GROUP BY (но разные механизмы SQL имеют различную поддержку для них, особенно когда речь идет о более сложных агрегациях данных, которые нужно сгруппировать).

В C # базовое решение является однострочным с помощью LINQ, просто используйте GroupBy расширения GroupBy :

 var data = new [] { new { RowId = 1, EmpId = 1, Name = "Rose", Value = "Drinker" }, new { RowId = 2, EmpId = 1, Name = "Rose", Value = "Runner" }, new { RowId = 3, EmpId = 2, Name = "Gary", Value = "Player" }, }; var grouping = data.GroupBy(row => row.Name); var sb = new StringBuilder(); foreach (var grp in grouping) { sb.AppendFormat("<tr><td>{0}</td>\n", grp.Key); sb.AppendLine(" <td><ul>"); foreach (var element in grp) { sb.AppendFormat(" <li>{0}</li>\n", element.Value); } sb.AppendLine(" </ul></td>\n</tr>"); } Console.WriteLine(sb.ToString()); 

Основная идея GroupBy – это просто какой-то ассоциативный массив или словарь, с атрибутами для группировки по ключу и списком всех строк, связанных с этим ключом. Например (используя переменные данные с указанным выше определением):

 var map = new Dictionary<Tuple<int, string>, List<object>>(); foreach (var row in data) { var key = Tuple.Create(row.EmpId, row.Name); if (!map.ContainsKey(key)) map.Add(key, new List<object>()); map[key].Add(row); } 

Вышеприведенный фрагмент концептуально совпадает с ответами на основе PHP от drew010 и invisal (они используют двумерный массив PHP, который концептуально такой же, как и выше Dictionary of Lists). Я попытался сделать шаги максимально ясными и не использовать слишком много специальных функций C # (например, в реальной программе вы должны использовать собственный класс для сложных ключей вместо простого Tuple), поэтому выше фрагмент должен быть легко переносимым на Java и другие языки.

Как вы можете видеть по всем примерам здесь, после группировки данных генерация HTML всегда одна и та же (две вложенные петли).

Однажды у меня была подобная таблица, и я закончил использование некоторого SQL для возврата данных в виде отдельных строк. Я изменил свои объекты, чтобы использовать ваш sql, и написал простую функцию, которая выводит ваши данные в таблицу, следуя формату, который вы использовали в своем вопросе.

Основная функция агрегирования запросов, которую я использовал, – это MySQL GROUP_CONCAT. Это похоже на функцию implode() . Разделитель по умолчанию – это запятая, поэтому, если у вас есть запятые в ваших метаданных, вы можете изменить их на другой маркер, добавив SEPARATOR 'yourSeparator' в скобки после имени столбца. Вы также можете использовать distinct infront имени столбца, чтобы выбрать только отдельные строки в group_concat.

Я поставил раздел заголовка таблицы внутри инструкции try , так что, если у вас нет никаких результатов, вы не будете отображать полугенерированную таблицу.

 <?php class myUser { public $ID; public $name; public $metas; } class myTables { public $SQL=" select ID, name, GROUP_CONCAT(metavalue) as metas FROM table1 GROUP BY ID, name;"; public function outputTable() { $hostname="mysql:host=localhost;dbname=test"; $username="testdb"; $password="testdbpassword"; try{ echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; $dbh = new PDO($hostname, $username, $password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); foreach($stmt as $myUser) { echo " <tr><td>".$myUser->name."</td><td><ul>"; $metas=explode(",", $myUser->metas); for($i=0;$i<count($metas);$i++) { echo "<li>".$metas[$i]."</li>"; } echo "</ul></td></tr>"; } unset($obj); unset($stmt); unset($dbh); echo " </tbody> </table> "; } catch(PDOException $e){ echo 'Error : '.$e->getMessage(); exit(); } } } $myPage= new myTables(); $myPage->outputTable(); ?> с <?php class myUser { public $ID; public $name; public $metas; } class myTables { public $SQL=" select ID, name, GROUP_CONCAT(metavalue) as metas FROM table1 GROUP BY ID, name;"; public function outputTable() { $hostname="mysql:host=localhost;dbname=test"; $username="testdb"; $password="testdbpassword"; try{ echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; $dbh = new PDO($hostname, $username, $password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); foreach($stmt as $myUser) { echo " <tr><td>".$myUser->name."</td><td><ul>"; $metas=explode(",", $myUser->metas); for($i=0;$i<count($metas);$i++) { echo "<li>".$metas[$i]."</li>"; } echo "</ul></td></tr>"; } unset($obj); unset($stmt); unset($dbh); echo " </tbody> </table> "; } catch(PDOException $e){ echo 'Error : '.$e->getMessage(); exit(); } } } $myPage= new myTables(); $myPage->outputTable(); ?> с <?php class myUser { public $ID; public $name; public $metas; } class myTables { public $SQL=" select ID, name, GROUP_CONCAT(metavalue) as metas FROM table1 GROUP BY ID, name;"; public function outputTable() { $hostname="mysql:host=localhost;dbname=test"; $username="testdb"; $password="testdbpassword"; try{ echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; $dbh = new PDO($hostname, $username, $password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); foreach($stmt as $myUser) { echo " <tr><td>".$myUser->name."</td><td><ul>"; $metas=explode(",", $myUser->metas); for($i=0;$i<count($metas);$i++) { echo "<li>".$metas[$i]."</li>"; } echo "</ul></td></tr>"; } unset($obj); unset($stmt); unset($dbh); echo " </tbody> </table> "; } catch(PDOException $e){ echo 'Error : '.$e->getMessage(); exit(); } } } $myPage= new myTables(); $myPage->outputTable(); ?> с <?php class myUser { public $ID; public $name; public $metas; } class myTables { public $SQL=" select ID, name, GROUP_CONCAT(metavalue) as metas FROM table1 GROUP BY ID, name;"; public function outputTable() { $hostname="mysql:host=localhost;dbname=test"; $username="testdb"; $password="testdbpassword"; try{ echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; $dbh = new PDO($hostname, $username, $password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); foreach($stmt as $myUser) { echo " <tr><td>".$myUser->name."</td><td><ul>"; $metas=explode(",", $myUser->metas); for($i=0;$i<count($metas);$i++) { echo "<li>".$metas[$i]."</li>"; } echo "</ul></td></tr>"; } unset($obj); unset($stmt); unset($dbh); echo " </tbody> </table> "; } catch(PDOException $e){ echo 'Error : '.$e->getMessage(); exit(); } } } $myPage= new myTables(); $myPage->outputTable(); ?> 

Пример:

 <table> <thead> <th>Name</th> <th>MetaValue1</th> </thead> <tbody> <tr><td>Rose</td><td><ul><li>Drinker</li><li>Nice Person</li><li>Runner</li></ul></td></tr> <tr><td>Gary</td><td><ul><li>Player</li><li>Funny</li></ul></td></tr> </tbody> </table> 

Я вырезал Autonum из вашего запроса, так как в противном случае он искажал агрегированную функцию. Если вам все же нужно, вам придется собрать его аналогичным образом. Функция group_concat пропускает group_concat поля, поэтому вам нужно будет привести их хитро, иначе ваши идентификаторы autonum не будут соответствовать вашим результатам. Я сделал это здесь с простым coalesce() внутри функции group_concat .

Чтобы разместить эти дополнительные биты, я немного переписал объект. Это должно делать все, что вам нужно, я оставил свои отладочные заметки, в которых задана личная переменная isDebug которую я установил в false, но установив ее в true, вы получите дополнительную информацию по мере прохождения объекта через функции. Это поможет вам с вашей отладкой, поскольку я предполагаю, что исходные данные на самом деле намного сложнее.

 <?php class myUser { public $ID; public $name; public $metaDesc; public $metaNum; public $metaDescs; public $metaNums; } // Basic User stored here. One Object per Row is created. class myTables { private $isDebug=false; // Change this to true to get all validation messages; private $hostname="mysql:host=localhost;dbname=test"; private $username="testdb"; private $password="testdbpassword"; private $myUsers=array(); private $curUser = myUser; private $userCount=0; private $SQL=" select ID, name, GROUP_CONCAT(coalesce(metavalue,'null')) as metaDesc, group_concat(autonum) as metaNum FROM table1 GROUP BY ID, name;"; public function getuserData() { $dbh = new PDO($this->hostname, $this->username, $this->password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); $userCount=0; foreach($stmt as $myUser) { $this->myUsers[$userCount]=new myUser; $this->myUsers[$userCount]->ID=$myUser->ID; $this->myUsers[$userCount]->name=$myUser->name; $this->myUsers[$userCount]->metaDesc=$myUser->metaDesc; $this->myUsers[$userCount]->metaNum=$myUser->metaNum; $userCount++; } $this->userCount=$userCount; if($this->isDebug){echo "There are ".$this->userCount." users found.<br>";} unset($obj); unset($stmt); unset($dbh); } // Pulls the data from the database and populates the this->object. public function outputTable() { echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; for($i=0; $i<$this->userCount; $i++) { if($this->isDebug){echo "Running main cycle. There are ".$this->userCount." elements.";} $this->myUsers[$i]->metaDescs=explode(',', $this->myUsers[$i]->metaDesc); $this->myUsers[$i]->metaNums=explode(',', $this->myUsers[$i]->metaNum); if($this->isDebug){echo "This user has ".(count($this->myUsers[$i]->metaDescs))." segments<br>";} if($this->isDebug){echo "My first segment is ".($this->myUsers[$i]->metaDesc)."<br>";} echo "<tr><td>".$this->myUsers[$i]->name."</td><td><ul>"; for($j=0;$j<count($this->myUsers[$i]->metaDescs);$j++) { echo "<li>ID: ".$this->myUsers[$i]->metaNums[$j]." - ".$this->myUsers[$i]->metaDescs[$j]."</li>"; } echo "</ul></td></tr>"; } echo " </tbody> </table> "; } // Outputs the data held in the object into the table as required. } $myPage= new myTables(); $myPage->getUserData(); $myPage->outputTable(); ?> с <?php class myUser { public $ID; public $name; public $metaDesc; public $metaNum; public $metaDescs; public $metaNums; } // Basic User stored here. One Object per Row is created. class myTables { private $isDebug=false; // Change this to true to get all validation messages; private $hostname="mysql:host=localhost;dbname=test"; private $username="testdb"; private $password="testdbpassword"; private $myUsers=array(); private $curUser = myUser; private $userCount=0; private $SQL=" select ID, name, GROUP_CONCAT(coalesce(metavalue,'null')) as metaDesc, group_concat(autonum) as metaNum FROM table1 GROUP BY ID, name;"; public function getuserData() { $dbh = new PDO($this->hostname, $this->username, $this->password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); $userCount=0; foreach($stmt as $myUser) { $this->myUsers[$userCount]=new myUser; $this->myUsers[$userCount]->ID=$myUser->ID; $this->myUsers[$userCount]->name=$myUser->name; $this->myUsers[$userCount]->metaDesc=$myUser->metaDesc; $this->myUsers[$userCount]->metaNum=$myUser->metaNum; $userCount++; } $this->userCount=$userCount; if($this->isDebug){echo "There are ".$this->userCount." users found.<br>";} unset($obj); unset($stmt); unset($dbh); } // Pulls the data from the database and populates the this->object. public function outputTable() { echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; for($i=0; $i<$this->userCount; $i++) { if($this->isDebug){echo "Running main cycle. There are ".$this->userCount." elements.";} $this->myUsers[$i]->metaDescs=explode(',', $this->myUsers[$i]->metaDesc); $this->myUsers[$i]->metaNums=explode(',', $this->myUsers[$i]->metaNum); if($this->isDebug){echo "This user has ".(count($this->myUsers[$i]->metaDescs))." segments<br>";} if($this->isDebug){echo "My first segment is ".($this->myUsers[$i]->metaDesc)."<br>";} echo "<tr><td>".$this->myUsers[$i]->name."</td><td><ul>"; for($j=0;$j<count($this->myUsers[$i]->metaDescs);$j++) { echo "<li>ID: ".$this->myUsers[$i]->metaNums[$j]." - ".$this->myUsers[$i]->metaDescs[$j]."</li>"; } echo "</ul></td></tr>"; } echo " </tbody> </table> "; } // Outputs the data held in the object into the table as required. } $myPage= new myTables(); $myPage->getUserData(); $myPage->outputTable(); ?> с <?php class myUser { public $ID; public $name; public $metaDesc; public $metaNum; public $metaDescs; public $metaNums; } // Basic User stored here. One Object per Row is created. class myTables { private $isDebug=false; // Change this to true to get all validation messages; private $hostname="mysql:host=localhost;dbname=test"; private $username="testdb"; private $password="testdbpassword"; private $myUsers=array(); private $curUser = myUser; private $userCount=0; private $SQL=" select ID, name, GROUP_CONCAT(coalesce(metavalue,'null')) as metaDesc, group_concat(autonum) as metaNum FROM table1 GROUP BY ID, name;"; public function getuserData() { $dbh = new PDO($this->hostname, $this->username, $this->password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); $userCount=0; foreach($stmt as $myUser) { $this->myUsers[$userCount]=new myUser; $this->myUsers[$userCount]->ID=$myUser->ID; $this->myUsers[$userCount]->name=$myUser->name; $this->myUsers[$userCount]->metaDesc=$myUser->metaDesc; $this->myUsers[$userCount]->metaNum=$myUser->metaNum; $userCount++; } $this->userCount=$userCount; if($this->isDebug){echo "There are ".$this->userCount." users found.<br>";} unset($obj); unset($stmt); unset($dbh); } // Pulls the data from the database and populates the this->object. public function outputTable() { echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; for($i=0; $i<$this->userCount; $i++) { if($this->isDebug){echo "Running main cycle. There are ".$this->userCount." elements.";} $this->myUsers[$i]->metaDescs=explode(',', $this->myUsers[$i]->metaDesc); $this->myUsers[$i]->metaNums=explode(',', $this->myUsers[$i]->metaNum); if($this->isDebug){echo "This user has ".(count($this->myUsers[$i]->metaDescs))." segments<br>";} if($this->isDebug){echo "My first segment is ".($this->myUsers[$i]->metaDesc)."<br>";} echo "<tr><td>".$this->myUsers[$i]->name."</td><td><ul>"; for($j=0;$j<count($this->myUsers[$i]->metaDescs);$j++) { echo "<li>ID: ".$this->myUsers[$i]->metaNums[$j]." - ".$this->myUsers[$i]->metaDescs[$j]."</li>"; } echo "</ul></td></tr>"; } echo " </tbody> </table> "; } // Outputs the data held in the object into the table as required. } $myPage= new myTables(); $myPage->getUserData(); $myPage->outputTable(); ?> с <?php class myUser { public $ID; public $name; public $metaDesc; public $metaNum; public $metaDescs; public $metaNums; } // Basic User stored here. One Object per Row is created. class myTables { private $isDebug=false; // Change this to true to get all validation messages; private $hostname="mysql:host=localhost;dbname=test"; private $username="testdb"; private $password="testdbpassword"; private $myUsers=array(); private $curUser = myUser; private $userCount=0; private $SQL=" select ID, name, GROUP_CONCAT(coalesce(metavalue,'null')) as metaDesc, group_concat(autonum) as metaNum FROM table1 GROUP BY ID, name;"; public function getuserData() { $dbh = new PDO($this->hostname, $this->username, $this->password); $stmt = $dbh->query($this->SQL); $obj = $stmt->setFetchMode(PDO::FETCH_INTO, new myUser); $userCount=0; foreach($stmt as $myUser) { $this->myUsers[$userCount]=new myUser; $this->myUsers[$userCount]->ID=$myUser->ID; $this->myUsers[$userCount]->name=$myUser->name; $this->myUsers[$userCount]->metaDesc=$myUser->metaDesc; $this->myUsers[$userCount]->metaNum=$myUser->metaNum; $userCount++; } $this->userCount=$userCount; if($this->isDebug){echo "There are ".$this->userCount." users found.<br>";} unset($obj); unset($stmt); unset($dbh); } // Pulls the data from the database and populates the this->object. public function outputTable() { echo " <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> "; for($i=0; $i<$this->userCount; $i++) { if($this->isDebug){echo "Running main cycle. There are ".$this->userCount." elements.";} $this->myUsers[$i]->metaDescs=explode(',', $this->myUsers[$i]->metaDesc); $this->myUsers[$i]->metaNums=explode(',', $this->myUsers[$i]->metaNum); if($this->isDebug){echo "This user has ".(count($this->myUsers[$i]->metaDescs))." segments<br>";} if($this->isDebug){echo "My first segment is ".($this->myUsers[$i]->metaDesc)."<br>";} echo "<tr><td>".$this->myUsers[$i]->name."</td><td><ul>"; for($j=0;$j<count($this->myUsers[$i]->metaDescs);$j++) { echo "<li>ID: ".$this->myUsers[$i]->metaNums[$j]." - ".$this->myUsers[$i]->metaDescs[$j]."</li>"; } echo "</ul></td></tr>"; } echo " </tbody> </table> "; } // Outputs the data held in the object into the table as required. } $myPage= new myTables(); $myPage->getUserData(); $myPage->outputTable(); ?> 

Вывод теперь выглядит следующим образом:

 <table> <thead> <th>Name</th> <th>MetaValue</th> </thead> <tbody> <tr><td>Rose</td><td><ul><li>ID: 1 - Drinker</li><li>ID: 2 - Nice Person</li><li>ID: 3 - Runner</li></ul></td></tr> <tr><td>Gary</td><td><ul><li>ID: 4 - Player</li><li>ID: 5 - Funny</li><li>ID: 6 - null</li><li>ID: 7 - Smelly</li></ul></td></tr> </tbody> </table> Name MetaValue Rose ID: 1 - Drinker ID: 2 - Nice Person ID: 3 - Runner Gary ID: 4 - Player ID: 5 - Funny ID: 6 - null ID: 7 - Smelly 
 This would print exactly what you need within the <tbody> tags : <?php $result = mysql_query("SELECT name, metavalue FROM table"); $previous_name = ""; //Variable to keep track of previous name value in array() print '<table><thead><th>Name</th><th>Metavalue</th></thead><tbody>'; //print the static table header while ($row = mysql_fetch_array($result)) { $name = $row['name']; $meta_value = $row['metavalue']; if (($previous_name != $name) && ($previous_name !="")) { //If $name has changed, close tags, reset $previous_name print '</ul></td></tr>'; $previous_name = ""; } if ($previous_name == "") { //New value of $name, open tags, set $name to $previous_name print '<tr><td>'.$name.'</td><td><ul>'; $previous_name = $name; } if ($previous_name == $name) { //print contents of $meta_value if $name is the same print '<li>'.$meta_value.'</li>'; } } //Once there are no more values in the array, close the static table header print '</tbody></table>'; //close the static table header ?> 

Вот мой второй подход к проблеме, как и в первом случае , я все еще поклонник решения этой проблемы с выходом, например:

 <table> <?php foreach($users as $name => $columns) : ?> <tr> <td> <?php echo htmlspecialchars($name); ?> </td> <td> <ul> <?php foreach($columns as $column) : ?> <li><?php echo htmlspecialchars($column); ?></li> <?php endforeach ?> </ul> </td> </tr> <?php endforeach ?> </table> 

Где, когда набор результатов базы данных здесь моделируется с помощью структуры массива, но типичный итератор:

 $rows = array( array(1, 1, 'Rose', 'Drinker'), array(2, 1, 'Rose', 'Nice Person'), array(3, 1, 'Rose', 'Runner'), array(4, 2, 'Gary', 'Player'), array(5, 2, 'Gary', 'Funny'), ); $result = new ArrayIterator($rows); $users = new Users($result); 

Это демонстрирует, что это единственный запрос, никаких подзапросов, никакой перегруппировки не требуется (как указано в первом вопросе), делая это на основе его порядка.

Только код для класса « Users » отсутствует:

 class Users extends IteratorIterator { private $last; public function __construct($it) { parent::__construct(new NoRewindIterator($it)); } public function current() { $current = $this->getInnerIterator()->current(); $this->last = $current[2]; return new Columns($this->getInnerIterator(), $this->last); } public function key() { return $this->last; } public function next() { $current = $this->getInnerIterator()->current(); if ($this->last === $current[2] || $current === NULL) { parent::next(); } } } class Columns extends IteratorIterator { private $name; public function __construct($it, $name) { $this->name = $name; parent::__construct($it); } public function valid() { $current = parent::current(); return parent::valid() && $current[2] === $this->name; } public function current() { $current = parent::current(); return $current[3]; } } 

Полная демонстрация в кодедепе – я должен признать, что это содержит немного итераторной черной магии.

Я рекомендую вам создать функцию в базе данных

 create function get_meta(@autonum int) returns varchar(500) as declare @meta varchar(500)='<ul>'; select @meta=@meta+'<li>'+MetaValue+'</li>' from mytable where Autonum=@autonum return @meta+'</ul>'; 

и использовать чистую инструкцию на стороне PHP, как это.

 select id,autonum,name, get_meta(autonum) as meta_output from mytable 

то вам нужно сделать

 echo $row["meta_output"]; 

как и любой другой столбец. Это то, что я буду использовать.

Это довольно близко к некоторым другим ответам, но я думаю, что немного чище и проще следовать.

Я создаю трехмерный массив перед любым выходом для удобства использования. Вы можете сделать все сразу, но я считаю, что более чистый код, как правило, отделяет его (чтение из базы данных отличается от вывода, например, так что держите их отдельно). Таким образом, у вас также есть удобный массив, если вам когда-либо понадобится ссылаться на 15-й метаанализ человека 33 $resultArr[33]["meta"][15] .

Я также добавил имена классов и идентификатор для каждой строки результатов для упрощения стилизации CSS.

 <?php // DB connect // // Get the DB Data $result = mysql_query("SELECT Autonum, ID, Name, MetaValue FROM table GROUP BY ID"); // Set Start Values $resultArr = array(); $curRow = -1; $curMeta = -1; // Loop Through the Rows while ($row = mysql_fetch_assoc($result)) { // Start of a new ID, Set the name and Create the MetaValue Array if ($curRow != $row["ID"]){ $curRow = $row["ID"]; $resultArr[$curRow]["name"] = $row["Name"]; $resultArr[$curRow]["meta"] = array(); $curMeta = 0; } // Add the MetaValue $resultArr[$curRow]["meta"][$curMeta] = $row["MetaValue"}; $curMeta++; } /* Array looks like: $resultArr[1]["name"] = "Rose"; $resultArr[1]["meta"][0] = "Drinker"; $resultArr[1]["meta"][1] = "Nice Person"; $resultArr[1]["meta"][2] = "Runner"; $resultArr[2]["name"] = "Gary"; $resultArr[2]["meta"][0] = "Player"; $resultArr[2]["meta"][1] = "Funny"; */ // Start the Table $out = "<table>"; $out .= "\n<thead>"; $out .= "\n<th>Name</th>"; $out .= "\n<th>MetaValue1</th>"; $out .= "\n</thead>"; $out .= "\n<tbody>"; // Add the Rows foreach($resultArr as $id => $col){ $out .= "\n\n<tr id='result_" . $id . "'>"; $out .= "\n<td class='rowName'>" . $col["name"] . "</td>"; $out .= "\n<td class='rowMeta'>"; $out .= "\n<ul>"; foreach($col["meta"] as $meta){ $out .= "\n<li>".$meta."</li>"; } $out .= "\n</ul>"; $out .= "\n</td>"; $out .= "\n</tr>"; } // Close the Table $out .= "\n</tbody>"; $out .= "\n</table>"; // Print It Out Where You Need It echo $out; ?> 

Iam здесь не группируется на стороне MySql или PHP, то есть не использует память php для группировки или использования функции mysql. Просто контроль над печатью.

Вы можете увидеть рабочий пример приведенного ниже кода.

 <?php $result = mysql_query("SELECT name,metavalue FROM table"); $count = mysql_num_rows($result); ?> <table> <thead> <th>Name</th> <th>MetaValue1</th> </thead> <tbody> <?php $i = 0; // loop through each row $start = 1; while ($row = mysql_fetch_assoc($result) { if ($name != $row['name']) {// row with new name if ($start) { $start = 0; echo "<tr>"; echo "<td>" . $row['name'] . "</td> <td><ul><li>" . $row['metadata'] . "</li>"; } else { $start = 1; echo "</ul></td>"; echo "</tr><tr>"; echo "<td>" . $row['name'] . "</td> <td><ul><li>" . $row['metadata'] . "</li>"; } } else {//row with the same name echo "<li>" . $row['metadata'] . "</li>"; if ($i == $count - 1) { echo "</ul></td></tr>"; } } ?> <?php $name = $row['name']; $i++; } ?> </tbody> </table> 

Вот код для вас. Чтобы избежать конфликта с вашим кодом, я не использую никакой концепции объекта класса, поэтому код написан простым способом. Это выглядит просто, но он не вызывает сомнений. Обратите внимание, что код размещается старшим программистом, работающим на php в течение последних полутора лет. поэтому не против простой моды кода


  <table> <thead> <th>Name</th> <th>MetaValue1</th> </thead> <tbody> <?php mysql_select_db("dbName", $con); $result = mysql_query("SELECT DISTINCT Name,ID FROM tableName"); while($row = mysql_fetch_array($result)) { $userName = $row['Name']; echo $userId = $row['ID']; ?> <tr> <td><?=$userName?> <?=$userId?></td> <td> <ul> <?php $resultNew = mysql_query("SELECT * FROM tableName WHERE ID =$userId"); while($row = mysql_fetch_array($resultNew)) { $mValue = $row['MetaValue']; ?> <li><?=$mValue?></li> <?php } ?> </ul> </td> </tr> <?php } ?> </tbody> </table> 
 <?php /** * Displaying a table in PHP with repeated columns * @link http://stackoverflow.com/a/11616884/367456 */ class Events { private $row = 0; public function newRow($name) { $this->closeRow(); echo " <tr><td>$name</td><ul>"; $this->row++; } public function closeRow() { if (!$this->row) return; echo "</ul></tr>\n"; $this->row = 0; } public function newItem($name) { echo "<li>$name</li>"; } } $rows = array( array(1, 1, 'Rose', 'Drinker'), array(2, 1, 'Rose', 'Nice Person'), array(3, 1, 'Rose', 'Runner'), array(4, 2, 'Gary', 'Player'), array(5, 2, 'Gary', 'Funny'), ); echo "<table>\n <thead>\n <th>Name</th>\n <th>MetaValue1</th>\n </thead>\n <tbody>\n"; $events = new Events(); $last = null; foreach ($rows as $row) { if (@$last[2] !== $row[2]) { $events->newRow($row[2]); } $events->newItem($row[3]); $last = $row; } $events->closeRow(); echo " </tbody>\n</table>";