I have the following recordset:
Date |Role |Name
=============================
01/02/14 |Musician |Bob
01/02/14 |Leader |Jerry
01/02/14 |Singer
<?php
echo '<div align=left>';
//initialize three arrays to hold the values
$x_role = array();
$y_date = array();
$val_name = array();
//store values from the database into three separate arrays NB theRole,
// theDate and theName are the column names from the database
foreach($data as $recordset)
{
$x_role[] = $recordset->theRole;
$y_date[] = $recordset->theDate;
$val_name[$recordset->theRole][$recordset->theDate] = $recordset->theName;
}
//get unique values for row and column and sort them if necessary
$unique_x_role = array_unique($x_role);
//asort($unique_x_role);
$unique_y_date = array_unique($y_date);
//asort($unique_y_date);
// prints table - OUTPUT
echo '<p>';
echo '<hr size=10 color=#6f9171 width=100% align=left>';
echo '<p>';
echo '<div align=left>';
echo '<table border=3 >';
echo '<tr>';
echo '<td>ROLE</td>';//prints 'ROLE" in upper left corner of table
foreach($unique_y_date as $theDate)
{
echo '<td>'.$theDate.'</td>';//prints column headings
}
echo '</tr>';//closes column headings
foreach($unique_x_role as $theRole)
{
echo '<tr>';
echo '<td>'.$theRole.'</td>'; //prints row title
foreach($unique_y_date as $theDate)
{
if(isset($val_name[$theRole][$theDate]))// checks if value exists
{
$v = $val_name[$theRole][$theDate];
echo '<td>'.$v.'</td>'; //prints name because it exists
}
else
{
echo '<td> - </td>';// prints a dash if no name exists
}
}//foreach($y_date as $theDate)
echo '</tr>';
}//foreach($unique_x_role as $theRole)
echo '</table>';
echo '</div>';
?>
...this WAS fun. I decided to replicate the desired output as raw text rather than html as a personal challenge.
Essentially, the data is formed into reference arrays, then imploded or iterated print the desired crosstab layout. The $columnWidth
variable allows the whole table to be easily re-sized. str_pad()
is used for center alignment. The null coalescing operator (??
) is used to fallback to -
when a respective value does not exist.
Code: (Demo)
//It is assumed that the sql is perfectly capable of sorting by date ASC, role ASC, name ASC
$resultSet = [
['date' => '01/02/14', 'role' => 'Leader', 'name' => 'Jerry'],
['date' => '01/02/14', 'role' => 'Musician', 'name' => 'Bob'],
['date' => '01/02/14', 'role' => 'Singer', 'name' => 'Carol'],
['date' => '08/02/14', 'role' => 'Leader', 'name' => 'Baz'],
['date' => '08/02/14', 'role' => 'Leader', 'name' => 'Gaz'],
['date' => '08/02/14', 'role' => 'Leader', 'name' => 'Haz'],
['date' => '08/02/14', 'role' => 'Musician', 'name' => 'Charles'],
['date' => '08/02/14', 'role' => 'Singer', 'name' => 'Norman'],
['date' => '15/02/14', 'role' => 'Astronaut', 'name' => 'Neil'],
];
$columnWidth = 20;
foreach ($resultSet as ['date' => $date, 'role' => $role, 'name' => $name]) {
$nested[$date][$role][] = $name;
$dates[$date] = str_pad($date, $columnWidth, " ", STR_PAD_BOTH);
$roles[$role] = str_pad($role, $columnWidth, " ", STR_PAD_BOTH);
}
$totalColumns = count($dates) + 1;
// HEADINGS
printf(
implode("|", array_fill(0, $totalColumns, '%s')) . "\n",
str_pad('Roles', $columnWidth, " ", STR_PAD_BOTH),
...array_values($dates)
);
// SEPARATOR
echo implode("|", array_fill(0, $totalColumns, str_repeat('=', $columnWidth)));
// DATA
foreach ($roles as $role => $paddedRole) {
echo "\n$paddedRole";
foreach ($nested as $date => $roleGroup) {
echo '|' . str_pad(implode(', ', $nested[$date][$role] ?? ['-']), $columnWidth, " ", STR_PAD_BOTH);
}
}
Output:
Roles | 01/02/14 | 08/02/14 | 15/02/14
====================|====================|====================|====================
Leader | Jerry | Baz, Gaz, Haz | -
Musician | Bob | Charles | -
Singer | Carol | Norman | -
Astronaut | - | - | Neil
Goodness, that was fun... :-/
This is tested code that does as required. There are lots of comments. Feel free to remove them to see the code more clearly. Whatever...
You should be able to change the $allRoles array to get the roles to print in a different order. I have tried it and it works fine.
It runs on PHP 5.3.18 on windows XP (XAMPP).
Added some css to make the table clearer.
Changed the code to read the data from a 'mysqli' query rather than an array
see the lines marked '!important' to ensure it works correctly.
sample output:
Roles 01/02/14 05/02/14 08/02/14
musician Bob Donald Charles
leader Jerry -- Baz
singer Carol Freddy Norman
code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Q2220229 - Pivot table</title>
<style>
td {
border-bottom: 1px solid grey;
width: 10em;
}
</style>
</head>
<body>
<?php
/*
* Some test data base on:
* Date |Role |Name
=============================
01/02/14 |Musician |Bob
01/02/14 |Leader |Jerry
01/02/14 |Singer |Carol
08/02/14 |Musician |Charles
08/02/14 |Leader |Baz
08/02/14 |Singer |Norman
*
*/
/* sample output:
*
* Role |01/02/14 |08/02/14
===============================
Musician |Bob |Charles
Leader |Jerry |Baz
Singer |Carol |Norman
*/
$db = mysqli_connect('localhost', 'test', 'test', 'testmysql');
// 1) Must return three columns only.
// 2) Can return any number of 'roles' - one per row
// 3) Any date range but beware you may need a wide page!
// 4) Must sort by date!
$query = mysqli_query($db, "SELECT service_date, role, member FROM role_by_date ORDER BY service_date ASC, role ASC");
// i prefer to used named subscripts to make the code easier to read.
// These MUST match up with column alias from the above query!
define('THE_DATE', 'service_date'); // !important
define('ROLE', 'role'); // !imortant
define('MEMBER', 'member'); // !important
/*
* Now, we need a complete array of Roles in the order that they are to be displayed.
*
* These names must match with the names of the roles in the input data.
* They will be printed out in the order that they appear in the array.
*
* These are the only roles that will appear in the $outputDates array.
* Add more and in any order to control which 'roles' are shown.
*
*/
$allRoles = array('musician', 'leader', 'singer'); // !important
/*
* At some point we will need an output array that we can easily traverse and
* print out as a row of dates. i.e. a 'page' of data.
*
* We will build it up as we go along...
*/
$outputDates = array(); // !important -- this is the 'pivoted' output array
/*
* Start to process the input data.
*
* To make my life easier, i will use the 'read ahead' technique to simplify the code.
*/
$currentInputRow = mysqli_fetch_array($query);
while (isset($currentInputRow[THE_DATE])) { // process all the input array...
// must be a new day...
$currentDay = $currentInputRow[THE_DATE];
// create an array to hold ALL the possible roles for this day...
$theDayRoles = array();
// initialise the array with default values for all the requested roles.
foreach ($allRoles as $role) {
$theDayRoles[$role] = '--';
}
// now we need to fill theDayRoles with what we actually have for the current day...
while ($currentInputRow[THE_DATE] == $currentDay) { // loop around all records for the current day
// set the appropiate DayRole to the current MEMBER
$theDayRoles[$currentInputRow[ROLE]] = $currentInputRow[MEMBER];
// read the next input row - may be current day, new day or no more
$currentInputRow = mysqli_fetch_array($query);
}
// end of day on the input for whatever reason...
/* we now have:
* 1) Current Date
*
* 2) an array of members for ALL the roles on that day.
*
* We need to output it to another array ($outputDates) where we can print it out
* by scanning the array line by line later.
*
* I will 'pivot' the array and produce an output array we can scan sequentially later.
*/
// to ensure that we are updating the correct $outputDates row i will use a subscript
$currentOutputRowIdx = 0;
// first add the current date to the output...
$outputDates[$currentOutputRowIdx][] = $currentDay;
$currentOutputRowIdx++; // next output row
// we need to drive off the '$allRoles' array to add the role data in the correct order
foreach ($allRoles as $outRole) {
$outputDates[$currentOutputRowIdx][] = $theDayRoles[$outRole];
$currentOutputRowIdx++; // next output row
}
} // end of all the input data
/*
* Now we just need to print the outputDates array one row at a time...
*/
// need the roles as the first column...
// so we need an index for which one we are currently printing
$currentRoleIdx = -1; // increment each time but allow for the first row being the title 'Roles'
echo '<table>';
foreach ($outputDates as $oneOutputRow) {
echo '<tr>';
// this is the first column...
if ($currentRoleIdx < 0) {
echo '<td>'. 'Roles' .'</td>';
}
else {
echo '<td>'. $allRoles[$currentRoleIdx] .'</td>';
}
// now output the day info
foreach($oneOutputRow as $column) {
echo '<td>'. $column .'</td>';
}
echo '</tr>';
$currentRoleIdx++; // next output Role to show...
}
echo '</table>';
?>
</body>
</html>