Selecting from multiple tables with Yii's CActiveDataProvider - yii

I have 3 tables: Topics, Users and Details (those are some of the tables of a custom forum)
Topics contains (among other usual fields) the id (FK) of the user that created the topic.
Users contains nick/pass and id (PK)
Details contains (among other usual fields) the id (FK) of the user.
Relations:
One user can have one 1 detail.
One user can have multiple topics, but a topic can be created only by one user.
Topic relations:
return array(
'user' => array(self::BELONGS_TO, 'User', 'User_iduser'),
);
User relations:
return array(
'details' => array(self::HAS_ONE, 'Details', 'User_iduser'),
);
I'm trying to get a list with Topics and User details (let's say for example the topic name and the user's name).
Currently I have this:
$dataProvider=new CActiveDataProvider('Topic', array(
'criteria'=>array(
'with'=>array('user.details')
)
));
But as you can imagine, it's not working (read as in it's not selecting anything from the tbles Users or Details).
What's wrong with my code?
+++++++++++++++
This code selects fields from the table user (and the topic table):
Topic::model()->with('user')->findAll();
But this one won't select from details, users and topic:
Topic::model()->with('user.details')->findAll();
Also, I need a CActiveDataProvider solution as I need it for zii widgets (means, even if some kind of modification on the Topic::model()->with().... code get's to select from the 3 tables it won't be really helpful)
EDIT: (SQL log from Yii)
Querying SQL: SELECT COUNT(DISTINCT `t`.`idtema`) FROM `tema` `t` LEFT
OUTER JOIN `usuario` `usuarioIdusuario` ON
(`t`.`Usuario_idusuario`=`usuarioIdusuario`.`idusuario`) LEFT OUTER JOIN
`detallesusuario` `detallesusuario` ON
(`detallesusuario`.`Usuario_idusuario`=`usuarioIdusuario`.`idusuario`)
Querying SQL: SELECT `t`.`idtema` AS `t0_c0`, `t`.`Usuario_idusuario` AS
`t0_c1`, `t`.`Categoria_idcategoria` AS `t0_c2`, `t`.`tema` AS `t0_c3`,
`t`.`fecha_hora` AS `t0_c4`, `usuarioIdusuario`.`idusuario` AS `t1_c0`,
`usuarioIdusuario`.`nick` AS `t1_c1`, `usuarioIdusuario`.`contrasena` AS
`t1_c2`, `usuarioIdusuario`.`email` AS `t1_c3`,
`detallesusuario`.`Usuario_idusuario` AS `t2_c0`,
`detallesusuario`.`nombre` AS `t2_c1`, `detallesusuario`.`apellidos` AS
`t2_c2`, `detallesusuario`.`cumpleanos` AS `t2_c3`,
`detallesusuario`.`telefono1` AS `t2_c4`, `detallesusuario`.`telefono2` AS
`t2_c5` FROM `tema` `t` LEFT OUTER JOIN `usuario` `usuarioIdusuario` ON
(`t`.`Usuario_idusuario`=`usuarioIdusuario`.`idusuario`) LEFT OUTER JOIN
`detallesusuario` `detallesusuario` ON
(`detallesusuario`.`Usuario_idusuario`=`usuarioIdusuario`.`idusuario`)
LIMIT 10

Try this
$dataProvider=new CActiveDataProvider('Topic', array(
'criteria'=>array(
'with'=>array('user'=>array('alias'=>'user','with'=>array('details'=>array('alias'=>'details'))))
)
));
And
Topic::model()->with(array('user'=>array('alias'=>'user','with'=>array('details'=>array('alias'=>'details')))))->findAll();

instead of the line
'with'=>array('user.details')
use
'with'=>array('user')
then add
$criteria->compare( 'user.details', $query, true );
$criteria->together = true;
}
*Where $query is a variable that will be searched in column details

Related

Rails/SQL: Using an array as a search parameter

In an ecommerce shop application I would like to retrieve all orders that match a first_name that was entered via a search form and where paid == true. The search form submits the search term via params Parameters: {"utf8"=>"✓", "search"=>"john", "commit"=>"Search"}. In the controller
#users = User.search(params[:search]) #returns all users with the matching first_name, e.g. 'john'
#order = Order.where('user_id = ? AND paid = ?', #users.ids, true )
The query in #order works just fine, if only one user is returned, e.g. only one user is named john. But if multiple users are named John, multiple user ids are returned and the error message ActiveRecord::StatementInvalid is returned. My understanding is that the query stops working once `#users.ids is an array with more than one value.
How do I structure the following query: for each user_id return all orders (user.orders) where paid equals true.
Models
user.rb
has_many :orders
order.rb
belongs_to :users
There are various ways to go about this.
You can use a JOIN as Nic Nilov suggests but this can be difficult if your #users query is built using scopes and you don't want to manually inline those scopes.
You could also use a subquery since ActiveRecord in Rails4 is smart enough do The Right Thing when you use a relation in a where, you just have to use the hash form of where:
#users = User.search(params[:search])
# #users should be a User::ActiveRecord_Relation now.
#orders = Order.where(:user_id => #users, :paid => true)
This will end up with SQL like:
select *
from orders
where paid = 't'
and user_id in (
select id
from users
where /* whatever `search` does... */
)
The advantage here is that you don't need to know what User.search does as long as it is returning an ActiveRecord relation.
If your #users is actually an array (of ids or whole User instances) then you'd do it exactly the same way:
# Suppose #users is an array of Users or an array of User ids...
#orders = Order.where(:user_id => #users, :paid => true)
and ActiveRecord will figure out what to do with your #users array without you having to do anything extra.
Instead of two queries, you should use a nested query with a WHERE IN clause
SELECT * from Order WHERE user_id IN (SELECT user_id FROM users WHERE first_name LIKE ?) AND paid = true
This should do:
Order.joins(:user).where(users: { name: params[:search] }, paid: true)
It generates a single query with an INNER JOIN:
SELECT "orders".*
FROM "orders"
INNER JOIN "users" ON "users"."id" = "orders"."user_id"
WHERE "users"."name" = 'Test User' AND "orders"."paid" = 't'

Rails ActiveRecord query where relationship does not exist based on third attribute

I have an Adventure model, which is a join table between a Destination and a User (and has additional attributes such as zipcode and time_limit). I want to create a query that will return me all the Destinations where an Adventure between that Destination and the User currently trying to create an Adventure does not exist.
The way the app works when a User clicks to start a new Adventure it will create that Adventure with the user_id being that User's id and then runs a method to provide a random Destination, ex:
Adventure.create(user_id: current_user.id) (it is actually doing current_user.adventures.new ) but same thing
I have tried a few things from writing raw SQL queries to using .joins. Here are a few examples:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
Destination.joins('LEFT OUTER JOIN adventure ON destination.id = adventure.destination_id').where('adventure.user_id != ?', user.id)
The below should return all destinations that user has not yet visited in any of his adventures:
destinations = Destination.where('id NOT IN (SELECT destination_id FROM adventures WHERE user_id = ?)', user.id)
To select a random one append one of:
.all.sample
# or
.pluck(:id).sample
Depending on whether you want a full record or just id.
No need for joins, this should do:
Destination.where(['id not in ?', user.adventures.pluck(:destination_id)])
In your first attempt, I see the problem to be in the usage of equality operator with where.not. In your first attempt:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
you're doing where.not('adventures.user_id != ?'), user.id). I understand this is just the opposite of what you want, isn't it? Shouldn't you be calling it as where.not('adventures.user_id = ?', user.id), i.e. with an equals =?
I think the following query would work for the requirement:
Destination.joins(:adventures).where.not(adventures: { user_id: user.id })
The only problem I see in your second method is the usage of destinations and adventures table in both join and where conditions. The table names should be plural. The query should have been:
Destination
.joins('LEFT OUTER JOIN adventures on destinations.id = adventures.destination_id')
.where('adventures.user_id != ?', user.id)
ActiveRecord doesn't do join conditions but you can use your User destinations relation (eg a has_many :destinations, through: adventures) as a sub select which results in a WHERE NOT IN (SELECT...)
The query is pretty simple to express and doesn't require using sql string shenanigans, multiple queries or pulling back temporary sets of ids:
Destination.where.not(id: user.destinations)
If you want you can also chain the above realation with additional where terms, ordering and grouping clauses.
I solved this problem with a mix of this answer and this other answer and came out with:
destination = Destination.where
.not(id: Adventure.where(user: user)
.pluck(:destination_id)
)
.sample
The .not(id: Adventure.where(user: user).pluck(:destination_id)) part excludes destinations present in previous adventures of the user.
The .sample part will pick a random destination from the results.

How do you add join a table using Criteria in yii for CActiveDataProvider?

I have three tables in my db tbl_project, tbl_employee and tbl_user_assignment.
I need to write CDbCriteria on below sql query
SELECT * from tbl_project
INNER JOIN tbl_user_assignment
ON tbl_user_assignment.project_id = tbl_project.id;
JOIN tbl_employee
ON tbl_employee.id = tbl_user_assignment.user_id
WHERE tbl_employee = 8
i have already created model class for these three tables as Project,Employee,Userassign respectively.
my data provider code is
$dataProvider=new CActiveDataProvider('Project',array(
'pagination'=>array(
'pageSize'=>3,
),
));
Please help
Thanks
You need to add a criteria which contains a with. Use the relationship name(s) you've defined in your models.
$dataProvider=new CActiveDataProvider('Project',array(
'pagination'=>array(
'pageSize'=>3,
'criteria'=>array(
'with'=>array(
'userassign',
'employee',
)
)
));
You'll obviously need to change the above to match your personally requirements.
More about criteria on the yii forum

Complex left outer join query

I have a Rails app with a complex query I can't seem to solve. I have 2 tables, clubs and selections. Table clubs is just like this:
id,name
1,A
2,B
3,C
4,D
Table selections contains the selected clubs from table clubs by all the users:
id,club_id,user_id
1,1,1
2,1,2
3,2,3
4,3,1
5,3,3
Now I want a select box with all items from table clubs without the elements already chosen by the current user (he can't chose the same club twice). So in the case of user 1, it should only show clubs B and D, because he already has chosen A and C.
So I created this as a scope in the model:
scope :selectable, ->(current_user) {
joins('LEFT OUTER JOIN selections ON selections.club_id = clubs.id').
where('selections.id IS NULL OR selections.user_id != ?', current_user.id).
group('clubs.id')
}
This works fine when there is only one user making selections, but if more users chose the same club as the current user, these clubs still show up. What can I improve to show the correct clubs?
I found a solution which seems to work, but I don't know if it is the most elegant one:
scope :selectable, ->(current_user) {
joins('LEFT OUTER JOIN selections ON selections.club_id = clubs.id').
where('selections.id IS NULL OR selections.user_id != ?', current_user.id).
where('clubs.id NOT IN (?)', current_user.clubs).
group('clubs.id')
}

Yii Framework - Having issue to setup "INNER JOIN" for relationship

I'm facing an issue in fetching data using relationship. Below is the detail:
I'm having two tables. First is item_master and Second is inventory table. Since both are having MANY_MANY relationship, so we are having another table named inventory_items with two fields (item_id, inventory_id).
Below is the relationship detail in ItemMaster Model:
'inventories' => array(
self::MANY_MANY,
'Inventories',
'inventory_items(item_id, inventory_id)'
),
'inventoryItems'=>array(self::HAS_MANY,'InventoryItems','item_id'),
I need to fetch Items of a particular Inventory only. For this, I'm implementing below filter criteria:
$criteria=new CDbCriteria;
$inventory_condition['condition']="inventoryItems.inventory_id=:inventoryID";
$inventory_condition['params'] = array(
':inventoryID' => $filter['inventory_id']
);
$inventory_condition['joinType']='INNER JOIN';
$criteria->with = array("inventoryItems"=>$inventory_condition);
$items=new CActiveDataProvider('ItemMaster',array('criteria'=>$criteria));
Problem:
Above code is returning me all the items with the required inventory. And if an item does not belong to that inventory than still it returns that Item but without inventory. I just need that only those Items should return which belong to a particular inventory.
You probably need to set together option to true:
$inventory_condition['together']=true;
But be careful if you are going to use it with SQL LIMIT.