francerz / sql-builder
SQL Builder
Installs: 1 196
Dependents: 3
Suggesters: 0
Security: 0
Stars: 5
Watchers: 2
Forks: 0
Open Issues: 0
Requires
- francerz/enum: ^0.1.1
- francerz/php-power-data: ^0.1
- psr/http-message: ^1.0
Suggests
- francerz/mysql-builder: MySQL compatible driver.
- francerz/sqlsrv-builder: Microsoft SQL Server compatible driver.
- dev-master
- v0.5.24
- v0.5.23
- v0.5.22
- v0.5.21
- v0.5.20
- v0.5.19
- v0.5.18
- v0.5.17
- v0.5.16
- v0.5.15
- v0.5.14
- v0.5.13
- v0.5.12
- v0.5.11
- v0.5.10
- v0.5.9
- v0.5.8
- v0.5.7
- v0.5.6
- v0.5.5
- v0.5.4
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.9
- v0.4.8
- v0.4.7
- v0.4.6
- v0.4.5
- v0.4.4
- v0.4.3
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.13
- v0.3.12
- v0.3.11
- v0.3.10
- v0.3.9
- v0.3.8
- v0.3.7
- v0.3.6
- v0.3.5
- v0.3.4
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.69
- v0.2.68
- v0.2.67
- v0.2.66
- v0.2.65
- v0.2.64
- v0.2.63
- v0.2.62
- v0.2.61
- v0.2.60
- v0.2.59
- v0.2.58
- v0.2.57
- v0.2.56
- v0.2.55
- v0.2.54
- v0.2.53
- v0.2.52
- v0.2.51
- v0.2.50
- v0.2.49
- v0.2.48
- v0.2.47
- v0.2.46
- v0.2.45
- v0.2.44
- v0.2.43
- v0.2.42
- v0.2.41
- v0.2.40
- v0.2.39
- v0.2.38
- v0.2.37
- v0.2.36
- v0.2.35
- v0.2.34
- v0.2.33
- v0.2.32
- v0.2.31
- v0.2.30
- v0.2.29
- v0.2.28
- v0.2.27
- v0.2.26
- v0.2.25
- v0.2.24
- v0.2.23
- v0.2.22
- v0.2.21
- v0.2.20
- v0.2.19
- v0.2.18
- v0.2.17
- v0.2.16
- v0.2.15
- v0.2.14
- v0.2.13
- v0.2.12
- v0.2.11
- v0.2.10
- v0.2.9
- v0.2.8
- v0.2.7
- v0.2.6
- v0.2.5
- v0.2.4
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.1
- 0.1.0
- dev-v0.5-draft
This package is auto-updated.
Last update: 2024-12-16 21:19:55 UTC
README
A PHP SQL query builder that prioritizes readability and optimal performance with object based construction.
Table of contents
- SQL Builder
Installation ↑
This package can be installed with composer using following command.
composer require francerz/sql-builder
Connect to database ↑
Using an URI string
$db = DatabaseManager::connect('driver://user:password@host:port/database');
Using environment variable
putenv('DATABASE_SCHOOL_DRIVER', 'driver'); putenv('DATABASE_SCHOOL_HOST', 'host'); putenv('DATABASE_SCHOOL_INST', 'instanceName'); putenv('DATABASE_SCHOOL_PORT', 'port'); putenv('DATABASE_SCHOOL_USER', 'user'); putenv('DATABASE_SCHOOL_PSWD', 'password'); putenv('DATABASE_SCHOOL_NAME', 'database'); // Support to Docker secrets putenv('DATABASE_SCHOOL_PSWD_FILE', '/run/secrets/db_school_password'); $db = DatabaseManager::connect('school');
Basic common usage syntax ↑
class Group { public $group_id; public $subject; public $teacher; }
Select query ↑
// SELECT group_id, subject, teacher FROM groups $query = Query::selectFrom('groups', ['group_id', 'subject', 'teacher']); $db = DatabaseManager::connect('school'); $result = $db->executeSelect($query); $groups = $result->toArray(Group::class);
Insert query ↑
$group = new Group(); $group->subject = 'Database fundamentals'; $group->teacher = 'francerz'; // INSERT INTO groups (subject, teacher) VALUES ('Database fundamentals', 'francerz') $query = Query::insertInto('groups', $group, ['subject', 'teacher']); $db = DatabaseManager::connect('school'); $result = $db->executeInsert($query); $group->group_id = $result->getInsertedId();
Update query ↑
$group = new Group(); $group->group_id = 10; $group->subject = 'Introduction to databases'; // UPDATE groups SET subject = 'Introduction to databases' WHERE group_id = 10 $query = Query::update('groups', $group, ['group_id'], ['subject']); $db = DatabaseManager::connect('school'); $result = $db->executeUpdate($query);
Delete query ↑
// DELETE FROM groups WHERE group_id = 10 $query = Query::deleteFrom('groups', ['group_id' => 10]); $db = DatabaseManager::connect('school'); $result = $db->executeDelete($query);
Build SELECT with WHERE or HAVING clause ↑
Bellow are examples of using WHERE
clause which aplies to SELECT
, UPDATE
and DELETE
queries.
Selecting all fields from table
groups
when the value of columngroup_id
is equal to10
.
SELECT * FROM groups WHERE group_id = 10
// Explicit syntax $query = Query::selectFrom('groups')->where()->equals('group_id', 10); // Implicit syntax $query = Query::selectFrom('groups')->where('group_id', 10);
Selecting all fields from table
groups
when value of columngroup_id
is equals to10
,20
or30
.
SELECT * FROM groups WHERE group_id IN (10, 20, 30)
// Explicit syntax $query = Query::selectFrom('groups')->where()->in('group_id', [10, 20, 30]); // Implicit syntax $query = Query::selectFrom('groups')->where('group_id', [10, 20, 30]);
Selecting all fields from table
groups
when value of columnteacher
isNULL
.
SELECT * FROM groups WHERE teacher IS NULL
// Explicit syntax $query = Query::selectFrom('groups')->where()->null('teacher'); // Implicit compact syntax $query = Query::selectFrom('groups')->where('teacher', 'NULL');
Selecting all fields from table
groups
when value of columngroup_id
is less or equals to10
and value from columnsubject
contains the word"database"
.
SELECT * FROM groups WHERE group_id <= 10 AND subject LIKE '%database%'
// Explicit syntax $query = Query::selectFrom('groups'); $query->where()->lessEquals('group_id', 10)->andLike('subject', '%database%'); // Implicit compact syntax $query = Query::selectFrom('groups'); $query->where('group_id', '<=', 10)->andLike('subject', '%database%');
Parentheses syntax
To incorporate highly specific and intricate conditions, it becomes essential to override the default operator precedence, a task traditionally achieved through the use of parentheses in SQL syntax. Within the SQL Builder, this functionality is adeptly handled through the utilization of an anonymous function parameter.
Parentheses anonymous function works in the following syntax:
$query->where(function($subwhere) { }); $query->where->not(function($subwhere) { }); $query->where->and(function($subwhere) { }); $query->where->or(function($subwhere) { }); $query->where->andNot(function($subwhere) { }); $query->where->orNot(function($subwhere) { });
Selecting all fields from table
groups
when the value ofgroup_id
is equals to10
or is within the range from20
to30
.
SELECT * FROM groups WHERE subject LIKE '%database%' AND (group_id = 10 OR group_id BETWEEN 20 AND 30)
$query = Query::selectFrom('groups'); // Using an anonymous function to emulate parenthesis $query->where() ->like('subject', '%database%') ->and(function(ConditionList $subwhere) { $subwhere ->equals('group_id', 10) ->orBetween('group_id', 20, 30); });
List of operators ↑
The library provides a comprehensive array of operators that are largely
consistent across various SQL database engines. To enhance readability, it also
prefixes the and
and or
logical operators for clarity.
About
ConditionList
classThe examples of condition list, functions and operators applies in the same way to
WHERE
,HAVING
andON
clauses.
Building SELECT with JOIN ↑
One of the most common operations in relational databases is merging and
combining data from multiple tables. The join operations allow to combine the
data from multiple tables by using the INNER JOIN
, LEFT JOIN
, RIGHT JOIN
and CROSS JOIN
syntax.
SUPPORTED JOIN TYPES
Query Builder supports many types of JOIN
syntaxes:
// INNER JOIN $query->innerJoin($table, $columns = []); // CROSS JOIN $query->crossJoin($table, $columns = []); // LEFT JOIN $query->leftJoin($table, $columns = []); // RIGHT JOIN $query->rightJoin($table, $columns = []);
SQL Join Syntax Compatibility:
Join Syntax is available to
SELECT
,UPDATE
andDELETE
sql syntax, however, not all database engines might support it.
Examples
SELECT * FROM groups INNER JOIN teachers ON groups.teacher_id = teachers.teacher_id
$query = Query::selectFrom('groups'); $query ->innerJoin('teachers') ->on('groups.teacher_id', 'teachers.teacher_id');
Using table aliases to reduce naming length.
SELECT * FROM groups AS g INNER JOIN teachers AS t ON g.teacher_id = t.teacher_id
// Alias array syntax $query = Query::selectFrom(['g' => 'groups']); $query ->innerJoin(['t' => 'teachers']) ->on('g.teacher_id', 't.teacher_id'); // Alias "AS" string syntax $query = Query::selectFrom('groups AS g'); $query ->innerJoin('teachers AS t') ->on('g.teacher_id', 't.teacher_id');
Multiple database (same host) select with join.
SELECT * FROM school.groups AS g INNER JOIN hr.employees AS e ON g.teacher_id = e.employee_id
$query = Query::selectFrom('school.groups AS g'); $query ->innerJoin('hr.employees AS e') ->on('g.teacher_id','e.employee_id');
Selecting fields from joined tables.
SELECT g.group_id, t.given_name, t.family_name FROM groups AS g INNER JOIN teachers AS t ON g.teacher_id = t.teacher_id
$query = Query::selectFrom('groups AS g', ['group_id']); $query ->innerJoin('teachers AS t', ['given_name', 'family_name']) ->on('g.teacher_id', 't.teacher_id');
Renaming fields from joined tables.
SELECT g.group_id, CONCAT(t.given_name, ' ', t.family_name) AS teacher_name FROM groups AS g INNER JOIN teachers AS t ON g.teacher_id = t.teacher_id
$query = Query::selectFrom('groups AS g', ['group_id']); $query ->innerJoin('teachers AS t', [ 'teacher_name' => "CONCAT(t.given_name, ' ', t.family_name)" ])->on('g.teacher_id', 't.teacher_id');
Selecting columns into an external function (cleaner code).
SELECT g.group_id, CONCAT(t.given_name, ' ', t.family_name) AS teacher_name FROM groups AS g INNER JOIN teachers AS t ON g.teacher_id = t.teacher_id
$query = Query::selectFrom('groups AS g'); $query ->innerJoin('teachers AS t') ->on('g.teacher_id', 't.teacher_id'); $query->columns([ 'g.group_id', 'teacher_name' => "CONCAT(t.given_name, ' ', t.family_name)" ]);
Join tables and subqueries.
-- Gets all groups of active teachers SELECT g.group_id, CONCAT(t.given_name, ' ', t.family_name) AS teacher_name FROM groups AS g INNER JOIN (SELECT * FROM teachers WHERE active = 1) AS t ON g.teacher_id = t.teacher_id
// Creating subquery object $subquery = Query::selectFrom('teachers'); $subquery->where('active', 1); $query = Query::selectFrom('groups AS g'); $query ->innerJoin(['t' => $subquery]) ->on('g.teacher_id', 't.teacher_id'); $query->columns([ 'g.group_id', 'teacher_name' => "CONCAT(t.given_name, ' ', t.family_name)" ]);
SELECT nesting ↑
In some cases, simple database table joining isn't sufficient for meeting all data requirements. It's common to need to execute additional filtered queries for each row in the result of a primary query.
However, this approach often leads to overly complex code and performance issues due to increased loops and database access roundtrips. To address these challenges, a more efficient and lightweight syntax is available for querying nested data.
Nesting a Collection of Result Objects
The nestMany
method is used to nest a collection of result objects within each
row of the primary query's result. In the provided example, this is used to
associate multiple students with their respective groups. This approach is
suitable when you expect multiple related records for each main record.
// Primary Query for Groups $groupsQuery = Query::selectFrom( 'groups', ['group_id', 'subject', 'classroom'] ); // Query for Students $studentsQuery = Query::selectFrom( 'students', ['student_id', 'group_id', 'first_name', 'last_name'] ); // Nesting students within each group $groupsQuery ->nestMany('Students', $studentsQuery, $groupRow, Student::class) ->where('students.group_id', $groupRow->group_id); // Connecting to the database and executing the query $db = DatabaseManager::connect('school'); $result = $db->executeSelect($groupsQuery); $groups = $result->toArray(Group::class);
Result:
[ { "group_id": 1, "subject": "Programing fundamentals", "classroom": "A113", "Students": [ { "student_id": 325, "first_name": "Charlie", "last_name": "Ortega" }, { "student_id": 743, "first_name": "Beth", "last_name": "Wilson" } ] }, { "group_id": 2, "subject" : "Object Oriented Programming", "classroom": "G7-R5", "Students": [ { "student_id": 536, "first_name": "Dylan", "last_name": "Morrison" } ] } ]
Nesting the First or Last Result Object
On the other hand, the linkFirst
method is employed to link only the first
result object from a secondary query with each row of the primary query's
result. In the given code snippet, this is used to link the first teacher to
each group. This method is beneficial when you want to link a single related
record to each main record, prioritizing the first match.
Additionally, there is the linkLast
method, which is similar to linkFirst
but instead links the last result object from a secondary query to each row of
the primary query's result. This can be useful in scenarios where you want to
prioritize the most recent or latest related record for each main record.
// Primary Query for Groups $groupsQuery = Query::selectFrom( 'groups', ['group_id', 'teacher_id', 'subject', 'classroom'] ); // Query for Teachers $teachersQuery = Query::selectFrom( 'teachers', ['teacher_id', 'first_name', 'last_name'] ); // Linking the first teacher to each group $groupsQuery ->linkFirst('Teacher', $teachersQuery, $groupRow, Teacher::class) ->where('teachers.teacher_id', $groupRow->teacher_id); // Query for Classes $classesQuery = Query::selectFrom( 'groups_classes', ['class_id', 'group_id', 'topic', 'date'] )->orderBy('date', 'ASC'); // Linking the last class to each group $groups ->linkLast('LastClass', $classesQuery, $groupRow, GroupClass::class) ->where('groups_classes.group_id', $groupRow->group_id); // Connecting to the database and executing the query $db = DatabaseManager::connect('school'); $result = $db->executeSelect($groupsQuery); $groups = $result->toArray(Group::class);
Result:
[ { "group_id": 1, "teacher_id": 3, "subject": "Programming fundamentals", "classroom": "A113", "Teacher": { "teacher_id": 3, "first_name": "Rosemary", "last_name": "Smith" }, "LastClass": { "class_id": 233, "group_id": 1, "topic": "Algorithms", "date": "2024-04-18" } }, { "group_id": 2, "teacher_id": 75, "subject" : "Object Oriented Programming", "classroom": "G7-R5", "Teacher": { "teacher_id": 75, "first_name": "Steve", "last_name": "Johnson" }, "LastClass": null } ]
By choosing the appropriate nesting mode (nestMany
, linkFirst
, or
linkLast
), you can tailor your queries to efficiently handle nested data based
on your specific data structure and requirements.
Legacy old nest syntax
There is a legacy nest syntax, that stills working underhood.
$groupsQuery->nest(['Students' => $studentsQuery], function (NestedSelect $nest, RowProxy $row) { $nest->getSelect()->where('s.group_id', $row->group_id); }, NestMode::COLLECTION, Student::class);
Transactions ↑
One of the most important features in databases is to keep data consistency across multiple records that might be stored in multiple tables.
$db = DatabaseManager::connect('school'); try { $db->startTransaction(); // Perform any needed operation inside this block to keep consistency. $db->commit(); } catch (Exception $ex) { $db->rollback(); }
Executing Stored Procedures ↑
// Connecting to database 'school'. $db = DatabaseManager::connect('school'); // Calls stored procedure with two argments. /** @var SelectResult[] */ $results = $db->call('procedure_name', 'arg1', 'arg2'); // Shows how many results obtained from procedure. echo count($results) . ' results.' . PHP_EOL; // Iterating procedure result sets. foreach ($results as $i => $selectResult) { echo "Fetched " . $selectResult->getNumRows() . PHP_EOL; }