From b572d773dcae55f9717fe3ab0515e2b9f39ab59c Mon Sep 17 00:00:00 2001 From: Henk Bierlee Date: Mon, 4 Jul 2016 18:44:41 +0200 Subject: [PATCH 1/4] Output LTV start date, monthly booker count and monthly booking count average I use a query to retrieve all bookings and group the bookings of each booker per month. Then I use PHP to further process the results by folding the requested lifetime period of months to get booker counts, booking counts and averages. --- assignment1/index.php | 188 ++++++++++++++++++++++++++++-------------- 1 file changed, 128 insertions(+), 60 deletions(-) diff --git a/assignment1/index.php b/assignment1/index.php index 955a2dc..2dbef41 100644 --- a/assignment1/index.php +++ b/assignment1/index.php @@ -5,70 +5,138 @@ require_once(__DIR__ . '/include.php'); // Vars -$period = 12; // Life-Time of 12 months +$period = 6; // Life-Time of 12 months $commission = 0.10; // 10% commission // Prepare query -$result = $db - ->prepare(' - SELECT * FROM bookings +$db_result = $db + ->prepare(' + SELECT bookers.id AS booker, COUNT(bookings.id) AS booking_count, strftime("%Y-%m", datetime(end_timestamp, "unixepoch")) AS "year_month" + FROM bookings + JOIN bookers ON bookers.id = bookings.booker_id + JOIN bookingitems ON bookings.id = bookingitems.booking_id + GROUP BY booker, year_month + ORDER BY booker, end_timestamp ') - ->run() -; + ->run(); + +$array = array(); + +foreach ($db_result as $index => $row) { + $month_counter = 0; + + array_push($array, array( + "booker" => $row->booker, + "booking_count" => $row->booking_count, + "year_month" => $row->year_month + )); + $next_row = $db_result->fetch(); + + while ($row->booker == $next_row->booker) { + // Aggregate the data of this booker if it is still in the Life Time period, else skip + if ($month_counter < $period) { + $array[$index]["booking_count"] += $next_row->booking_count; + $month_counter++; + } + $next_row = $db_result->fetch(); + } + + $row = $next_row; +} + + +usort($array, function ($a, $b) { + $a = $a['year_month']; + $b = $b['year_month']; + if ($a < $b) { + return -1; + } else if ($a > $b) { + return 1; + } else { + return 0; + } +}); + + +$grouped_array = array(); +array_push($grouped_array, $array[0]); + +foreach ($array as $value) { + if ($value["year_month"] != $grouped_array[count($grouped_array) - 1]["year_month"]) { + array_push($grouped_array, $value); + $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; + } else { + $grouped_array[count($grouped_array) - 1]["booking_count"] += $value["booking_count"]; + + if (isset($grouped_array[count($grouped_array) - 1]["booker_count"])) { + $grouped_array[count($grouped_array) - 1]["booker_count"]++; + } else { + $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; + } + } +} + + ?> + +
+    
+
+ - - Assignment 1: Create a Report (SQL) - - - - -

Report:

- - - - - - - - - - - - $row): ?> - - - - - - - - - - - - - - - -
StartBookers# of bookings (avg)Turnover (avg)LTV
TODOTODOTODOTODOTODO
Total rows:
- - \ No newline at end of file + + Assignment 1: Create a Report (SQL) + + + + +

Report:

+ + + + + + + + + + + + $row): ?> + + + + + + + + + + + + + + + +
StartBookers# of bookings (avg)Turnover (avg)LTV
TODOTODO
Total rows:
+ + From fb6813fbe2146a9419e167abb8b254bc360a073e Mon Sep 17 00:00:00 2001 From: Henk Bierlee Date: Tue, 5 Jul 2016 17:01:43 +0200 Subject: [PATCH 2/4] Use end-of-line normalization --- .gitattributes | 1 + assignment1/errors.php | 70 +++--- assignment1/include.php | 334 ++++++++++++++--------------- assignment1/index.php | 284 ++++++++++++------------ assignment1/resources/database.sql | 124 +++++------ 5 files changed, 407 insertions(+), 406 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/assignment1/errors.php b/assignment1/errors.php index 4cb0c92..c980293 100644 --- a/assignment1/errors.php +++ b/assignment1/errors.php @@ -1,35 +1,35 @@ -' - . 'ERROR: ' . $exception->getMessage() . ' :: ' . $exception->getCode() . "\n" - . 'In ' . $exception->getFile() . ':' . $exception->getLine() . "\n" - . '-------------------------------------------------------------------------------------------------------' . "\n" - . $exception->getTraceAsString() - . ''; -} -register_shutdown_function('fatalErrorHandler'); -set_exception_handler('uncaughtExceptionHandler'); -set_error_handler('errorToExceptionHandler'); +' + . 'ERROR: ' . $exception->getMessage() . ' :: ' . $exception->getCode() . "\n" + . 'In ' . $exception->getFile() . ':' . $exception->getLine() . "\n" + . '-------------------------------------------------------------------------------------------------------' . "\n" + . $exception->getTraceAsString() + . ''; +} +register_shutdown_function('fatalErrorHandler'); +set_exception_handler('uncaughtExceptionHandler'); +set_error_handler('errorToExceptionHandler'); diff --git a/assignment1/include.php b/assignment1/include.php index 7a9e935..9fd772f 100644 --- a/assignment1/include.php +++ b/assignment1/include.php @@ -1,167 +1,167 @@ - - */ -class DatabaseStatement extends PDOStatement -{ - /** - * Database - * - * @var Database - */ - protected $db_; - - /** - * Constructor - * - * @param PDO $pdo - */ - protected function __construct(Database $db) - { - $this->db_ = $db; - } - - /** - * Database - * - * @return Database - */ - public function db() - { - return $this->db_; - } - - /** - * Bind a param by reference. - * - * @param string|int $param - * @param mixed& $reference - */ - public function bind($param, &$reference, $type = PDO::PARAM_STR) - { - $this->bindParam($param, $reference, $type); - return $this; - } - - /** - * Bind param by value. - * - * @param string|int $param - * @param mixed $value - */ - public function param($param, $value, $type = PDO::PARAM_STR) - { - $this->bindValue($param, $value, $type); - return $this; - } - - /** - * Apply the binded params and execute the query. - * - * @return DatabaseStatement Self, now ready for a foreach loop. - */ - public function run() - { - $this->execute(); - return $this; - } -} - -/** - * Database - * - * @author Leon van der Veen - */ -class Database -{ - /** - * Connection - * - * @var PDO - */ - protected $db_; - - /** - * Constructor - */ - public function __construct() - { - // Open PDO connection - $this->db_ = new PDO('sqlite:' . __DIR__ . '/db/assignment1.db'); - - // Settings - $this->db_->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->db_->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); - $this->db_->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['DatabaseStatement', [$this]]); - } - - /** - * Prepare query. - * - * @param string $sql - * @return DatabaseStatement - */ - public function prepare($sql) - { - return $this->db_->prepare($sql); - } - - /** - * Run query (with applied data). - * - * For example: - * $db->query('SELECT * FROM table WHERE col1 < :val1 AND col2 = :val2', [ - * ':val1' => [4, PDO::PARAM_INT], // Set type explicitly - * ':val2' => 'Test', // PARAM_STR by default - * ]); - * - * @param string $sql - * @param mixed[mixed] $params Params, key/value pairs, to be binded to the query. When the value is an array with length two, the first item will be binded and the second item will be the type. - * @return DatabaseStatement A Database Statement object which is directly usable to use in a foreach loop. - */ - public function query($sql, array $params = []) - { - // Prepare the statement - $statement = $this->prepare($sql); - - // Bind params - foreach ($params as $param => &$val) - { - if (is_array($val) && count($val) >= 2) - { - $statement->bindByRef($param, $val[0], $val[1]); - } - else - { - $statement->bindByRef($param, $val); - } - } - - // Run query - return $statement->run(); - } -} - -// Database instance -$db = new Database(); + + */ +class DatabaseStatement extends PDOStatement +{ + /** + * Database + * + * @var Database + */ + protected $db_; + + /** + * Constructor + * + * @param PDO $pdo + */ + protected function __construct(Database $db) + { + $this->db_ = $db; + } + + /** + * Database + * + * @return Database + */ + public function db() + { + return $this->db_; + } + + /** + * Bind a param by reference. + * + * @param string|int $param + * @param mixed& $reference + */ + public function bind($param, &$reference, $type = PDO::PARAM_STR) + { + $this->bindParam($param, $reference, $type); + return $this; + } + + /** + * Bind param by value. + * + * @param string|int $param + * @param mixed $value + */ + public function param($param, $value, $type = PDO::PARAM_STR) + { + $this->bindValue($param, $value, $type); + return $this; + } + + /** + * Apply the binded params and execute the query. + * + * @return DatabaseStatement Self, now ready for a foreach loop. + */ + public function run() + { + $this->execute(); + return $this; + } +} + +/** + * Database + * + * @author Leon van der Veen + */ +class Database +{ + /** + * Connection + * + * @var PDO + */ + protected $db_; + + /** + * Constructor + */ + public function __construct() + { + // Open PDO connection + $this->db_ = new PDO('sqlite:' . __DIR__ . '/db/assignment1.db'); + + // Settings + $this->db_->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->db_->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $this->db_->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['DatabaseStatement', [$this]]); + } + + /** + * Prepare query. + * + * @param string $sql + * @return DatabaseStatement + */ + public function prepare($sql) + { + return $this->db_->prepare($sql); + } + + /** + * Run query (with applied data). + * + * For example: + * $db->query('SELECT * FROM table WHERE col1 < :val1 AND col2 = :val2', [ + * ':val1' => [4, PDO::PARAM_INT], // Set type explicitly + * ':val2' => 'Test', // PARAM_STR by default + * ]); + * + * @param string $sql + * @param mixed[mixed] $params Params, key/value pairs, to be binded to the query. When the value is an array with length two, the first item will be binded and the second item will be the type. + * @return DatabaseStatement A Database Statement object which is directly usable to use in a foreach loop. + */ + public function query($sql, array $params = []) + { + // Prepare the statement + $statement = $this->prepare($sql); + + // Bind params + foreach ($params as $param => &$val) + { + if (is_array($val) && count($val) >= 2) + { + $statement->bindByRef($param, $val[0], $val[1]); + } + else + { + $statement->bindByRef($param, $val); + } + } + + // Run query + return $statement->run(); + } +} + +// Database instance +$db = new Database(); diff --git a/assignment1/index.php b/assignment1/index.php index 2dbef41..49938de 100644 --- a/assignment1/index.php +++ b/assignment1/index.php @@ -1,142 +1,142 @@ -prepare(' - SELECT bookers.id AS booker, COUNT(bookings.id) AS booking_count, strftime("%Y-%m", datetime(end_timestamp, "unixepoch")) AS "year_month" - FROM bookings - JOIN bookers ON bookers.id = bookings.booker_id - JOIN bookingitems ON bookings.id = bookingitems.booking_id - GROUP BY booker, year_month - ORDER BY booker, end_timestamp - ') - ->run(); - -$array = array(); - -foreach ($db_result as $index => $row) { - $month_counter = 0; - - array_push($array, array( - "booker" => $row->booker, - "booking_count" => $row->booking_count, - "year_month" => $row->year_month - )); - $next_row = $db_result->fetch(); - - while ($row->booker == $next_row->booker) { - // Aggregate the data of this booker if it is still in the Life Time period, else skip - if ($month_counter < $period) { - $array[$index]["booking_count"] += $next_row->booking_count; - $month_counter++; - } - $next_row = $db_result->fetch(); - } - - $row = $next_row; -} - - -usort($array, function ($a, $b) { - $a = $a['year_month']; - $b = $b['year_month']; - if ($a < $b) { - return -1; - } else if ($a > $b) { - return 1; - } else { - return 0; - } -}); - - -$grouped_array = array(); -array_push($grouped_array, $array[0]); - -foreach ($array as $value) { - if ($value["year_month"] != $grouped_array[count($grouped_array) - 1]["year_month"]) { - array_push($grouped_array, $value); - $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; - } else { - $grouped_array[count($grouped_array) - 1]["booking_count"] += $value["booking_count"]; - - if (isset($grouped_array[count($grouped_array) - 1]["booker_count"])) { - $grouped_array[count($grouped_array) - 1]["booker_count"]++; - } else { - $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; - } - } -} - - -?> - -
-    
-
- - - - - Assignment 1: Create a Report (SQL) - - - - -

Report:

- - - - - - - - - - - - $row): ?> - - - - - - - - - - - - - - - -
StartBookers# of bookings (avg)Turnover (avg)LTV
TODOTODO
Total rows:
- - +prepare(' + SELECT bookers.id AS booker, COUNT(bookings.id) AS booking_count, strftime("%Y-%m", datetime(end_timestamp, "unixepoch")) AS "year_month" + FROM bookings + JOIN bookers ON bookers.id = bookings.booker_id + JOIN bookingitems ON bookings.id = bookingitems.booking_id + GROUP BY booker, year_month + ORDER BY booker, end_timestamp + ') + ->run(); + +$array = array(); + +foreach ($db_result as $index => $row) { + $month_counter = 0; + + array_push($array, array( + "booker" => $row->booker, + "booking_count" => $row->booking_count, + "year_month" => $row->year_month + )); + $next_row = $db_result->fetch(); + + while ($row->booker == $next_row->booker) { + // Aggregate the data of this booker if it is still in the Life Time period, else skip + if ($month_counter < $period) { + $array[$index]["booking_count"] += $next_row->booking_count; + $month_counter++; + } + $next_row = $db_result->fetch(); + } + + $row = $next_row; +} + + +usort($array, function ($a, $b) { + $a = $a['year_month']; + $b = $b['year_month']; + if ($a < $b) { + return -1; + } else if ($a > $b) { + return 1; + } else { + return 0; + } +}); + + +$grouped_array = array(); +array_push($grouped_array, $array[0]); + +foreach ($array as $value) { + if ($value["year_month"] != $grouped_array[count($grouped_array) - 1]["year_month"]) { + array_push($grouped_array, $value); + $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; + } else { + $grouped_array[count($grouped_array) - 1]["booking_count"] += $value["booking_count"]; + + if (isset($grouped_array[count($grouped_array) - 1]["booker_count"])) { + $grouped_array[count($grouped_array) - 1]["booker_count"]++; + } else { + $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; + } + } +} + + +?> + +
+    
+
+ + + + + Assignment 1: Create a Report (SQL) + + + + +

Report:

+ + + + + + + + + + + + $row): ?> + + + + + + + + + + + + + + + +
StartBookers# of bookings (avg)Turnover (avg)LTV
TODOTODO
Total rows:
+ + diff --git a/assignment1/resources/database.sql b/assignment1/resources/database.sql index aea9037..7eee6ff 100644 --- a/assignment1/resources/database.sql +++ b/assignment1/resources/database.sql @@ -1,62 +1,62 @@ --- Structure - -CREATE TABLE venues ( - id integer PRIMARY KEY AUTOINCREMENT, - name VARCHAR(255) NOT NULL -); - -CREATE TABLE items ( - id integer PRIMARY KEY AUTOINCREMENT, - venue_id integer NOT NULL, - name VARCHAR(255) NOT NULL, - FOREIGN KEY (venue_id) REFERENCES venues (id) -); - -CREATE TABLE spaces ( - id integer PRIMARY KEY AUTOINCREMENT, - item_id integer NOT NULL, - hour_price real NOT NULL, - FOREIGN KEY (item_id) REFERENCES items (id) -); - -CREATE TABLE products ( - id integer PRIMARY KEY AUTOINCREMENT, - item_id integer NOT NULL, - price real NOT NULL, - FOREIGN KEY (item_id) REFERENCES items (id) -); - -CREATE TABLE users ( - id integer PRIMARY KEY AUTOINCREMENT, - first_name VARCHAR(255) NOT NULL, - last_name VARCHAR(255) NOT NULL, - registered integer NOT NULL DEFAULT 0, - email VARCHAR(255) NOT NULL -); - -CREATE TABLE bookers ( - id integer PRIMARY KEY AUTOINCREMENT, - user_id integer NOT NULL, - created integer NOT NULL DEFAULT 0, - FOREIGN KEY (user_id) REFERENCES users (id) -); - -CREATE TABLE bookings ( - id integer PRIMARY KEY AUTOINCREMENT, - booker_id integer NOT NULL, - created integer NOT NULL DEFAULT 0, - FOREIGN KEY (booker_id) REFERENCES bookers (id) -); - -CREATE TABLE bookingitems ( - id integer PRIMARY KEY AUTOINCREMENT, - booking_id integer NOT NULL, - item_id integer NOT NULL, - quantity integer NOT NULL, - locked_piece_price real NOT NULL, - locked_total_price real NOT NULL, - start_timestamp integer NULL, - end_timestamp integer NULL, - FOREIGN KEY (booking_id) REFERENCES bookings (id), - FOREIGN KEY (item_id) REFERENCES items (id) -); +-- Structure + +CREATE TABLE venues ( + id integer PRIMARY KEY AUTOINCREMENT, + name VARCHAR(255) NOT NULL +); + +CREATE TABLE items ( + id integer PRIMARY KEY AUTOINCREMENT, + venue_id integer NOT NULL, + name VARCHAR(255) NOT NULL, + FOREIGN KEY (venue_id) REFERENCES venues (id) +); + +CREATE TABLE spaces ( + id integer PRIMARY KEY AUTOINCREMENT, + item_id integer NOT NULL, + hour_price real NOT NULL, + FOREIGN KEY (item_id) REFERENCES items (id) +); + +CREATE TABLE products ( + id integer PRIMARY KEY AUTOINCREMENT, + item_id integer NOT NULL, + price real NOT NULL, + FOREIGN KEY (item_id) REFERENCES items (id) +); + +CREATE TABLE users ( + id integer PRIMARY KEY AUTOINCREMENT, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + registered integer NOT NULL DEFAULT 0, + email VARCHAR(255) NOT NULL +); + +CREATE TABLE bookers ( + id integer PRIMARY KEY AUTOINCREMENT, + user_id integer NOT NULL, + created integer NOT NULL DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users (id) +); + +CREATE TABLE bookings ( + id integer PRIMARY KEY AUTOINCREMENT, + booker_id integer NOT NULL, + created integer NOT NULL DEFAULT 0, + FOREIGN KEY (booker_id) REFERENCES bookers (id) +); + +CREATE TABLE bookingitems ( + id integer PRIMARY KEY AUTOINCREMENT, + booking_id integer NOT NULL, + item_id integer NOT NULL, + quantity integer NOT NULL, + locked_piece_price real NOT NULL, + locked_total_price real NOT NULL, + start_timestamp integer NULL, + end_timestamp integer NULL, + FOREIGN KEY (booking_id) REFERENCES bookings (id), + FOREIGN KEY (item_id) REFERENCES items (id) +); From bc77391e68fcdc05faeee43caba07291f58548f9 Mon Sep 17 00:00:00 2001 From: Henk Bierlee Date: Tue, 5 Jul 2016 17:17:18 +0200 Subject: [PATCH 3/4] Query for turnover and output average turnover and LTV --- assignment1/index.php | 117 ++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/assignment1/index.php b/assignment1/index.php index 49938de..6ebd5d7 100644 --- a/assignment1/index.php +++ b/assignment1/index.php @@ -9,9 +9,10 @@ $commission = 0.10; // 10% commission // Prepare query +//TODO filter out turnover from booked products, as only booked spaces should count towards the LTV $db_result = $db ->prepare(' - SELECT bookers.id AS booker, COUNT(bookings.id) AS booking_count, strftime("%Y-%m", datetime(end_timestamp, "unixepoch")) AS "year_month" + SELECT bookers.id AS booker, COUNT(bookings.id) AS booking_count, strftime("%Y-%m", datetime(end_timestamp, "unixepoch")) AS "year_month", SUM(locked_total_price) AS turnover FROM bookings JOIN bookers ON bookers.id = bookings.booker_id JOIN bookingitems ON bookings.id = bookingitems.booking_id @@ -28,7 +29,8 @@ array_push($array, array( "booker" => $row->booker, "booking_count" => $row->booking_count, - "year_month" => $row->year_month + "year_month" => $row->year_month, + "turnover" => $row->turnover )); $next_row = $db_result->fetch(); @@ -36,7 +38,9 @@ // Aggregate the data of this booker if it is still in the Life Time period, else skip if ($month_counter < $period) { $array[$index]["booking_count"] += $next_row->booking_count; + $array[$index]["turnover"] += $next_row->turnover; $month_counter++; + //FIXME this introduces bugs when the booker skips months! } $next_row = $db_result->fetch(); } @@ -57,7 +61,6 @@ } }); - $grouped_array = array(); array_push($grouped_array, $array[0]); @@ -70,73 +73,67 @@ if (isset($grouped_array[count($grouped_array) - 1]["booker_count"])) { $grouped_array[count($grouped_array) - 1]["booker_count"]++; + $grouped_array[count($grouped_array) - 1]["turnover"] += $value["turnover"]; } else { $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; } } } - ?> -
-    
-
- - - Assignment 1: Create a Report (SQL) - - - - -

Report:

- - - - - - - - - - - - $row): ?> - - - - - - - - - - + + Assignment 1: Create a Report (SQL) + + + + +

Report:

+
StartBookers# of bookings (avg)Turnover (avg)LTV
TODOTODO
+ + + + + + + + + + + $row): ?> - - + + + + + - -
StartBookers# of bookings (avg)Turnover (avg)LTV
Total rows:
- + + + + + Total rows: + + + + + From 0006d9668a248fc4453c044a2e32ae4ea2700d67 Mon Sep 17 00:00:00 2001 From: Henk Bierlee Date: Tue, 5 Jul 2016 17:27:47 +0200 Subject: [PATCH 4/4] Add some comments to explain the calculation of the LTV --- assignment1/index.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/assignment1/index.php b/assignment1/index.php index 6ebd5d7..30cafe0 100644 --- a/assignment1/index.php +++ b/assignment1/index.php @@ -9,6 +9,7 @@ $commission = 0.10; // 10% commission // Prepare query +// This query returns the following results: the booker id, a month specified by a year-month format, the amount of bookings for that month by that booker and the total turnover for that month by that booker. //TODO filter out turnover from booked products, as only booked spaces should count towards the LTV $db_result = $db ->prepare(' @@ -21,8 +22,9 @@ ') ->run(); -$array = array(); +// Filter out bookings in months beyond the LTV period and put the results in an array +$array = array(); foreach ($db_result as $index => $row) { $month_counter = 0; @@ -35,7 +37,7 @@ $next_row = $db_result->fetch(); while ($row->booker == $next_row->booker) { - // Aggregate the data of this booker if it is still in the Life Time period, else skip + // Aggregate the data of this booker if it is still in the Life Time Value period, else skip if ($month_counter < $period) { $array[$index]["booking_count"] += $next_row->booking_count; $array[$index]["turnover"] += $next_row->turnover; @@ -48,7 +50,7 @@ $row = $next_row; } - +// Sort the array by month usort($array, function ($a, $b) { $a = $a['year_month']; $b = $b['year_month']; @@ -61,9 +63,11 @@ } }); + +// Group the bookers from the same month together +// The result is an array with the booking counts and turnover per month (and all within the LTV period of the bookers that made the bookings/turnover) $grouped_array = array(); array_push($grouped_array, $array[0]); - foreach ($array as $value) { if ($value["year_month"] != $grouped_array[count($grouped_array) - 1]["year_month"]) { array_push($grouped_array, $value); @@ -78,9 +82,7 @@ $grouped_array[count($grouped_array) - 1]["booker_count"] = 1; } } -} - -?> +}?>