Ioannis Kokkinis Ioannis Kokkinis - 6 months ago 12
SQL Question

Pivoting mysql result with dates and sums

I have some data in mysql that I need to create some reports from.
My data are coming from the following query :

SELECT StoreNbr,StoreName,Date, SUM(`Sales`) FROM sales_tbl GROUP BY StoreNbr,StoreName,Date;


This results in the following data (just a small subset for my example):

+-----------+---------------------------+----------------------------+
| StoreNbr | StoreName | Date | SUM(Sales) |
+-----------+---------------------------+----------------------------+
| 1112 | Store1 | 2016-01-16 | 115.09 |
| 1112 | Store1 | 2016-01-17 | 81.00 |
| 1113 | Store2 | 2016-01-16 | 112.44 |
| 1113 | Store2 | 2016-01-17 | 56.61 |


I would like to transform my data to be this way :

+-----------+---------------------------+----------------------------+
| StoreNbr | StoreName | 2016-01-16 | 2016-01-17 |
+-----------+---------------------------+----------------------------+
| 1112 | Store1 | 115.09 | 81.00 |
| 1113 | Store2 | 112.44 | 56.61 |


Obviously there might be thousands of rows (stores) and unknown number of dates to be returned in the query as my query might be run like this (this will need to return 120+ number of columns for dates):

SELECT StoreNbr,StoreName,Date, SUM(`Sales`) FROM sales_tbl WHERE (Date BETWEEN '2016-01-10' AND '2016-05-10') GROUP BY StoreNbr,StoreName,Date;


There are a few ways to do this, none very simple. I did some research and there are some that mention that mysql does not support pivoting. I am running mariadb though, and saw that mariadb supports pivoting through the connect engine. I was unable to make it work though (adjust their official examples on my data).
Another way is lots of IFs and Cases, but most of the answers I am finding are very difficult to adapt or are tailored only to the data the guy that asks provides.
Another approach would be to process the data as they come out on my array as I have a json response in the end that feeds a datatable. - This is another think I have not managed to figure out yet.

I am looking for a way to get the desired output independent on the amount of dates (and I guess dates could be replaced by weeks or whatever else). Can anyone help?

Answer

Select all distinct dates

SELECT DISTINCT Date 
FROM sales_tbl 
WHERE (Date BETWEEN '2016-01-10' AND '2016-05-10')
ORDER BY Date;

and initialize an array which is indexed by that dates storing zeros:

$dateIndexedArray = array();
while($row = $stmt1->fetch(PDO::FETCH_ASSOC) {
    $dateIndexedArray[$row['Date']] = 0;
}

The arry will look like

[
    '2016-01-16' => 0,
    '2016-01-17' => 0
]

Then execute your query

SELECT StoreNbr, StoreName,Date, SUM(`Sales`) AS Sales 
FROM sales_tbl
WHERE (Date BETWEEN '2016-01-10' AND '2016-05-10')
GROUP BY StoreNbr,StoreName,Date;

And store the "Sales" in a date indexed array per store

$report = array();
while($row = $stmt2->fetch(PDO::FETCH_ASSOC)){
    $storeIndex = $row['StoreNbr'] . ':' . $row['StoreName'];
    if (!isset($report[$storeIndex])) {
        $report[$storeIndex] = array(
            'StoreNbr'  => $row['StoreNbr'],
            'StoreName' => $row['StoreName'],
            'Sales' => $dateIndexedArray
        );
    }
    $report[$storeIndex]['Sales'][$row['Date']] = $row['Sales'];
}

The $report array will look like:

[
    '1112:Store1' => [
        'StoreNbr'  => 1112,
        'StoreName' => 'Store1',
        'Sales' => [
            '2016-01-16' => 115.09,
            '2016-01-17' => 81.00
        ]
    ],
    '1113:Store2' => [
        'StoreNbr'  => 1113,
        'StoreName' => 'Store2',
        'Sales' => [
            '2016-01-16' => 112.44,
            '2016-01-17' => 56.61
        ]
    ]
]

Update: If you need all data to be in one row for each store you can change the code to:

$report = array();
while($row = $stmt2->fetch(PDO::FETCH_ASSOC)){
    $storeIndex = $row['StoreNbr'] . ':' . $row['StoreName'];
    if (!isset($report[$storeIndex])) {
        $report[$storeIndex] = $dateIndexedArray;
        $report[$storeIndex]['StoreNbr']  = $row['StoreNbr'];
        $report[$storeIndex]['StoreName'] = $row['StoreName'];
    }
    $report[$storeIndex][$row['Date']] = $row['Sales'];
}

The resulting array will look like:

[
    '1112:Store1' => [
        'StoreNbr'  => 1112,
        'StoreName' => '
        '2016-01-16' => 115.09,
        '2016-01-17' => 81.
    ],
    '1113:Store2' => [
        'StoreNbr'  => 1113,
        'StoreName' => 'Store2',
        '2016-01-16' => 112.44,
        '2016-01-17' => 56.61
    ]
]

Update 2: To get the total sales per store you can use WITH ROLLUP

SELECT StoreNbr, StoreName,Date, SUM(`Sales`) AS Sales 
FROM sales_tbl
WHERE (Date BETWEEN '2016-01-10' AND '2016-05-10')
GROUP BY StoreNbr,StoreName,Date WITH ROLLUP;
$report = array();
while($row = $stmt2->fetch(PDO::FETCH_ASSOC)){
    if ($row['StoreName'] === null) {
        // Skip this row. It contains total sales grouped by StoreNbr.
        continue;
    }
    $storeIndex = $row['StoreNbr'] . ':' . $row['StoreName'];
    if (!isset($report[$storeIndex])) {
        $report[$storeIndex] = $dateIndexedArray;
        $report[$storeIndex]['StoreNbr']  = $row['StoreNbr'];
        $report[$storeIndex]['StoreName'] = $row['StoreName'];
    }
    if ($row['Date'] === null) {
        // This row contains total sales grouped by StoreNbr & StoreName
        $report[$storeIndex]['TotalSales'] = $row['Sales']
    } else {
        $report[$storeIndex][$row['Date']] = $row['Sales'];
    }
}

Please note that i've never used WITH ROLLUP. So you might need to adjust the code.

Comments