I want to apply a where condition to relation. Here's what I do:
Replay::whereHas('players', function ($query) {
$query->where('battletag_name', 'test');
})->limit(100);
It generates the following query:
select * from `replays`
where exists (
select * from `players`
where `replays`.`id` = `players`.`replay_id`
and `battletag_name` = 'test')
order by `id` asc
limit 100;
Which executes in 70 seconds. If I manually rewrite query like this:
select * from `replays`
where id in (
select replay_id from `players`
where `battletag_name` = 'test')
order by `id` asc
limit 100;
It executes in 0.4 seconds. Why where exists is the default behavior if it's so slow? Is there a way to generate the correct where in query with query builder or do I need to inject raw SQL? Maybe I'm doing something wrong altogether?
replays table has 4M rows, players has 40M rows, all relevant columns are indexed, dataset doesn't fit into MySQL server memory.
Update: found that the correct query can be generated as:
Replay::whereIn('id', function ($query) {
$query->select('replay_id')->from('players')->where('battletag_name', 'test');
})->limit(100);
Still have a question why exists performs so poorly and why it is the default behavior
Try this:
mpyw/eloquent-has-by-non-dependent-subquery: Convert has() and whereHas() constraints to non-dependent subqueries.
mpyw/eloquent-has-by-join: Convert has() and whereHas() constraints to join() ones for single-result relations.
Replay::hasByNonDependentSubquery('players', function ($query) {
$query->where('battletag_name', 'test');
})->limit(100);
That's all. Happy Eloquent Life!
The reason for laravel has(whereHas) sometimes slowly is that implemented with where exists syntax.
For example:
// User hasMany Post
User::has('posts')->get();
// Sql: select * from `users` where exists (select * from `posts` where `users`.`id`=`posts`.`user_id`)
The 'exists' syntax is a loop to the external table, and then queries the internal table (subQuery) every time.
However, there will be performance problems when the users table has a large amount of data, because above sql select * from 'users' where exists... unable to use index.
It can use where in instead of where exists here without damaging the structure.
// select * from `users` where exists (select * from `posts` where `users`.`id`=`posts`.`user_id`)
// =>
// select * from `users` where `id` in (select `posts`.`user_id` from `posts`)
This will greatly improve performance!
I recommend you try this package hasin, in the above example, you can use the hasin instead of the has.
// User hasMany Post
User::hasin('posts')->get();
// Sql: select * from `users` where `id` in (select `posts`.`user_id` from `posts`)
The hasin just only use where in syntax instead of where exists compared with the framework has, but everywhere else is the same, such as parameters and call mode even the code implementation, and can be used safely.
whereHas performance is poor on tables without index, put index on it and be happy!
Schema::table('category_product', function (Blueprint $table) {
$table->index(['category_id', 'product_id']);
});
This is related to the mysql not to the laravel. You can perform the same thing you wanted from the above with the both options, joins and the subqueries. Subqueries are generally much slower than joins.
Subqueries are:
less complicated
elegant
easier to understand
easier to write
logic separation
and the above facts are why ORMs like eloquent are using suquries. but there are slower! Especially when you have many rows in the database.
Join version of your query is something like this :
select * from `replays`
join `players` on `replays`.`id` = `players`.`replay_id`
and `battletag_name` = 'test'
order by `id` asc
limit 100;
but now you must change select and add group by and be careful on many other things, but why is this so it is beyond that answer. New query would be :
select replays.* from `replays`
join `players` on `replays`.`id` = `players`.`replay_id`
and `battletag_name` = 'test'
order by `id` asc
group by replays.id
limit 100;
So that are the reasons why join in more complicated.
You can write raw query in laravel, but eloquent support for join queries are not well supported, also there are no much packages that can help you with that, this one is for example : https://github.com/fico7489/laravel-eloquent-join
WhereHas() query is really as slow as lazy turtle, so I created and still using a trait that I glue to any laravel model which required a simple join requests. This trait make a scope function whereJoin(). You can just pass there a joined model class name, where clause params and enjoy. This trait take care of table names and related details in query. Well, it's for my personal use and ofc feel free to modify this monstruosity.
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
/** #mixin Model */
trait ModelJoinTrait
{
/**
* #param string|\Countable|array $on
* #param $column
* #param $whereOperator
* #param $value
* #param Model $exemplar
* #return array
*/
function _modelJoinTraitJoinPreset($on, $column, $whereOperator, $value, $exemplar){
$foreignTable = $exemplar->getTable();
$foreignId = $exemplar->getKeyName();
$localTable = $this->getTable();
$localId = $this->getKeyName();
//set up default join and condition parameters
$joinOn =[
'local' => $localTable.'.'.$localId,
'foreign'=> $foreignTable.'.'.$foreignId,
'operator' => '=',
'type'=>'inner',
'alias'=>'_joint_id',
'column'=>$column,
'where_operator'=>$whereOperator,
'value'=>$value
];
//config join parameters based on input
if(is_string($on)){
//if $on is string it treated as foreign key column name for join clause
$joinOn['foreign'] = $foreignTable.'.'.$on;
} elseif (is_countable($on)){
//if $is array or collection there can be join parameters
if(isset($on['local']) && $on['local'])
$joinOn['local'] = $localTable.'.'.$on['local'];
if(isset($on['foreign']) && $on['foreign'])
$joinOn['foreign'] = $localTable.'.'.$on['foreign'];
if(isset($on['operator']) && $on['operator'])
$joinOn['operator'] = $on['operator'];
if(isset($on['alias']) && $on['alias'])
$joinOn['alias'] = $on['alias'];
}
//define join type
$joinTypeArray = ['inner', 'left', 'right', 'cross'];
if(is_countable($on) && isset($on['type']) && in_array($on['type'], $joinTypeArray))
$joinOn = $on['type'];
return $joinOn;
}
/**
* #param Model $exemplar
* #param string|array|\Countable $joinedColumns
* #param string|array|\Countable $ownColumns
* #param string $jointIdAlias
* #return array
*/
function _modelJoinTraitSetColumns($exemplar, $joinedColumns, $ownColumns, $jointIdAlias = '_joint_id')
{
$foreignTable = $exemplar->getTable();
$foreignId = $exemplar->getKeyName();
$localTable = $this->getTable();
$localId = $this->getKeyName();
if(is_string($joinedColumns))
$foreignColumn = ["$foreignTable.$joinedColumns"];
else if(is_countable($joinedColumns)) {
$foreignColumn = array_map(function ($el) use ($foreignTable) {
return "$foreignTable.$el";
}, $joinedColumns);
} else {
$foreignColumn = ["$foreignTable.*"];
}
if(is_string($ownColumns))
$ownColumns = ["$localTable.$ownColumns"];
elseif(is_countable($ownColumns)) {
$ownColumns = array_map(function ($el) use ($localTable) {
return "$localTable.$el";
}, $ownColumns);
} else {
$ownColumns = ["$localTable.*"];
}
$columns = array_merge($foreignColumn, $ownColumns);
if($foreignId == $localId){
$columns = array_merge(["$foreignTable.$foreignId as $jointIdAlias"], $columns);
}
return $columns;
}
/**
* #param Builder $query
* #param string|array|\Countable $on
* #param Model $exemplar
*/
function _modelJoinTraitJoinPerform($query, $on, $exemplar){
$funcTable = ['left'=>'leftJoin', 'right'=>'rightJoin', 'cross'=>'crossJoin', 'inner'=>'join'];
$query->{$funcTable[$on['type']]}($exemplar->getTable(),
function(JoinClause $join) use ($exemplar, $on){
$this->_modelJoinTraitJoinCallback($join, $on);
}
);
}
function _modelJoinTraitJoinCallback(JoinClause $join, $on){
$query = $this->_modelJoinTraitJoinOn($join, $on);
$column = $on['column'];
$operator = $on['where_operator'];
$value = $on['value'];
if(is_string($column))
$query->where($column, $operator, $value);
else if(is_callable($column))
$query->where($column);
}
/**
* #param JoinClause $join
* #param array|\Countable $on
* #return JoinClause
*/
function _modelJoinTraitJoinOn(JoinClause $join, $on){
//execute join query on given parameters
return $join->on($on['local'], $on['operator'], $on['foreign']);
}
/**
* A scope function used on Eloquent models for inner join of another model. After connecting trait in target class
* just use it as ModelClass::query()->whereJoin(...). This query function forces a select() function with
* parameters $joinedColumns and $ownColumns for preventing overwrite primary key on resulting model.
* Columns of base and joined models with same name will be overwritten by base model
*
* #param Builder $query Query given by Eloquent mechanism. It's not exists in
* ModelClass::query()->whereJoin(...) function.
* #param string $class Fully-qualified class name of joined model. Should be descendant of
* Illuminate\Database\Eloquent\Model class.
* #param string|array|\Countable $on Parameter that have join parameters. If it is string, it should be foreign
* key in $class model. If it's an array or Eloquent collection, it can have five elements: 'local' - local key
* in base model, 'foreign' - foreign key in joined $class model (default values - names of respective primary keys),
* 'operator' = comparison operator ('=' by default), 'type' - 'inner', 'left', 'right' and 'cross'
* ('inner' by default) and 'alias' - alias for primary key from joined model if key name is same with key name in
* base model (by default '_joint_id')
* #param Closure|string $column Default Eloquent model::where(...) parameter that will be applied to joined model.
* #param null $operator Default Eloquent model::where(...) parameter that will be applied to joined model.
* #param null $value Default Eloquent model::where(...) parameter that will be applied to joined model.
* #param string[] $joinedColumns Columns from joined model that will be joined to resulting model
* #param string[] $ownColumns Columns from base model that will be included in resulting model
* #return Builder
* #throws \Exception
*/
public function scopeWhereJoin($query, $class, $on, $column, $operator = null, $value=null,
$joinedColumns=['*'], $ownColumns=['*']){
//try to get a fake model of class to get table name and primary key name
/** #var Model $exemplar */
try {
$exemplar = new $class;
} catch (\Exception $ex){
throw new \Exception("Cannot take out data of '$class'");
}
//preset join parameters and conditions
$joinOnArray = $this->_modelJoinTraitJoinPreset($on, $column, $operator, $value, $exemplar);
//set joined and base model columns
$selectedColumns = $this->_modelJoinTraitSetColumns($exemplar, $joinedColumns, $ownColumns, $joinOnArray['alias']);
$query->select($selectedColumns);
//perform join with set parameters;
$this->_modelJoinTraitJoinPerform($query, $joinOnArray, $exemplar);
return $query;
}
}
You can use it like this (Model Goods in example have a dedicated extended data model GoodsData with hasOne relationship between them):
$q = Goods::query();
$q->whereJoin(GoodsData::class, 'goods_id',
function ($q){ //where clause callback
$q->where('recommend', 1);
}
);
//same as previous exmple
$q->whereJoin(GoodsData::class, 'goods_id',
'recommend', 1); //where clause params
// there we have sorted columns from GoodsData model
$q->whereJoin(GoodsData::class, 'goods_id',
'recommend', 1, null, //where clause params
['recommend', 'discount']); //selected columns
//and there - sorted columns from Goods model
$q->whereJoin(GoodsData::class, 'goods_id',
'recommend', '=', 1, //where clause params
['id', 'recommend'], ['id', 'name', 'price']); //selected columns from
//joined and base model
//a bit more complex example but still same. Table names is resolved
//by trait from relevant models
$joinData = [
'type'=>'inner' // inner join `goods_data` on
'local'=>'id', // `goods`.`id`
'operator'=>'=' // =
'foreign'=>'goods_id', // `goods_data`.`goods_id`
];
$q->whereJoin(GoodsData::class, $joinData,
'recommend', '=', 1, //where clause params
['id', 'recommend'], ['id', 'name', 'price']); //selected columns
return $q->get();
Resulting SQL query will be like this
select
`goods_data`.`id` as `_joint_id`, `goods_data`.`id`, `goods_data`.`recommend`,
`goods`.`id`, `goods`.`name`, `goods`.`price` from `goods`
inner join
`goods_data`
on
`goods`.`id` = `goods_data`.`goods_id`
and
-- If callback used then this block will be a nested where clause
-- enclosed in parenthesis
(`recommend` = ? )
-- If used scalar parameters result will be like this
`recommend` = ?
-- so if you have complex queries use a callback for convenience
In your case there should be like this
$q = Replay::query();
$q->whereJoin(Player::class, 'replay_id', 'battletag_name', 'test');
//or
$q->whereJoin(Player::class, 'replay_id',
function ($q){
$q->where('battletag_name', 'test');
}
);
$q->limit(100);
To use it more efficiently, you can go like this:
// Goods.php
class Goods extends Model {
use ModelJoinTrait;
//
public function scopeWhereData($query, $column, $operator = null,
$value = null, $joinedColumns = ['*'], $ownColumns = ['*'])
{
return $query->whereJoin(
GoodsData::class, 'goods_id',
$column, $operator, $value,
$joinedColumns, $ownColumns);
}
}
// -------
// any.php
$query = Goods::whereData('goods_data_column', 1)->get();
PS I dont run any automated tests for this so be careful in use. It works just fine in my case, but there may be unexpected behaviour in yours.
I think performance does not depend on whereHas only it depends on how many records you have selected
Plus try to optimize your mysql server
https://dev.mysql.com/doc/refman/5.7/en/optimize-overview.html
and also Optimize your php server
and if you have faster query why don't you use raw query object from larval
$replay = DB::select('select * from replays where id in (
select replay_id from players where battletag_name = ?)
order by id asc limit 100', ['test']
);
You can use left join
$replies = Replay::orderBy('replays.id')
->leftJoin('players', function ($join) {
$join->on('replays.id', '=', 'players.replay_id');
})
->take(100)
->get();
Related
A Little Background...
My User model has the following two relations:
/**
* Get the connections added by the user.
*
* #param boolean $accepted
* #return BelongsToMany
*/
public function addedConnections($accepted = true)
{
return $this->belongsToMany('App\Models\User\User', 'user_connections', 'user_id', 'connection_id')
->wherePivot('connection_type', '=', 'user')
->wherePivot('accepted', '=', $accepted)
->withTimestamps();
}
/**
* Get the connections the user was invited to.
*
* #param boolean $accepted
* #return BelongsToMany
*/
public function invitedConnections($accepted = true)
{
return $this->belongsToMany('App\Models\User\User', 'user_connections', 'connection_id', 'user_id')
->wherePivot('connection_type', '=', 'user')
->wherePivot('accepted', '=', $accepted)
->withTimestamps();
}
I have attempted to write a method that merges the above two methods to return connections. However, instead of returning this as a Laravel Collection, I want to return it as a query Builder so I can add to the query...
This is what I have implemented:
/**
* Get the connections the user was invited to.
*
* #return Builder
*/
public function connections()
{
return $this
->where(function ($query) {
$query
->whereHas('addedConnections')
->orWhereHas('invitedConnections');
})
->whereDoesntHave('blocked');
}
The Issue...
The problem with this function is that it is not building the query correctly (from what I can see). Instead of returning the connections for the current user, it is returning all connections. This is because of the following two lines in the query:
WHERE user_connections.user_id = users.id
WHERE user_connections.connection_id = users.id
users.id should be the id of the current user, not a reference to the users table.
My Question(s)
Why is this happening? Why is the model id (1, 2, 3 etc) not being inserted into the query instead of the reference to the table?
Regardless of what is causing the issue, I am keen to know if there are any better methods of achieving the above?
Update 1
I think I may have fixed this with the following:
/**
* Get the connections the user was invited to.
*
* #return Builder
*/
public function connections()
{
return $this
->where(function ($query) {
$query
->whereHas('addedConnections', function ($query) {
$query->where('id', '=', $this->id);
})
->orWhereHas('invitedConnections', function ($query) {
$query->where('id', '=', $this->id);
});
})
->whereDoesntHave('blocked');
}
Is this the right solution?
Additional Information
If needed, this is the full SQL query:
SELECT
*
FROM
`users`
WHERE
(
EXISTS
(
SELECT
*
FROM
`users` AS `laravel_reserved_3`
INNER JOIN
`user_connections`
ON `laravel_reserved_3`.`id` = `user_connections`.`connection_id`
WHERE
`user_connections`.`user_id` = `users`.`id` -- THIS IS THE ISSUE
AND `user_connections`.`connection_type` = 'user'
AND `user_connections`.`accepted` = TRUE
AND `laravel_reserved_3`.`deleted_at` IS NULL
)
OR EXISTS
(
SELECT
*
FROM
`users` AS `laravel_reserved_4`
INNER JOIN
`user_connections`
ON `laravel_reserved_4`.`id` = `user_connections`.`user_id`
WHERE
`user_connections`.`connection_id` = `users`.`id` -- THIS IS THE ISSUE
AND `user_connections`.`connection_type` = 'user'
AND `user_connections`.`accepted` = TRUE
AND `laravel_reserved_4`.`deleted_at` IS NULL
)
)
AND NOT EXISTS
(
SELECT
*
FROM
`users` AS `laravel_reserved_5`
INNER JOIN
`user_blocks`
ON `laravel_reserved_5`.`id` = `user_blocks`.`blocked_id`
WHERE
`user_blocks`.`user_id` = `users`.`id`
AND `laravel_reserved_5`.`deleted_at` IS NULL
)
AND `users`.`deleted_at` IS NULL
Given the table structures below:
table users
- id
- name
table group_user
- id
- group_id
- user_id
table groups
- id
- name
table events
- id
- name
- group_id
We can see that a User has relations to Event objects, however it has to pass through the many-to-many relationship of a User to a Group.
I'm not sure what kind of relationship this is called... Is it a has-many-through-many-to-many since it has many Event objects through a many-to-many relationship of users-group_user-groups?
In any case, how do I compose a Laravel query for this?
Given $id is the ID of the user performing the call, the SQL query that we are looking for looks something like this:
SELECT * FROM events WHERE group_id IN (
SELECT group_id FROM group_user WHERE user_id = $id
);
So if in your group_user table, we find that the user with user_id = 1 has the following group relations group_id = [1, 2, 3, 4, 5], the resulting query looks like this: SELECT * FROM events WHERE group_id IN [1, 2, 3, 4, 5]; which are what we are looking for (the events related to those groups).
Open up vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
Insert the following code after the hasMany method.
/**
* Define a many-through-many relationship.
*
* #param string $related
* #param string $through
* #param string $pivotTable
* #param string $pivotKey
* #param string $pivotThroughKey
* #param string|null $firstKey
* #param string|null $secondKey
* #return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function manyThroughMany($related, $through, $pivotTable, $pivotKey, $pivotThroughKey, $firstKey = null, $secondKey = null)
{
$relatedModel = new $related; // App\Event
$relatedTable = $relatedModel->getTable(); // events
$firstKey = $firstKey ?: $this->getForeignKey(); // event_id
$throughModel = new $through; // App\Group
$throughTable = $throughModel->getTable(); // groups
$secondKey = $secondKey ?: $throughModel->getForeignKey(); // group_id
return $relatedModel
->whereIn($secondKey, function ($query) use ($pivotTable, $pivotKey, $pivotThroughKey)
{
return $query
->from($pivotTable)
->where($pivotKey, '=', $this->id)
->select($pivotThroughKey);
})
->select($relatedTable . '.*');
}
Now for your model. To use the many-through-many relation, go to your User model and add the following.
public function events()
{
return $this->manyThroughMany('App\Event', 'App\Group', 'group_user', 'user_id', 'group_id');
}
The parameters are as follows:
The name of the target model you are interested in (i.e. App\Event).
The name of the secondary model you need to pass through (i.e. App\Group).
The name of the pivot table (i.e. group_user).
The pivot table key referencing the current model (i.e. user_id).
The pivot table key referencing the secondary model (i.e. group_id).
Disclaimers
I don't really know if there's such a thing as a many-through-many relationship, or what the name for it is. This is just the code that worked for me, so I wanted to share it with people who might have the same dilemma as I did.
If there is a simpler way of doing it, please answer! If there are things I can improve, please comment. Thanks.
I have sql condidtion SELECT * FROM (SELECT * FROM Prices WHERE aliasId = :aliasId order by id desc) p1 group by p1.currency and I am trying to use it in hasMany statement.
$q = $this->hasMany(Prices::className(), ['aliasId' => 'id']);
$db = \Yii::$app->db;
$query = $db
->createCommand('SELECT * FROM (SELECT * FROM Prices WHERE aliasId = :aliasId order by id desc) p1 group by p1.currency')
->bindValue(':aliasId', $this->id);
$query->prepare(true);
$q->sql = $query->getRawSql();
return $q;
But $this->id is empty when hasMany calling. Is there any way to bind custom query and link array there?
UPDATE.
I know that the reason of $this->id is empty, because I'm using Prices::find()>with('prices') in my Controller, so Yii creates query for all prices list. hasMany just adds addWhere('in', $key, $value) in empty query from $link parameter, I'm trying to override his query, but I can't.
$this->id is empty for new PriceAlias instances, and it's filled only after the model is saved in db - you are getting an empty value most likely because getPrices() is called before the model is saved in db.
You can test if $this->id != null or $this->isNewRecord == false before building the custom command, otherwise return null, an empty array or as required.
UPDATE 1: not sure I fully understand your update,
Prices::find()>with('prices') does create a WHERE ... IN (...) query, but
hasMany does not add an addWhere rule, it creates a relation for the ActiveRecord class. In your case:
$this->hasMany(Prices::className(), ['aliasId' => 'id'])
// generates: SELECT * FROM `prices` WHERE `aliasId` = :id
And the query is executed only when you specifically call getPrices() for an object.
So your problem is? after $q->sql = $query->getRawSql(); statement, $q->sql is not SELECT * FROM (SELECT * FROM Prices WHERE aliasId = :aliasId order by id desc) p1 group by p1.currency ?
UPDATE 2: I understand now. I can't think of any way of using Prices::find()->with() on relations with custom sql, at least not as the one you would like to use.
I can only suggest to find an alternative to find()->with() in your controller if you need to keep the custom query.
From official doc:
$subQuery = (new Query())->select('id')->from('user')->where('status=1');
// SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u
$query->from(['u' => $subQuery]);
In your case it should be something like this:
$subQuery = (new Query())->select('*')->from('Prices')->where('aliasId = :aliasId', ['aliasId'=>$aliasId])->orderBy('id');
$query->from(['p1' => $subQuery])->groupBy('p1.currency');
I'm needing to convert this query for Laravel and I'm having issues trying to create an Insert... Select statement using Laravel's Eloquont ORM, or Queries. I'm not sure how I would go about creating this query.
Insert into Demand (Login, Name, ApptTime, Phone, Physician, Location, FormatName, FormatDate, FormatTime, ApptDate, FormatPhone, cellphone)
Select Login, Name, ApptTime, Phone, Physician, Location, FormatName, FormatDate, FormatTime, ApptDate, FormatPhone, cellphone from " . [dbname] . "
Where " . $where_statement
How is it possible to create this query using Laravel's ORM?
EDIT: I'm not sure if this is clear, but I'm thinking in terms of 1 query, like they do here http://dev.mysql.com/doc/refman/5.0/en/insert-select.html
There is no way of doing this in one query (unless you are on Laravel 5.7), however I came across the same issue and wanted to make sure I can keep using a certain select I build with the QueryBuilder.
So what you could do, to keep things half what clean and to reuse functionality which has built a select statement before, is this:
/**
* Wherever your Select may come from
**/
$select = User::where(...)
->where(...)
->whereIn(...)
->select(array('email','moneyOwing'));
/**
* get the binding parameters
**/
$bindings = $select->getBindings();
/**
* now go down to the "Network Layer"
* and do a hard coded select
*/
$insertQuery = 'INSERT into user_debt_collection (email,dinero) '
. $select->toSql();
\DB::insert($insertQuery, $bindings);
UPDATE Laravel 5.7
As of Laravel 5.7.17 you can use ->insertUsing(). See here for details. Thank you #Soulriser for pointing this out.
So above query would look like this:
DB::table('user_debt_collection')->insertUsing(['email','dinero'], $select);
You can use:
DB::insert("insert into contacts(contact_id,contact_type,account_id,created_at,updated_at) select f.id,'App\\Friend',f.account_id,f.created_at,f.updated_at from friends as f where f.id=?",[16]);
I used DB::statement("...");
DB::statement("INSERT INTO table (SELECT)");
In Laravel 5.5, I created a helper function for executing easier:
class QueryHelper
{
/**
* #param string $model
* #param array $columns
* #param Builder $select
* #return bool
*/
public static function insertFromSelectStatement($model, $columns, $select)
{
/** #var \Illuminate\Database\Query\Builder $query */
$query = (new $model)->getQuery();
$sql = "insert into {$query->from} (". implode(', ', $columns) .") {$select->toSql()}";
return $query->getConnection()->insert($sql, $select->getBindings());
}
}
For example:
$notification = Notification::create([
'title' => 'this is title',
'message' => 'this is message',
]);
$now = DB::raw("'". Carbon::now()->toDateTimeString() ."'");
$selectActivatedUsers = User::select('id', $notification->id, $now, $now)->where('status', '=', 'activated');
// insert notifications to activated users
QueryHelper::insertFromSelectStatement(UserNotification::class, ['user_id', 'notification_id', 'created_at', 'updated_at'], $selectActivatedUser);
Try this
DB::table(table2)
->insert(
(array) DB::table(table1)
->where('column', '=', $variable)
->select('column1','column2')
->first()
);
First, you'll need to create a model for Demand, so then you can use:
$demand = new Demand;
$demand->Login = $login;
$demand->Name = $name;
[...]
$demand->save(); // <~ this is your "insert" statement
$id = $demand->id;
Then for your select statement you can do something similar to these options:
$demand = Demand::find($id); // will be similar to: SELECT * FROM Demand WHERE `id` = '$id';
$demand = Demand::where('id', $id)->get(); //same as above
$demand = Demand::where('id', $id)->get(array('Login', 'Name')); // will be similar to: SELECT `Login`, `Name` FROM Demand WHERE `id` = '$id';
There is a lot more information in the manual here and here
Doing a Native SQL Query on a class table inheritance hierarchy is mentioned in the examples in the docs but I will produce the relevant example here:
<?php
use Doctrine\ORM\Query\ResultSetMapping;
// Equivalent DQL query: "select u from User u where u.name=?1"
// User is a mapped base class for other classes. User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
From the example:
Note that in the case of Class Table Inheritance, an example as above
would result in partial objects if any objects in the result are
actually a subtype of User. When using DQL, Doctrine automatically
includes the necessary joins for this mapping strategy but with native
SQL it is your responsibility.
The problem with the above query is that while it will return fields from the parent class User if you are looking to return fields from a child class that above example does not return them. How do we do this?
Before I go any further I have taken the time to create some example classes (one a parent, the other a child), that should work for the above example query. Let me provide them for you:
/**
* #ORM\Table(name="users",
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({
* "regular_user" = "RegularUser",
* "administrator" = "Administrator",
* })
*/
class User
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string")
*/
protected $name;
}
And:
/**
* #ORM\Table(name="regular_users",
* #ORM\Entity
*/
class RegularUser extends User
{
/**
* #ORM\Column(name="phone_number", type="string")
*/
protected $phoneNumber;
}
So we have a parent class User with two child classes RegularUser and Administrator (note: I only provided the one child class RegularUser but I did provide the discriminator mapping for child class Administrator).
Consider this: Instead of querying the database for a user with name 'romanb' (like at the top example) we would like to use a fulltext search engine like Solr to index the database. We query Solr for romanb and it returns an array of user ids of users with a name like romanb ordered by relevance. With these ordered id's we would like to query the database and return a list of regular users, fully hydrated (i.e. including their phone number) and ordered in the same order as the id's that Solr returned. How can we do this?
Doctrine does not support custom ordering by as it is not supported by all SQL engines. It is supported by MySQL which happens to be the database that we are using.
Consider the following Native SQL Query:
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr');
$rsm->setDiscriminatorColumn('u', 'discr');
$rsm->addFieldResult('u', 'phone_number', 'phoneNumber'); // How do we map this result?
// Returned by Solr
$userIds = array(22, 3, 88, 109, 12);
$sql = <<<SQL
SELECT u.id, u.name, u.discr, r.phone_number
FROM users u
INNER JOIN regular_users r ON users.id = regular_users.id
WHERE u.id IN (?)
ORDER BY FIELD(u.id, ?); # Custom SQL logic that will return an ordered set
SQL;
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, $userIds);
$query->setParameter(2, $userIds);
$users = $query->getResult();
That above query will fail with an error though the SQL is fine. If we commented out $rsm->addFieldResult('u', 'phone_number', 'phoneNumber') it would work and a RegularUser object would be returned in the results (because all ids belong to regular users and we defined the discriminator column in the result set mapping) but none of those RegularUser objects would contain a phone number.
Is there anyway to do a query that will return a fully hydrated RegularUser object?
Thanks for reading.
Sometimes the good thing about writing out questions on stackoverflow is that it helps you think through the question better and that can help you come to a solution.
Here is a working Native SQL query for the above:
<?php
$rsm = new ResultSetMapping;
$rsm->addEntityResult('RegularUser', 'r');
$rsm->addFieldResult('r', 'id', 'id');
$rsm->addFieldResult('r', 'name', 'name');
$rsm->addFieldResult('r', 'phone_number', 'phoneNumber');
$userIds = array(22, 3, 88, 109, 12);
$sql = <<<SQL
SELECT r.id, u.name, r.phone_number
FROM regular_users r
INNER JOIN users u ON r.id = u.id
WHERE u.id IN (?)
ORDER BY FIELD(u.id, ?); # Custom SQL logic that will return an ordered set
SQL;
$query = $this->_em->createNativeQuery($sql, $rsm);
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, $userIds);
$query->setParameter(2, $userIds);
$users = $query->getResult();
Basically there is no need to involve the discriminator map assuming you know the ids you are querying for are of class RegularUser.
I hope this helps someone else (edge case I know).