I am trying to get data in a Yii2 table call Post. This table has an attribute call owner and I want to check whether the value of owner is equal to a particular Value I pass call it userId or the value of owner is equal to the following attribute of the Followship table where the value of the follower attribute of the Followship Table is equal to the the value I pass call it userId.
In implementing the above logically and bit by bit, I have written the following code;
$allpost = Post::find()->all();
$relevantpost = [];
foreach ($allpost as $post) {
if($post->owner == $userId){
$relevantpost[] = $post;
}
else{
$follower = Followship::findOne(['follower'=>$userId, 'following'=>$post->owner]);
if($follower){
$relevantpost[] = $post;
}
}
}
return $relevantpost;
This code works well but I want to write an active query for this such as ;
$allpost = Post::find()
->where(['owner'=>$userId])
->orWhere(['is NOT', $follower = Followship::findOne(['follower'=>$userId]) and 'owner' => $follower->following, NULL])
->all();
or in the worse case,
$allpost = \Yii::$app->db
->createCommand(
"SELECT postId, location, details, created_at FROM Post
WHERE owner = " . $userId. "OR
owner = '0' OR
owner = following IN (
SELECT following FROM Followship WHERE follower = ". $userId . " AND
)
ORDER BY dateCreated DESC"
)
->queryAll();
I keep getting errors with the above queries. I am missing out a fundamental of the Yii2 query builders.
Please any help on this will be greatly appreciated.
First you could make a relation (which connects Post and Followers by post owner) inside your Post class
class Post extends ActiveRecord {
public function getFollowersDataset() {
return $this->hasMany(Followers::className(), ['following' => 'owner']);
}
...
}
And then you can just use it in your queries
Post::find()
->joinWith('followersDataset')
->where(['or',
['owner' => $user_id],
['follower' => $user_id]])
->all()
The condition accept three parameters
[the_condition, the_attribute, the_value]
In case of AND and OR the thing change
[the_condition, first_condition, second_condition]
With the second tried you can make something like that
$allpost = Post::find()
->where(['owner'=>$userId])
->orWhere([
'AND',
['is NOT', $follower, Followship::findOne(['follower'=>$userId]),
['owner', $follower->following, NULL]
])
->all();
You can check in debug bar the querys that you're making, or another way, its to make a mistake in a field, for example if in your where condition you put ->where(['owners'=>$userId]), that trhow an error with the query that you made, so you can see what query you did
Related
I want to select all the users in my table "User" except the first One cause its the admin,
im using this function index in my controller but it doesn't work .
public function index()
{
// this '!=' for handling the 1 row
$user = User::where('id', '!=', auth()->id())->get();
return view('admin.payroll',compact('user'))->with(['employees' => User::all()]);
}
Better to used here whereNotIn Method
Note: first you find the admin role users and create a static array
$exceptThisUserIds = [1];
$user = User::whereNotIn('id', $exceptThisUserIds)->get();
It's not a good idea to specify the admin user with just id. A better design would be using some sort of a flag like is_admin as a property of your User model.
Still you can use the following code to get the users who have an id greater than 1:
User::where('id', '>', 1)->get()
For getting data skipping the first one you should use skip() method and follow the code like below
public function index()
{
$user = User::orderBy('id','asc')->skip(1)->get();
return view('admin.payroll',compact('user'))->with(['employees' => User::all()]);
}
I have two models, User and Training, with Many to many relationship between them. I'm using the Laravel Datatables package to display a table of all the users. This is how the data controller method (which retrieves the query results and creates a Datatables table) looks like:
public function getData()
{
$users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->remove_column('id')
->make();
}
How can I add a column to the created table which displays the total number of relations for each user (that is, how many Trainings does each User have)?
The brute force way would be to try a User::selectRaw(...) which has a built in subquery to get the count of trainings for the user and expose it as a field.
However, there is a more built-in way to do this. You can eager load the relationship (to avoid the n+1 queries), and use the DataTables add_column method to add in the count. Assuming your relationship is named trainings:
public function getData() {
$users = User::with('trainings')->select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->add_column('trainings', function($user) {
return $user->trainings->count();
})
->remove_column('id')
->make();
}
The name of the column in add_column should be the same name as the loaded relationship. If you use a different name for some reason, then you need to make sure to remove the relationship column so it is removed from the data array. For example:
return \Datatables::of($users)
->add_column('trainings_count', function($user) {
return $user->trainings->count();
})
->remove_column('id')
->remove_column('trainings')
->make();
Edit
Unfortunately, if you want to order on the count field, you will need the brute force method. The package does its ordering by calling ->orderBy() on the Builder object passed to the of() method, so the query itself needs the field on which to order.
However, even though you'll need to do some raw SQL, it can be made a little cleaner. You can add a model scope that will add in the count of the relations. For example, add the following method to your User model:
Note: the following function only works for hasOne/hasMany relationships. Please refer to Edit 2 below for an updated function to work on all relationships.
public function scopeSelectRelatedCount($query, $relationName, $fieldName = null)
{
$relation = $this->$relationName(); // ex: $this->trainings()
$related = $relation->getRelated(); // ex: Training
$parentKey = $relation->getQualifiedParentKeyName(); // ex: users.id
$relatedKey = $relation->getForeignKey(); // ex: trainings.user_id
$fieldName = $fieldName ?: $relationName; // ex: trainings
// build the query to get the count of the related records
// ex: select count(*) from trainings where trainings.id = users.id
$subQuery = $related->select(DB::raw('count(*)'))->whereRaw($relatedKey . ' = ' . $parentKey);
// build the select text to add to the query
// ex: (select count(*) from trainings where trainings.id = users.id) as trainings
$select = '(' . $subQuery->toSql() . ') as ' . $fieldName;
// add the select to the query
return $query->addSelect(DB::raw($select));
}
With that scope added to your User model, your getData function becomes:
public function getData() {
$users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->selectRelatedCount('trainings')
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->remove_column('id')
->make();
}
If you wanted the count field to have a different name, you can pass the name of the field in as the second parameter to the selectRelatedCount scope (e.g. selectRelatedCount('trainings', 'training_count')).
Edit 2
There are a couple issues with the scopeSelectRelatedCount() method described above.
First, the call to $relation->getQualifiedParentKeyName() will only work on hasOne/hasMany relations. This is the only relationship where that method is defined as public. All the other relationships define this method as protected. Therefore, using this scope with a relationship that is not hasOne/hasMany throws an Illuminate\Database\Query\Builder::getQualifiedParentKeyName() exception.
Second, the count SQL generated is not correct for all relationships. Again, it would work fine for hasOne/hasMany, but the manual SQL generated would not work at all for a many to many relationship (belongsToMany).
I did, however, find a solution to both issues. After looking through the relationship code to determine the reason for the exception, I found Laravel already provides a public method to generate the count SQL for a relationship: getRelationCountQuery(). The updated scope method that should work for all relationships is:
public function scopeSelectRelatedCount($query, $relationName, $fieldName = null)
{
$relation = $this->$relationName(); // ex: $this->trainings()
$related = $relation->getRelated(); // ex: Training
$fieldName = $fieldName ?: $relationName; // ex: trainings
// build the query to get the count of the related records
// ex: select count(*) from trainings where trainings.id = users.id
$subQuery = $relation->getRelationCountQuery($related->newQuery(), $query);
// build the select text to add to the query
// ex: (select count(*) from trainings where trainings.id = users.id) as trainings
$select = '(' . $subQuery->toSql() . ') as ' . $fieldName;
// add the select to the query
return $query->addSelect(DB::raw($select));
}
Edit 3
This update allows you to pass a closure to the scope that will modify the count subquery that is added to the select fields.
public function scopeSelectRelatedCount($query, $relationName, $fieldName = null, $callback = null)
{
$relation = $this->$relationName(); // ex: $this->trainings()
$related = $relation->getRelated(); // ex: Training
$fieldName = $fieldName ?: $relationName; // ex: trainings
// start a new query for the count statement
$countQuery = $related->newQuery();
// if a callback closure was given, call it with the count query and relationship
if ($callback instanceof Closure) {
call_user_func($callback, $countQuery, $relation);
}
// build the query to get the count of the related records
// ex: select count(*) from trainings where trainings.id = users.id
$subQuery = $relation->getRelationCountQuery($countQuery, $query);
// build the select text to add to the query
// ex: (select count(*) from trainings where trainings.id = users.id) as trainings
$select = '(' . $subQuery->toSql() . ') as ' . $fieldName;
$queryBindings = $query->getBindings();
$countBindings = $countQuery->getBindings();
// if the new count query has parameter bindings, they need to be spliced
// into the existing query bindings in the correct spot
if (!empty($countBindings)) {
// if the current query has no bindings, just set the current bindings
// to the bindings for the count query
if (empty($queryBindings)) {
$queryBindings = $countBindings;
} else {
// the new count query bindings must be placed directly after any
// existing bindings for the select fields
$fields = implode(',', $query->getQuery()->columns);
$numFieldParams = 0;
// shortcut the regex if no ? at all in fields
if (strpos($fields, '?') !== false) {
// count the number of unquoted parameters (?) in the field list
$paramRegex = '/(?:(["\'])(?:\\\.|[^\1])*\1|\\\.|[^\?])+/';
$numFieldParams = preg_match_all($paramRegex, $fields) - 1;
}
// splice into the current query bindings the bindings needed for the count subquery
array_splice($queryBindings, $numFieldParams, 0, $countBindings);
}
}
// add the select to the query and update the bindings
return $query->addSelect(DB::raw($select))->setBindings($queryBindings);
}
With the updated scope, you can use the closure to modify the count query:
public function getData() {
$users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->selectRelatedCount('trainings', 'trainings', function($query, $relation) {
return $query
->where($relation->getTable().'.is_creator', false)
->where($relation->getTable().'.is_speaker', false)
->where($relation->getTable().'.was_absent', false);
})
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->remove_column('id')
->make();
}
Note: as of this writing, the bllim/laravel4-datatables-package datatables package has an issue with parameter bindings in subqueries in the select fields. The data will be returned correctly, but the counts will not ("Showing 0 to 0 of 0 entries"). I have detailed the issue here. The two options are to manually update the datatables package with the code provided in that issue, or to not use parameter binding inside the count subquery. Use whereRaw to avoid parameter binding.
I would setup your DB tables and Eloquent models using the conventions provided at http://laravel.com/docs/4.2/eloquent. In your example you would have three tables.
trainings
training_user
users
Your models would look something like this.
class Training {
public function users() {
return $this->belongsToMany('User');
}
}
class User {
public function trainings() {
return $this->belongsToMany('Training');
}
}
You can then use Eloquent to get a list of users and eager load their trainings.
// Get all users and eager load their trainings
$users = User::with('trainings')->get();
If you want to count the number of trainings per user you can simply iterate over $users and count the size of the trainings array.
foreach ( $users as $v ) {
$numberOfTrainings = sizeof($v->trainings);
}
Or you can simply do it in pure SQL. Note that my example below assumes you follow Laravel's conventions for naming tables and columns.
SELECT
u.*, COUNT(p.user_id) AS number_of_trainings
FROM
users u
JOIN
training_user p ON u.id = p.user_id
GROUP BY
u.id
Now that you have a couple of ways to count the number of relations, you can use whatever method you like to store that value somewhere. Just remember that if you store that number as a value in the user table you'll need to update it every time a user creates/updates/deletes a training (and vice versa!).
Currently, a criteria BelongsToMany alerts and viceversa. They are related through a pivot table: criteria_id and alert_id.
I am getting all Criteria with the associated Alerts that belongs to the authenticated user, as such:
public function getMatches()
{
$matches = Criteria::whereUserId( Auth::id() )
->has('alerts')
->get();
}
This returns all associated results, whereas now, if a user picks a certain result, I want to be able to show just that. This is what I have so far:
Controller
public function getMatchDetails($alert_id, $criteria_id)
{
$matches = Alert::find($alert_id)
->has('criterias')
->where('criteria_id', $criteria_id)
->get();
}
Which is bringing over the correct variables, however, I am getting a MYSQL error:
Column not found: 1054 Unknown column 'criteria_id' in 'where clause'
select * from `alerts` where `alerts`.`deleted_at` is null and
(select count(*) from `criterias` inner join `alert_criteria` on `criterias`.`id` =
`alert_criteria`.`criteria_id` where `alert_criteria`.`alert_id` = `alerts`.`id`)
>= 1 and `criteria_id` = 7)
Any help would be hugely appreciated.
You could try something like this
public function getMatchDetails($alert_id, $criteria_id)
{
$match = Alert::whereHas('criterias', function ($q) use ($criteria_id) {
$q->where('criteria_id', $criteria_id);
})->find($alert_id);
}
Which will find the alert by id and also check that it has a relationship to criterias meeting those requirements.
I don't know if I understood well the question, but I'm going to try to answer
If you want to pass more than just a variable from the view to the controller, you can do something like this:
View
#foreach($matches as $match)
#foreach($match->alerts as $alert)
<td>{{$alert->pivot->criteria_id}}</td>
<td>{{$alert->id}}</td>
#endforeach
#endforeach
Controller
public function getMatchDetails($id, $var_2 = 0)
{
$myCriteriaIds = Criteria::whereUserId( Auth::id() )
->lists('id');
$match = Alert::find($id)->criterias()
->wherePivot('criteria_id', 'IN', $myCriteriaIds)
->get();
}
Route
Route::post('/users/alert/matches/{id}/{var_2}', array(
'as' => 'users-alert-matches',
'uses' => 'XXXController#getMatchDetails'
));
I have two table for post and user. I want to show post count of user in users list gridview. In yii 1 I use this in model to define a relation for this purpose:
'postCount' => array(self::STAT, 'Post', 'author',
'condition' => 'status = ' . Post::ACTIVE),
...
User:find...().with('postCount').....
But i don't know how to implement this in Yii2 to get count of post in User:find():with('...') to show in gridview.
Anyone try this in yii2?
Here is an example of what I did and it seems to work fine so far. It was used to get a count of comments on a post. I simply used a standard active record count and created the relation with the where statement using $this->id and the primary key of the entry its getting a count for.
public function getComment_count()
{
return Comment::find()->where(['post' => $this->id])->count();
}
Just a passing it along...
You can try the code below:
User::find()->joinWith('posts',true,'RIGHT JOIN')->where(['user.id'=>'posts.id'])->count();
Or if you want to specify a user count:
//user id 2 for example
User::find()->joinWith('posts',true,'RIGHT JOIN')->where(['user.id'=>'posts.id','user.id'=>2])->count();
Please note that, posts is a relation defined in your User model like below:
public function getPosts()
{
return $this->hasMany(Post::className(), ['user_id' => 'id']);
}
Well still I think for those who it may concern, if you JUST want the count a select and not the data it will be better use this instead imho:
$count = (new \yii\db\Query())
->select('count(*)')
->from('table')
->where(['condition'=>'value'])
->scalar();
echo $count;
I am trying to create an update function that adds an entered value to the value for the user in a database. (It adds accounts that can be used for a class to a teacher) So far, I have a test button that should add one account when it's clicked and then refresh the page.
Here is my function thus far:
public function add_account()
{
$this->load->model("teacher_data_model");
$user = $this->ion_auth->user()->row();
$user_id = $user->id;
$data = array(
'user_id' => $user_id,
'unused_accounts' => [what do I insert here?]
);
$this->teacher_data_model->add_accounts($data, $user_id);
redirect('teacher', 'refresh');
}
This function is admittedly sloppy, I am still fairly new to this. This function gets the user ID and passes it to a model that uses it to find our teacher and update the number of student accounts they have. So far this works as long as I have a fixed value for 'unused_accounts', the problem is when I do this:
public function add_account()
{
// Getter of the user data
$user = $this->ion_auth->user()->row();
$user_id = $user->id;
$this->load->model("teacher_data_model");
//This gets the existing accounts
$num_accounts["record"] = $this->teacher_data_model->get_unused_accounts($user_id);
$this->load->view("teacher/teacher_data_viewer", $num_accounts);
$data = array(
'user_id' => $user_id,
'unused_accounts' => [what do I insert here?]
);
// data insertion
$this->teacher_data_model->add_accounts($data, $user_id);
redirect('teacher', 'refresh');
}
I'm not sure whether to but the +1 increment in the controller or the model, but I can't even seem to identify the searched data as an int at this point.
For [what do I insert here?] if I use $num_accounts I get this database error:
Error Number: 1054
Unknown column 'Array' in 'field list'
UPDATE teachers SET user_id = '1', unused_accounts = Array WHERE user_id = '1'
Here is my model in case it's needed:
function add_accounts($data, $uid)
{
$this->db->where('user_id', $uid);
$this->db->update('teachers',$data);
}
EDIT: This is my function that gets the data from the database
function get_unused_accounts($usersid)
{
$this->db->select('unused_accounts');
$this->db->from('teachers');
$this->db->where('user_id', $usersid);
$query = $this->db->get();
return $query->result();
}
I'm not sure if this puts out data in INT form. When I was using this to check if the user has at least one account, it always accepts as true even if the user has zero accounts.
I would suggest using raw SQL in your model for this purpose:
function add_accounts($uid, $quantity = 1)
{
$this->db->query('
UPDATE teachers
SET unused_accounts=unused_accounts+'.$quantity.'
WHERE user_id='.$uid
);
}
Now you can increment unused_accounts value by one for a user using
$this->teacher_data_model->add_accounts($user_id);
or by any amount you want using
$this->teacher_data_model->add_accounts($user_id, $amount);
Not sure if CodeIgniter's ActiveRecord would accept statement unused_accounts=unused_accounts+'.$quantity.', but if it does, you can try using this in your controller it wont work, see below for an edited part:
$data = array(
'user_id' => $user_id,
'unused_accounts' => 'unused_accounts + '.$amount
);
EDIT: Looks like if you want to use ActiveRecord, you must use $this->db->set() with a third parameter set to FALSE, so something like this in your model:
function add_accounts($unused_accounts, $uid, $amount = 1)
{
$this->db->where('user_id', $uid);
$this->db->set('unused_accounts', 'unused_accounts+'.$amount, FALSE);
$this->db->update('teachers');
}
Cant answer comments yet but
if($remaining_accounts = 1)
Will allways be true, try:
if($remaining_accounts == 1)
See PHP manual for comparison operators