I have scopes in my User model:
public function scopes()
{
return array(
'sumPrice'=>array(
'select'=>'SUM(`price`)',
),
);
}
And I want get sumPrice in controller, but I dont know what is a way to do this. I tried:
$sumPrice=User::model()->sumPrice(); //it returns empty record (user model filled by NULLs)
$sumPrice=User::model()->sumPrice()->count(); //it returns count records in user
$sumPrice=User::model()->sumPrice()->findAll(); //it returns all records in user
But there isn't function which return sum, so how to get it?
Solved:
$sumPrice = Yii::app()->db->createCommand('SELECT SUM(`price`) AS `sum` FROM `user`')->queryAll();
var_dump($sumPrice[0]['sum']);
You need a Statistical query on your active record, define a relation on your model
class Post extends CActiveRecord
{
public function relations()
{
return array(
'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
'categoryCount'=>array(
self::STAT, 'Category', 'post_category(post_id, category_id)'
),
);
}
}
Yiiframework
you can use ActiveRecord :
$criteria = new CDbCriteria;
$criteria->select='SUM(your_column) as sum';
$criteria->condition='your_condition';
$user = User::model()->find($criteria);
and you need to declare: public $sum; in your model class. (Using the above example.)
Related
Appears I'm trying to do something uncommonly complex or going about this the wrong way. I can't seem to find anything online similar to what I am doing here. I am trying to query a relationship where a column in the related table is equal to a column in the table of the model.
Given the following tables
| entity
============
id
...
| foo
============
id
entity_id
type_id
...
| baz
============
id
entity_id
type_id
...
| type
============
id
...
and Models
class Entity extends Model
{
public function baz()
{
$this->hasMany( Baz::class );
}
public function foo()
{
$this->hasMany( Foo::class );
}
//...
}
class Foo extends Model
{
public function entity()
{
$this->belongsTo( Entity::class );
}
public function type()
{
$this->belongsTo( Type::class );
}
//...
}
class Baz extends Model {
public function entity()
{
$this->belongsTo( Entity::class );
}
public function type()
{
$this->belongsTo( Type::class );
}
//...
}
class Type extends Model
{
//...
}
I have the following code working, but in a different context Both Foo and Baz are filtered using $type_id
public function controllerActionWorking( Request $request, $type_id )
{
// I'm using this in another place to get rows of Foo with type_id == $type_id. Including Related Baz which also have type_id == $type_id
$results = Foo::with(
[
'entity.baz' =>
function( $query ) use ($type )
{
return $query->where( 'type_id', $type->id ))
}
])
->where( 'type_id', $type_id )
->get()
// ....
}
What I need is all Foo records, The Entity that goes with Foo, and Entities related Baz records where the Baz.type_id is equal to the type_id of the Foo record. this is non working code, but something like this is what I started trying first.
public function controllerActionHelpWanted( Request $request )
{
$results = Foo::with(
[
'entity.baz' =>
function( $query )
{
// Here I need to verify the entity.baz.type_id = foo.type_id
// the following does NOT work
// I get errros that 'foo'.'type_id' does not exist
return $query->whereColumn( 'foo.type_id', 'baz.type_id' )
})
])
->get()
// .....
}
I can use a join() something like this
$results = Foo::with( 'entity' )
->join(
'baz',
function($join)
{
return $join->on( 'foo.entity_id', 'baz.entity_id' )
->on( 'foo.type_id', 'baz.type_id' );
})
->get()
That returns an instance of Foo with combined attributes of both tables. It also looses the 'entity.baz' nested relationship. which will matter in downstream code.
I could $results->map() to filter the 'entity.baz' relationship after the query. I'd prefer to avoid the impact on performance if possible.
just seems like I'm missing something here. Any help/advice is appreciated.
Hard to read, when names are foo/baz and no logic reference is applied. Looks like many to many relation, where you can apply pivot.
public function products()
{
return $this->belongsToMany(Product::class)
->withPivot('quantity', 'price'); // joins data from intermediate table
}
Pivot tables are queried by: $obj->pivot->quantity;
As far you wrap your question into faz/baz/entity it becomes quiet abstract to me, still :)
Let's try to make your query more model-side:
class Foo extends Model
{
use HasFactory;
public function entity()
{
return $this->belongsTo(Entity::class, 'entity_id', 'id');
}
public function scopeTypes($query, $type_id)
{
return $query->where('type_id', $type_id);
}
public function scopeWithBazes($query, $type_id)
{
/* #var $query Builder */
return $query->join('bazs', function($type_id) {
return self::where('type_id', $type_id);
});
}
}
Inside controller I can do:
class TestController extends Controller
{
public function index(Request $request)
{
$foos = Foo::query()->withBazes(1)->get();
foreach($foos as $foo)
dd($foo->baz_name); // i placed baz_name in Baz table for test
}
}
Well, drawback is that the table is joined. It is joined because you don't have any intermediate tables, so you need use joins etc.
Foo::types($type) will return collection of foos with type given.
Foo::bazTypes($type) will return joined table.
Still, these kind of problems appear when you omit intermediate tables, queries become more complex but database is a bit less robust.
If you were able to make that naming abstraction into 'real' problem, maybe people would play with it more. Just make foo => invoice, baz => address, etc, so perhaps you'll find better database schema for your scenario. Sometimes you will end up with multiple queries no matter what, but it's always worth asking :)
I'm performing validation of a form, where a user may select a range of values (based on a set of entries in a model)
E.g. I have the Model CfgLocale(id, name)
I would like to have something like:
CfgLocale->listofAvailableIds() : return a array
What I did is:
Inside Model this method:
class CfgLocale extends Model
{
protected $table = 'cfg_locales';
public static function availableid()
{
$id_list = [];
$res = self::select('id')->get();
foreach($res as $i){
$id_list[] = $i->id;
}
return $id_list;
}
}
On Controller for validation I would do then:
$this->validate($request, [
'id' => 'required|integer|min:1',
...
'locale' => 'required|in:'.implode(',', CfgLocale::availableid()),
]);
Any better Idea, or Laravel standard to have this done?
Thanks
You can use exists rule of laravel.You can define a validation rule as below. Might be this can help.
'locale' => 'exists:cfg_locales,id'
Use this code instead,
class CfgLocale extends Model
{
protected $table = 'cfg_locales';
public static function availableid()
{
return $this->pluck('id')->toArray();
}
}
pluck method selects the id column from your table and toArray method converts your model object collection into array.
Know more about Laravel Collections here.
This will return an array of IDs:
public static function availableid()
{
return $this->pluck('id')->toArray();
}
https://laravel.com/docs/5.3/collections#method-pluck
https://laravel.com/docs/5.3/collections#method-toarray
I have this problem. I have a Group and Role models, with a Many-to-Many relationship setup.
Group model
public function roles()
{
return $this->belongsToMany('App\Role', 'group_roles');
}
Role Model
public function groups()
{
return $this->belongsToMany('App\Group', 'group_roles');
}
GroupsController store method
public function store(Request $requests)
{
$group = new Group;
//$group->group_name = $requests->group_name;
//$group->save();
$group->create($requests->all());
$group->roles()->sync($requests->input('roles'));
Session::flash('success', $requests->group_name.' successfully added');
return redirect('/settings/groups/groups');
}
The problem I have here is that when I call create method on the group model, it returns null, thus causing this $group->roles()->sync($requests->input('roles')); to fail. However, when I use the save method, it works flawlessly. Why doesn't the create work?
EDIT: if I use create it does insert the records into the database, but the problem is that it's not return insertLastId.
In Group Model
public function groups()
{
protected $fillable = ['group_name'] //add all the fillable fields
return $this->belongsToMany('App\Group', 'group_roles');
}
When create() method is used, it mass assigns all the values, so protected $fillable = [] should be used in that particular Model. see https://laravel.com/docs/5.1/eloquent#mass-assignment
For Last Insert Id use db function instead because create() method doesn't return lastInsertId, it only returns true if data inserted successfully.
return DB::('table_name')->insertGetId([
'group_name' => 'some name'
]);
I have figured it out. I changed this:
$group = new Group;
$group->create($requests->all());
To
$group = Group::create($requests->all())
Now create returns the last insert ID.
I have a complex Model with multiple defined relations. In this example I would want to count the Like model and create a property named likes so it can be returned from a REST service.
Is it possible to eager load a model count into a dynamic property?
$beat = Post::with(
array(
'user',
'likes' => function($q){
$q->count();
}
))
->where('id', $id)
->first();
Assuming you are having Post->hasMany->Like relationship and you have declared likes relationship as:
class Post{
public function likes(){
return $this->hasMany('Like');
}
}
create a new function say likeCountRelation as:
public function likeCountRelation()
{
$a = $this->likes();
return $a->selectRaw($a->getForeignKey() . ', count(*) as count')->groupBy($a->getForeignKey());
}
now you can override __get() function as:
public function __get($attribute)
{
if (array_key_exists($attribute, $this->attributes)) {
return $this->attributes[$attribute];
}
switch ($attribute) {
case 'likesCount':
return $this->attributes[$attribute] = $this->likesCountRelation->first() ? $this->likesCountRelation->first()->count : 0;
break;
default:
return parent::__get($attribute);
}
}
or you can use getattribute function as :
public function getLikesCountAttribute(){
return $this->likesCountRelation->first() ? $this->likesCountRelation->first()->count : 0;
}
and simply access likesCount as $post->likesCount you can even eager load it like:
$posts=Post::with('likesCountRelation')->get();
foreach($post as $post){
$post->likesCount;
}
NOTE: Same logic can be used for morph many relationships.
You should use the SQL Group By statement in order to make it works. You can rewrite your query like the following one.
$beat = Post::with(
array(
'user',
'likes' => function($q) {
// The post_id foreign key is needed,
// so Eloquent could rearrange the relationship between them
$q->select( array(DB::raw("count(*) as like_count"), "post_id") )
->groupBy("post_id")
}
))
->where('id', $id)
->first();
The result of likes is a Collection object with one element. I'm assuming the relationship between model Post and Like is Post hasMany Like. So you can access the count like this.
$beat->likes->first()->like_count;
I'm not tested code above but it should works.
I try to execute this relational query in Yii:
$r = MachineData::model()->with('machineGames')->findByPk($machine_id);
but it returns this error:
CDbCommand failed to execute the SQL statement: SQLSTATE[42702]: Ambiguous column: 7 ERROR: column reference "machine_id" is ambiguous
LINE 1: ..."."game_id") WHERE ("t"."machine_id"=3) ORDER BY machine_id...
It seems the problem is in ORDER BY clause where the reference to machine_id is unclear. It may refer to both of the tables because they both have machine_id column. Can you suggest me any solution, please?
REGARDS!
P.s.
Using the following CDbCriteria gives the same error:
$criteria=new CDbCriteria();
$criteria->alias = "u";
$criteria->compare('u.machine_id',$machine_id);
$criteria->with = array('machineGames');
$r = MachineData::model()->findAll($criteria);
This is the relation in model MachineData:
abstract class BaseMachineData extends GxActiveRecord {
public function relations() {
return array('machineGames' => array(self::HAS_MANY, 'MachineGames', 'machine_id', 'order'=>'machine_id', 'with'=>'game');
}
//code goes here
}
class MachineData extends BaseMachineData{
//code goes here
}
This is the relation in model MachineGames:
abstract class BaseMachineGames extends GxActiveRecord {
public function relations() {
return array('machine' => array(self::BELONGS_TO, 'MachineData', 'machine_id');
}
//code goes here
}
class MachineGames extends BaseMachineGames
{
//code goes here
}
I think the problem lies in your MachineData::relations() method:
public function relations() {
return array('machineGames' => array(self::HAS_MANY, 'MachineGames', 'machine_id', 'order'=>'machine_id', 'with'=>'game');
}
You should disambiguate *machine_id* here as explained in the docs for CActiveRecord::relations() :
public function relations() {
return array('machineGames' => array(self::HAS_MANY, 'MachineGames', 'machine_id', 'order'=>'machineGames.machine_id', 'with'=>'game');
}
NB : The code above is using the relation's name, hence the *machine_games.machine_id* column. If you want to disambiguate on the main table column (here : *machine_data.machine_id*), use the alias 't'.