How do I create a Crosstab table with php mysql

后端 未结 3 1592
暖寄归人
暖寄归人 2021-01-06 16:58

I have the following recordset:

Date     |Role      |Name
=============================
01/02/14 |Musician  |Bob
01/02/14 |Leader    |Jerry
01/02/14 |Singer          


        
相关标签:
3条回答
  • 2021-01-06 17:10
        <?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>';
            
     ?>
    
    0 讨论(0)
  • 2021-01-06 17:12

    ...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        
    
    0 讨论(0)
  • 2021-01-06 17:25

    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>
    
    0 讨论(0)
提交回复
热议问题