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 955a2dc..30cafe0 100644 --- a/assignment1/index.php +++ b/assignment1/index.php @@ -1,74 +1,141 @@ -prepare(' - SELECT * FROM bookings - ') - ->run() -; -?> - - - - Assignment 1: Create a Report (SQL) - - - - -

Report:

- - - - - - - - - - - - $row): ?> - - - - - - - - - - - - - - - -
StartBookers# of bookings (avg)Turnover (avg)LTV
TODOTODOTODOTODOTODO
Total rows:
- - \ No newline at end of file +prepare(' + 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 + GROUP BY booker, year_month + ORDER BY booker, end_timestamp + ') + ->run(); + + +// 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; + + array_push($array, array( + "booker" => $row->booker, + "booking_count" => $row->booking_count, + "year_month" => $row->year_month, + "turnover" => $row->turnover + )); + $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 Value 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(); + } + + $row = $next_row; +} + +// Sort the array by month +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; + } +}); + + +// 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); + $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"]++; + $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): ?> + + + + + + + + + + + + + + + +
StartBookers# of bookings (avg)Turnover (avg)LTV
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) +);