VV database abstraction layer with query builder and DB structure models.
This is the basic package. To use it with concrete DBMS install one of these drivers using Composer:
- db-mysqli - MySQL driver over MySQLi extension;
- db-pdo - PostgreSQL (and other) driver over PDO extension;
- db-oci - Oracle driver over oci8 extension.
composer require phpvv/db-mysqli
# or
composer require phpvv/db-pdo
# or
composer require phpvv/db-oci
big-select.php in DB Playground:
use App\Db\MainDb;
use VV\Db\Param;
use VV\Db\Sql;
$db = MainDb::instance();
$categoryId = 102;
$stateParam = Param::int(value: 1, name: 'state');
$query = $db->tbl->product
->select('title', 'b.title brand', 'price', 'c.title as color') // SELECT p.title, ... FROM tbl_product as p
->join($db->tbl->brand) // INNER JOIN tbl_brand as b ON b.brand_id = p.brand_id
->left($db->tbl->color, 'p') // LEFT JOIN tbl_color as c ON c.color_id = p.color_id
// ->left($db->tbl->color, on: 'c.color_id = p.color_id', alias: 'c') // same as above
->where( // WHERE ...
Sql::condition()
->exists( // WHERE EXISTS (
$db->tbl->productsCategories->select('1') // SELECT 1 FROM tbl_products_categories pc
->where('`pc`.`product_id`=`p`.`product_id`') // WHERE ("pc"."product_id"="p"."product_id")
->where([
'category_id' => $categoryId, // AND pc.category_id = :p1
'state' => $stateParam // AND pc.state = :state
])
) // )
->and('state')->eq($stateParam) // AND p.state = :state
// ->and('title')->like('Comp%')
->and('title')->startsWith('Comp') // AND p.title LIKE :p2 -- 'Comp%'
->and('price')
->gte(1000) // AND p.price >= :p3
->and
->lte(2000) // AND p.price <= :p4
->and('weight')->between(7000, 15000) // AND p.weight BETWEEN :p5 AND :p6
->and( // AND (
Sql::condition('width') // nested condition
->lt(500) // p.width > :p7
->or // OR
->isNull() // p.width IS NULL
) // )
->and('c.color_id')->in(1, 5, null) // AND (c.color_id IN (:p8, :p9) OR c.color_id IS NULL)
->and('brand_id')->not->in(3, 4) // AND (p.brand_id NOT IN (:p10, :p11) OR p.brand_id IS NULL)
->and('height')->isNotNull() // AND p.height IS NOT NULL
)
// ->groupBy(...)
// ->having(...)
->orderBy( // ORDER BY
Sql::case('p.color_id')
->when(5)->then(1) // color_id = 5 - first
->when(1)->then(2) // color_id = 1 - second
->else(3), // other colors at the end - first
// Sql::case()
// ->when(Sql::condition('p.color_id')->eq(5))->then(1) // same CASE as above
// ->when(['p.color_id' => 1])->then(2)
// ->else(3),
'-price', // price DESC,
'title' // title ASC
)
->limit(10); // LIMIT 10
echo "SQL:\n", $query->toString(), "\n\n";
// $rows = $query->rows;
// $rows = $query->rows(\VV\Db::FETCH_NUM);
// $rows = $query->rows(null, keyColumn: 'product_id', decorator: function (&$row, &$key) => { /*...*/ });
// $row = $query->row;
// $row = $query->row(\VV\Db::FETCH_NUM | \VV\Db::FETCH_LOB_NOT_LOAD);
// $title = $query->column;
// $brand = $query->column(1);
/*
$statement = $query->prepare();
print_r($statement->result()->rows);
$stateParam->setValue(0);
print_r($statement->result()->rows);
$statement->close();
*/
echo "rows:\n";
foreach ($query->result as $row) {
print_r($row);
}
Output:
SQL:
SELECT "p"."title", "b"."title" "brand", "p"."price", "c"."title" "color"
FROM "tbl_product" "p"
JOIN "tbl_brand" "b" ON ("b"."brand_id" = "p"."brand_id")
LEFT JOIN "tbl_color" "c" ON ("c"."color_id" = "p"."color_id")
WHERE (
EXISTS (
SELECT 1
FROM "tbl_products_categories" "pc"
WHERE ("pc"."product_id" = "p"."product_id")
AND "category_id" = :p1
AND "state" = :state
)
AND "p"."state" = :state
AND "p"."title" LIKE :p2
AND "p"."price" >= :p3
AND "p"."price" <= :p4
AND "p"."weight" BETWEEN :p5 AND :p6
AND ("p"."width" < :p7 OR "p"."width" IS NULL)
AND ("c"."color_id" IN (:p8, :p9) OR "c"."color_id" IS NULL)
AND ("p"."brand_id" NOT IN (:p10, :p11) OR "p"."brand_id" IS NULL)
AND "p"."height" IS NOT NULL
)
ORDER BY (
CASE "p"."color_id"
WHEN 5 THEN 1
WHEN 1 THEN 2
ELSE 3
END
) NULLS FIRST,
"price" DESC NULLS LAST,
"title" NULLS FIRST
LIMIT 10
rows:
Array
(
[title] => Computer 11
[brand] => Brand 2
[price] => 1500.00
[color] => White
)
Array
(
[title] => Computer 10
[brand] => Brand 1
[price] => 1000.00
[color] => Black
)
Array
(
[title] => Computer 12
[brand] => Brand 1
[price] => 1200.00
[color] =>
)
big-transaction.php in DB Playground:
use App\Db\MainDb;
use VV\Db\Param;
$db = MainDb::instance();
$userId = 1;
$cart = [
// productId => quantity
10 => rand(1, 2),
20 => rand(1, 3),
40 => rand(1, 5),
];
$productIterator = $db->tbl->product->select('product_id', 'price')
->whereIdIn(...array_keys($cart))
->result(\VV\Db::FETCH_NUM);
$transaction = $db->startTransaction();
try {
$orderId = $db->tbl->order->insert()
->set([
'user_id' => $userId,
'date_created' => new \DateTime(),
])
->insertedId($transaction);
// variants:
switch (3) {
case 1:
// build and execute queries for each item
foreach ($productIterator as [$productId, $price]) {
$db->tbl->orderItem->insert()
->set([
'order_id' => $orderId,
'product_id' => $productId,
'price' => $price,
'quantity' => $cart[$productId],
])
->exec($transaction);
}
break;
case 2:
// build and execute one query for all items
$insertItemQuery = $db->tbl->orderItem->insert()
->columns('order_id', 'product_id', 'price', 'quantity');
foreach ($productIterator as [$productId, $price]) {
$insertItemQuery->values($orderId, $productId, $price, $cart[$productId]);
}
$insertItemQuery->exec($transaction);
break;
case 3:
// prepare query and execute it for each item
$prepared = $db->tbl->orderItem->insert()
->set([
'order_id' => Param::int($orderId),
'product_id' => $productIdParam = Param::str(size: 16),
'price' => $priceParam = Param::str(size: 16),
'quantity' => $quantityParam = Param::str(size: 16),
]);
foreach ($productIterator as [$productId, $price]) {
$productIdParam->setValue($productId);
$priceParam->setValue($price);
$quantityParam->setValue($cart[$productId]);
$prepared->exec($transaction);
}
break;
}
$db->tbl->order->update()
->set(
'amount',
$db->tbl->orderItem->select('SUM(price * quantity)')->where('order_id=o.order_id')
)
->whereId($orderId)
// ->exec() // throws an exception that you are trying to execute statement
// outside of transaction started for current connection
->exec($transaction);
// you can execute important statement in transaction free connection
$db->tbl->log->insert()
->set(['title' => "new order #$orderId"])
->exec($db->getFreeConnection());
// throw new \RuntimeException('Test transactionFreeConnection()');
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollback();
/** @noinspection PhpUnhandledExceptionInspection */
throw $e;
}
Example (connection.php):
use APP\DB\MAIN as CONF;
use VV\Db\Connection;
use VV\Db\Pdo\Driver;
// $driver = new \VV\Db\Oci\Driver;
// $driver = new \VV\Db\Mysqli\Driver;
$driver = new Driver(Driver::DBMS_POSTGRES);
$connection = new Connection($driver, CONF\HOST, CONF\USER, CONF\PASSWD, CONF\DBNAME);
// $connection->connect(); // auto connect on first query is enabled by default
// all variants do same job:
$queryString = 'SELECT product_id, title FROM tbl_product WHERE price > :price';
$result = $connection->query($queryString, ['price' => 100]);
// or
$result = $connection->prepare($queryString)->bind(['price' => 100])->result();
// or
$result = $connection->select('product_id', 'title')->from('tbl_product')->where('price > ', 100)->result();
print_r($result->rows);
At start, it is needed to create somewhere class <MyNameOf>Db extends \VV\Db
and implement one abstract method createConnection()
.
Example (App/Db/MainDb.php):
namespace App\Db;
use APP\DB\MAIN as CONF;
use VV\Db\Connection;
use VV\Db\Pdo\Driver;
/**
* @method MainDb\TableList tables()
* @method MainDb\ViewList views()
* @property-read MainDb\TableList $tbl
* @property-read MainDb\TableList $vw
*/
class MainDb extends \VV\Db {
public function createConnection(): Connection {
$driver = new Driver(Driver::DBMS_POSTGRES);
return new Connection($driver, CONF\HOST, CONF\USER, CONF\PASSWD, CONF\DBNAME);
}
}
Just run this code (gen-db-model.php):
use App\Db\MainDb;
use VV\Db\Model\Generator\ModelGenerator;
(new ModelGenerator(MainDb::instance()))->build();
DB schema representation classes will be created in the App\Db\MainDb
folder.
Example (db-model.php):
use App\Db\MainDb;
$db = MainDb::instance();
$products = $db->tbl->product
->select('product_id', 'b.title brand', 'title', 'price')
->join($db->tbl->brand)
->where('brand_id', 1)
->where('price >', 100)
->rows;
print_r($products);
Create SelectQuery
There are several variants to create SelectQuery
(create-select-query.php):
use App\Db\MainDb;
$db = MainDb::instance();
$connection = $db->getConnection(); // or $db->getFreeConnection();
// $connection = new \VV\Db\Connection(...);
$columns = ['product_id', 'b.title brand', 'title', 'price'];
// from `Connection` directly:
$selectQuery = $connection->select(...$columns)->from('tbl_product');
// from `Db`:
$selectQuery = $db->select(...$columns)->from('tbl_product');
// from `Table` (recommended):
$selectQuery = $db->tbl->product->select(...$columns);
From Result
Fetch single row or cell (execute-select-query.php):
$query = $db->tbl->product->select('product_id', 'title')->whereId(10);
$row = $query->result->row/*($flags)*/);
$productId = $query->result->cell/*($columnIndex[, $flags])*/);
Fetch all rows or column (execute-select-query.php):
$query = $db->tbl->product->select('product_id', 'title', 'b.title brand')->join($db->tbl->brand)->limit(3);
$result = $query->result;
while (['product_id' => $productId, 'title' => $title] = $result->fetch()) {
echo "$productId: $title\n";
}
// or
$rowIterator = $query->result(Db::FETCH_NUM);
foreach ($rowIterator as [$productId, $title, $brand]) {
echo "$productId: $brand $title\n";
}
// or
$rows = $query->result->rows/*($flags)*/;
// or
$assoc = $query->result()->assoc/*(keyColumn: 'product_id'[, valueColumn: 'title'])*/;
//
You can set fetch mode flags to fetch()
, row()
, rows()
, column()
:
use VV\Db;
$rows = $query->rows(
Db::FETCH_ASSOC // column name as key; default fetch mode (if $flags === null)
| Db::FETCH_NUM // column index as key
| Db::FETCH_LOB_OBJECT // return LOB object instead of LOB content (only for Oracle yet)
);
Fetch result directly from query (execute-select-query.php):
$query = $db->tbl->product->select('product_id', 'title', 'brand_id')->limit(3);
$assocRows = $query->rows;
$numRows = $query->rows(Db::FETCH_NUM);
$decoratedRows = $query->rows(decorator: function (&$row, &$key) {
$key = $row['product_id'] . '-' . $row['brand_id'];
$row = array_values($row);
});
$assoc = $query->rows(keyColumn: 'product_id', decorator: 'title');
$assoc = $query->assoc;
$assocRow = $query->row;
$bothRow = $query->row(Db::FETCH_NUM | Db::FETCH_ASSOC);
$productIdList = $query->column;
$titleList = $query->column(1);
$productId = $query->cell;
$title = $query->cell(1);
Method select(...)
(see above) returns SelectQuery
object. You can change columns using methods SelectQuery::columns()
or SelectQuery::addColumns()
(select.php):
$query = $db->tbl->product->select()
->columns('product_id', 'brand_id')
->addColumns('title', 'price');
All these methods accepts string
or Expression
interface as each column. So you can do something like this (select.php):
$query = $db->tbl->product->select(
'product_id',
'title product',
$db->tbl->brand
->select('title')
->where('b.brand_id = p.brand_id')
->as('brand'),
'price',
Sql::case()
->when(['price >= ' => 1000])->then(1)
->else(0)
->as('is_expensive'),
);
To set table or view to query you can call from()
method or create query directly from Table
or View
(from.php):
$query = $db->tbl->product->select(/*...*/);
// or
$query = $db->select(/*...*/)->from($db->tbl->product); // same as above
// or
$query = $db->select(/*...*/)->from('tbl_product'); // not same as above regarding JOIN clause
By default, alias of table (or view) consists of first letters of each word of table (or view) name without prefix (tbl_
,t_
, vw_
, v_
). For example: tbl_order
-> o
, tbl_order_item
-> oi
.
To change table alias, call mainTableAs()
method of query (from.php):
$query = $db->tbl->product->select(/*...*/)->mainTableAs('prod');
To set JOIN clause use these methods: join()
, left()
, right()
, full()
.
Example (join.php):
$query = $db->tbl->orderItem->select(/*...*/) // SELECT ... FROM "tbl_order_item" "oi"
->join($db->tbl->order) // JOIN "tbl_order" "o" ON "o"."order_id" = "oi"."order_id"
->left($db->tbl->orderState) // LEFT JOIN "tbl_order_state" "os" ON "os"."state_id" = "o"."state_id"
->join($db->tbl->product, 'oi') // JOIN "tbl_product" "p" ON "p"."product_id" = "oi"."product_id"
->where('o.state_id', 1); // WHERE "o"."state_id" = ?
By default, table joins to previous table by primary key column. Default alias of table is first letters of each word of table name. You can change ON condition (second parameter) and alias (third parameter) (join.php):
$query = $db->tbl->orderItem->select(/*...*/)
->join(
$db->tbl->order,
'order.order_id=oi.order_id', // string
'order'
)
->left(
$db->tbl->orderState,
Sql::condition() // Condition object
->and('os.state_id')->eq(Sql::expression('o.state_id'))
->and('os.state_id')->eq(1)
);
Specify alias of table to which join is needed (join.php):
$query = $db->tbl->orderItem->select(/*...*/)
->join($db->tbl->order)
->join($db->tbl->product, 'oi'); // join to tbl_order_item (not tbl_order) by product_id field
Specify column of table to which join is needed (join.php):
$query = $db->tbl->orderItem->select(/*...*/)
->join($db->tbl->order, '.foo_id'); // "o"."order_id" = "oi"."foo_id"
Specify alias and column of table to which join is needed (join.php):
$query = $db->tbl->orderItem->select(/*...*/)
->join($db->tbl->order)
->join($db->tbl->product, 'oi.foo_id'); // "p"."product_id" = "oi"."foo_id"
joinParent()
(join.php):
$query = $db->tbl->productCategory->select(/*...*/)
//->joinParent('pc2', 'pc', 'parent_id') // same as below
->joinParent('pc2'); // JOIN "tbl_product_category" "pc2" ON ("pc2"."category_id" = "pc"."parent_id")
joinBack()
(join.php):
$query = $db->tbl->order->select(/*...*/)
->joinBack($db->tbl->orderItem); // JOIN "tbl_order_item" "oi" ON ("oi"."item_id" = "o"."order_id")
Nest resulting columns manually (nested-columns.php):
$query = $db->tbl->product->select('product_id', 'price', 'weight')
->addNestedColumns('brand', 'b.brand_id', 'b.title') // first argument is nesting path: string|string[]
->addNestedColumns(['nested', 'color'], 'c.color_id', 'c.title')
->addNestedColumns(['nested', 'size'], 'width', 'height', 'depth')
->join($db->tbl->brand)
->join($db->tbl->color, 'p');
print_r($query->row);
Result:
Array
(
[product_id] => 10
[price] => 1000.00
[weight] => 10000
[brand] => Array
(
[brand_id] => 1
[title] => Brand 1
)
[nested] => Array
(
[color] => Array
(
[color_id] => 1
[title] => Black
)
[size] => Array
(
[width] => 250.0
[height] => 500.0
[depth] => 500.0
)
)
)
Nest resulting columns with join (nested-columns.php):
$query = $db->tbl->orderItem->select('item_id', 'price', 'quantity')
->joinNestedColumns(
$db->tbl->order->select('order_id', 'amount', 'comment'), // sub query
'oi.order_id', // ON condition (see above)
['my', 'nested', 'order'] // nesting path
)
->joinNestedColumns(
$db->tbl->product->select('product_id', 'title') // sub query
->joinNestedColumns(
$db->tbl->brand->select('brand_id', 'title'), // sub sub query
'p.brand_id'
)
->joinNestedColumns($db->tbl->color, 'p', 'color'), // just join table - select all its columns
'oi.product_id',
'product'
);
print_r($query->row);
Result:
Array
(
[item_id] => 1
[price] => 1000.00
[quantity] => 1
[my] => Array
(
[nested] => Array
(
[order] => Array
(
[order_id] => 1
[amount] => 1133.47
[comment] =>
)
)
)
[product] => Array
(
[brand_id] => Array
(
[brand_id] => 1
[title] => Brand 1
)
[color] => Array
(
[color_id] => 1
[title] => Black
)
[product_id] => 10
[title] => Computer 10
)
)
To set query condition use where()
method. Each where()
adds AND
condition.
Method accepts:
Condition
as first argument (where.php):
$query = $db->tbl->product->select(/*...*/)
->where( // WHERE
Sql::condition()
->and('color_id')->eq(5) // ("color_id" = ?
->and('price')->lte(2000) // AND "price" <= ?
->and('title')->isNotNull() // AND "title" IS NOT NULL
->and('brand_id')->in(2, 3) // AND "brand_id" IN (?, ?)
->and('width')->between(250, 350) // AND "width" BETWEEN ? AND ?
->and('state=1')->custom() // AND state=1) -- w/o quotes: custom query not changed
);
Expression|string
as first argument and (binding) value (orExpression
) to compare as second argument (where.php):
$query = $db->tbl->product->select(/*...*/)
->where('color_id', 5) // same: `->where('color_id =', 5)`
->where(
'price <=', // supported operators: = | != | <> | < | > | <= | >=
$db->tbl->product->select('AVG(price)') // HEAVING clause described below
)
->where('title !=', null);
string
as custom SQL as first argument and (binding) array of values as second argument (where.php):
$query = $db->tbl->product->select(/*...*/)
->where('`width` BETWEEN ? AND ?', [250, 350]) // custom sql with binding parameters
->where('state=1'); // custom sql w/o binding parameters
- array as first argument ($expression => $parameter) (where.php):
$query = $db->tbl->product->select(/*...*/)
->where([
'color_id' => 5,
'price <=' => 2000,
'title !=' => null,
Sql::condition('brand_id')->eq(2)->or->eq(3), // AND ("brand_id" = ? OR "brand_id" = ?)
'`width` BETWEEN ? AND ?' => [250, 350],
'state=1',
]);
Query has some shortcuts methods:
->whereId(1)
(for$db->tbl->product->select()
-product_id = ?
);->where[Not]In('brand_id', 1, 2, 3)
;->whereId[Not]In(1, 2, 3)
;->where[Not]Between('width', 250, 350)
;->where[Not]Like('title', 'computer%', caseInsensitive: true)
.
To set GROUP BY clause use groupBy()
method that behaves like columns()
(see Columns Clause).
To set condition for aggregate use having()
method that behaves like where()
(see Where Clause).
Example (group-by-having.php):
$query = $db->tbl->product->select('b.title brand', 'COUNT(*) cnt')
->join($db->tbl->brand)
->groupBy('b.title')
->having('COUNT(*) > ', 1);
Simple order by columns (order-by.php):
$query = $db->tbl->product->select('b.title brand', 'p.title product', 'price')
->left($db->tbl->brand)
->orderBy( // ORDER BY
'b.title', // "b"."title" NULLS FIRST,
'-price' // "price" DESC NULLS LAST
);
Order by expression (CASE for example) (order-by.php):
$query = $db->tbl->product->select('p.title product', 'color_id')
->orderBy( // ORDER BY
Sql::case('color_id') // CASE "color_id"
->when(5)->then(1) // WHEN 5 THEN 1
->when(1)->then(2) // WHEN 1 THEN 2
->else(100) // ELSE 100 END
);
Use ->limit($count, $offset)
(limit.php):
$query = $db->tbl->product->select()->orderBy('product_id')->limit(3, 2);
Use ->union[All](...$queries)
, ->intersect[All](...$queries)
, ->except[All](...$queries)
(union-intersect-except):
$query = $db->select()
->unionAll(
$db->tbl->product->select('product_id', 'title')->orderBy('product_id')->limit(2),
$db->tbl->product->select('product_id', 'title')->orderBy('-product_id')->limit(2),
)
->except(
$db->tbl->product->select('product_id', 'title')->orderBy('product_id')->limit(5, 1),
)
->orderBy('product_id');
Create InsertQuery
There are several variants to create InsertQuery
(create-insert-query.php):
use App\Db\MainDb;
$db = MainDb::instance();
$connection = $db->getConnection(); // or $db->getFreeConnection();
// $connection = new \VV\Db\Connection(...);
// from Connection directly:
$insertQuery = $connection->insert()->into('tbl_order');
// from Db:
$insertQuery = $db->insert()->into('tbl_order');
// from Table (recommended):
$insertQuery = $db->tbl->order->insert();
// $insertQuery = $connection->insert()->into($db->tbl->order);
Last variant is preferable due to adjusting type of inserted value to column type:
$db->tbl->foo->insert([
'int_column' => true, // inserts `1` (or `0` for `false`)
'bool_column' => 1, // inserts `true` for `1` and `false` for `0` (exception for other numbers)
'date_column' => new \DateTimeImmutable(), // inserts date only: `Y-m-d`
'time_column' => new \DateTimeImmutable(), // inserts time only: `H:i:s`
'timestamp_column' => new \DateTimeImmutable(), // inserts date and time: `Y-m-d H:i:s`
'timestamp_tz_column' => new \DateTimeImmutable(), // inserts date and time with time zone: `Y-m-d H:i:s P`
]);
Execute InsertQuery
Just execute:
$result = $query->exec();
Get inserted ID (autoincrement) or affected rows (execute-insert-query.php):
$result = $query->initInsertedId()->exec();
$id = $result->insertedId();
$affectedRows = $result->affectedRows();
Execute query and return inserted ID or affected rows (execute-insert-query.php):
$id = $query->insertedId(); // executes Insert
$affectedRows = $query->affectedRows(); // executes Insert too
Regular insert query (insert-single-row.php):
$query = $db->tbl->order->insert()
->columns('user_id', 'comment')
->values(1, 'my comment');
Insert assignment list (insert-single-row.php):
$query = $db->tbl->order->insert()
// ->set([
// 'user_id' => 1,
// 'comment' => 'my comment',
// ])
->set('user_id', 1)
->set('comment', 'my comment');
Shortcut (executes query) (insert-single-row.php):
$insertedId = $db->tbl->order->insert([
'user_id' => 1,
'date_created' => new \DateTime(),
]);
Insert values list (insert-multiple-rows.php):
$query = $db->tbl->orderItem->insert()->columns('order_id', 'product_id', 'price', 'quantity');
foreach ($valuesList as $values) {
$query->values(...$values);
}
Insert from Select (insert-multiple-rows.php):
// copy order
$newOrderId = $db->tbl->order->insert([
'user_id' => $userId,
'date_created' => new \DateTime(),
]);
$query = $db->tbl->orderItem->insert()
->columns('order_id', 'product_id', 'price', 'quantity')
->values(
$db->tbl->orderItem
->select((string)$newOrderId, 'product_id', 'price', 'quantity')
->where('order_id', $orderId)
);
Insert values list executing statement per N rows (insert-multiple-rows.php):
$query = $db->tbl->orderItem->insert()
->columns('order_id', 'product_id', 'price', 'quantity')
->execPer(1000);
foreach ($valuesList as $values) {
$query->values(...$values);
}
$query->execPerFinish(); // exec last
Create UpdateQuery
There are several variants to create UpdateQuery
(create-update-query.php):
use App\Db\MainDb;
$db = MainDb::instance();
$connection = $db->getConnection(); // or $db->getFreeConnection();
// $connection = new \VV\Db\Connection(...);
// from Connection directly:
$updateQuery = $connection->update()->table('tbl_order');
// from Db:
$updateQuery = $db->update()->table('tbl_order');
// from Table (recommended):
$updateQuery = $db->tbl->order->update();
// $updateQuery = $connection->update()->table($db->tbl->order);
Last variant is preferable due to adjusting type of updated value to column type:
$db->tbl->foo->update([
'int_column' => true, // sets `1` (or `0` for `false`)
'bool_column' => 1, // sets `true` for `1` and `false` for `0` (exception for other numbers)
'date_column' => new \DateTimeImmutable(), // sets date only: `Y-m-d`
'time_column' => new \DateTimeImmutable(), // sets time only: `H:i:s`
'timestamp_column' => new \DateTimeImmutable(), // sets date and time: `Y-m-d H:i:s`
'timestamp_tz_column' => new \DateTimeImmutable(), // sets date and time with time zone: `Y-m-d H:i:s P`
]);
Execute UpdateQuery
Just execute:
$result = $query->exec();
Get affected rows (execute-update-query.php):
$affectedRows = $result->affectedRows();
Execute query and return affected rows (execute-update-query.php):
$affectedRows = $query->affectedRows();
Method set()
accepts column name as first argument and value (or Expression
) as second argument or array column => value
.
WHERE clause is required for UpdateQuery
. To set condition use where()
method or its shortcuts (see select query where clause).
To update all rows just set something like this: ->where('1=1')
.
Example (update.php):
$query = $db->tbl->order->update()
// ->set([
// 'amount' => 1000,
// 'state_id' => 1,
// ])
->set('amount', rand(10_00, 10000_00) * 0.01)
->set('state_id', rand(1, 3))
->whereId(2);
Shortcut (executes query) (update.php):
$affectedRows = $db->tbl->order->update(
[
'amount' => rand(1_00, 10_000_00) * 0.01,
'state_id' => rand(1, 3),
],
3
// ['order_id' => 3]
// Sql::condition()->and('order_id')->eq(3)
);
Update with Expression
(SelectQuery
, CaseExpression
, ...) (update.php):
$query = $db->tbl->order->update()
->set(
'amount',
$db->tbl->orderItem
->select('SUM(price * quantity)')
->where('order_id=o.order_id')
)
->where('amount', null);
Create DeleteQuery
There are several variants to create DeleteQuery
(create-delete-query.php):
use App\Db\MainDb;
$db = MainDb::instance();
$connection = $db->getConnection(); // or $db->getFreeConnection();
// $connection = new \VV\Db\Connection(...);
// from Connection directly:
$deleteQuery = $connection->delete()->into('tbl_order');
// from Db:
$deleteQuery = $db->delete()->into('tbl_order');
// from Table (recommended):
$deleteQuery = $db->tbl->order->delete();
// $deleteQuery = $connection->delete()->from($db->tbl->order);
Execute DeleteQuery
See execute UpdateQuery section:
$result = $query->exec();
$affectedRows = $result->affectedRows();
// or
$affectedRows = $query->affectedRows();
WHERE clause is required for DeleteQuery
. To set condition use where()
method or its shortcuts (see WHERE clause section).
To delete all rows just set something like this: ->where('1=1')
.
Example (delete.php):
$query = $db->tbl->orderItem->delete()
->where(
'price > ',
$db->tbl->orderItem
->select('AVG(price)')
->mainTableAs('oi2')
->where('oi2.order_id=oi.order_id')
);
To start transaction for connection use startTransaction()
:
$transaction = $connection->startTransaction();
// or
$transaction = $db->startTransaction();
To commit or rollback changes use Transaction
s commit()
or rollback()
methods:
try {
// ...
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollback();
// ...
}
To execute query inside transaction pass Transaction
object to method exec($transaction)
(or affectedRows($transaction)
, or insertedId($transaction)
).
Query execution for Connection
with started transaction without passing Transaction
to exec()
throws exception.
To overcome this use transaction free connection ($db->getFreeConnection()
).
Example (copy-order.php):
$transaction = $db->startTransaction();
try {
$newOrderId = $db->tbl->order->insert()
->set([
'user_id' => $userId,
'date_created' => new \DateTime(),
])
->insertedId($transaction);
echo "new Order ID: $newOrderId\n";
$affectedRows = $db->tbl->orderItem->insert()
->columns('order_id', 'product_id', 'price', 'quantity')
->values(
$db->tbl->orderItem
->select((string)$newOrderId, 'product_id', 'price', 'quantity')
->where('order_id', $orderId)
)
->affectedRows($transaction);
echo "copied Order items: $affectedRows\n";
$db->tbl->order->update()
->set(
'amount',
$db->tbl->orderItem->select('SUM(price * quantity)')->where('order_id=o.order_id')
)
->whereId($newOrderId)
->exec($transaction);
// you can execute important statement in transaction free connection
$db->tbl->log->insert()
->set(['title' => "new order copy #$newOrderId"])
->exec($db->getFreeConnection());
// throw new \RuntimeException('Test transactionFreeConnection()');
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollback();
/** @noinspection PhpUnhandledExceptionInspection */
throw $e;
}
Create condition:
use VV\Db\Sql;
use VV\Db\Sql\Condition;
$condition = new Condition();
// or
$condition = Sql::condition();
To add new predicate to condition at first you need set target expression via "connector" methods and($expression)
or or($expression)
.
Then call "comparison" method like eq($value)
, like($value)
, in($value1, $value2)
and other:
$condition = Sql::condition()
->and('foo')->gte(100)
->and/*('foo')*/->lte(1000) // if target expression of next predicate is same as previous one
// you may omit argument for "connector" method
->and(
(new Condition())
->or('bar')->eq(1)
->or('bar')->isNull()
// Sql::condition('bar')->eq(1)->or->isNull()
)
eq($param)
- = ?
ne($param)
- != ?
lt($param)
- < ?
lte($param)
- <= ?
gt($param)
- > ?
gte($param)
- >= ?
compare($param, $operator)
- $operator ?
between($from, $till)
- BETWEEN ? AND ?
in(...$params)
- IN (?, ?, ?, ...)
isNull()
- IS NULL
isNotNull()
- IS NOT NULL
like($pattern, $caseInsensitive = false)
- LIKE ?
startsWith($prefix, $caseInsensitive = false)
- LIKE ?%
endsWith($suffix, $caseInsensitive = false)
- LIKE %?
contains($string, $caseInsensitive = false)
- LIKE %?%
exists($db->select(...)->where(...)
- EXISTS (SELECT ... FROM ... WHERE ...)
and('MY_FUNC(foo, ?, ?)')->custom($param1, $param2)
- MY_FUNC(foo, ?, ?)
$db->select(
Sql::case('foo')
->when(1)->then('first')
->when(2)->then('second')
->when(3)->then('third')
->else('N-th')
->as('placement'),
)
->from('bar');
Sql::case()
->when(['foo <' => 10])
->then('low')
->when(Sql::condition('foo')->between(10, 100))
->then('middle')
->else('high');