Nice piece of code you got there, but i think you are overthinking this :-) It's easier to just transform the data before you are outputting it, since outputting it is easy!
<?php
$sql = "SELECT id, title, data FROM table ORDER BY ID ASC;";
if (!$result = $mysqli->query($sql))
die('There was an error running the query [' . $mysqli->error . ']');
$dataList = array();
$maxDataCount = 0;
while ($row = $result->fetch_assoc()) {
$title = strtoupper($row['title']);
$data = json_decode($row['data'], false);
// keep the maximum number of data rows. We need to fill in empty gaps later on.
$maxDataCount = max($maxDataCount,count($data));
$dataList[] = array( $title, $data );
}
foreach($dataList as $row)
{
// the first row of the new output array is an array containing headers
// we just push new headers to that array.
if (!isset($output[0]))
$output[0] = array();
// 0 = title, 1 = data
$output[0][] = $row[0];
$data = $row[1];
// some magic: we append every value to it's corresponding row
// if the value does not exist, we put NULL
for($i = 0, $l = $maxDataCount; $i < $l; $i++)
{
$idx = $i+1;
if (!isset($output[$idx]))
$output[$idx] = array();
$output[$idx][] = empty($data[$i]) ? NULL : $data[$i];
}
}
foreach($output as $row)
echo implode(',',$row) . "
";