Yii2 dynamically add joinWith - join

i have an online store with dynamic fields (specifications) and filters. All specifications are stored in table e.g
product_specifications:
-id
-product_id
-spec_id
-spec_val_id
Filters are connected with specifications, so when i send filter value i send specification values. For example
$_GET['Filters']['filter_id']['spec_val_id']
When i loop filters, i want to add left joins to product query. Something like that
$joinString = "";
foreach($filters){
$joinString .= "LEFT JOIN product_specifications AS prod_'.filter.' ON .....";
}
I have query to ActiveDataProvider like that:
$prodQuery = Product::find()->joinWith('translation')->joinWith('cats')->[HERE FILTERS JOINS]->where($whereString)->groupBy(['id']);
But if i have 5 filters, i need 5 joins to table product_specifications.
Can i add in joinWith an array with all joins or add 5 joins to query chain?
To one category my filters are also dynamically, so the page can have 5 or 10 filters, i can't add static number of joinWith('specs').
Thanks in advance.
I'm agree with different desicion, too.
EDIT:
I change query with findBySql like this
$prodQuery = Product::findBySql('
SELECT `product`.*
FROM `product`
LEFT JOIN `productLang` ON `product`.`id` = `productLang`.`product_id`
LEFT JOIN `product_cat` ON `product`.`id` = `product_cat`.`product_id`
LEFT JOIN `page` ON `product_cat`.`page_id` = `page`.`id`
LEFT JOIN `page` `parent` ON `page`.`id_in` = `parent`.`id`'
.$joinString.
' WHERE ( '
.$whereString.
' AND (`language`=\''.$lang->url.'\')) GROUP BY `product`.`id` ');
And my dataprovider:
$dataProvider = new ActiveDataProvider([
'query' => $prodQuery,
'pagination' => [
'totalCount' => count($prodQuery->all()),
'pageSize' => $pageSize,
'route' => Yii::$app->getRequest()->getQueryParam('first_step'),
],
'sort' => [
'defaultOrder' => $orderArr,
],
]);
Now my problem is pagination and sorting. In my test site i have 4 products, when i set $pageSize = 1;, I have 4 pages in pagination, but in every page i have all 4 products.
Where is my mistake?

Could be you can use andFilterWhere (if the value is null no where codntion is added otherwise the and Where is added)
$prodQuery = Product::find()->joinWith('translation')->joinWith('cats')->where($whereString)->groupBy(['id']);
$prodQuery->andFilterWhere(['attribute1' => $value1])
->andFilterWhere(['attribute2' => $value2])
->andFilterWhere(['attribute3' => $value3])
->andFilterWhere(['attribute4' => $value4])
->andFilterWhere(['attribute5' => $value5])

Related

Filter activerecord by nested ID and value combination in Ruby on Rails

Lets say I have two models with a many to many relationship: Item and Property
Now I have an array of properties and I want to filter all items which properties match a given value (lets say a boolean value: property.value = true)
When I try
#items = Item.includes(:properties).where(:properties => {:id => [1,2,3].to_a, :value => true})
I would like to get all items where property(1) is true AND property(2) is true and so on. But with the code above I get all items related to the property id's and where any property is true. How should I change my code?
I would appreciate not to use a gem for this.
Looks like you are almost there:
property_ids = [1,2,3]
Item.joins(:properties).
where(:properties => { :id => property_ids, :value => true }).
group('items.id').
having('COUNT(properties.id) >= ?', property_ids.size)
joins does an INNER JOIN and is preferred over includes when you really need to join tables.
where is basically the conditions you already had, the only change is that there is not need to call to_a on the array.
Than you have to group to make that COUNT in SQL work.
having extracts the lines that have at least the expected number of property lines matching the condition.

How to force the order of where conditions in ActiveRecord

As far as I know, the "where" method in ActiveRecord doesn't grantee the order of conditions when creating the sql command with an attribute hash. For example:
Student.where(:classroom_id => 1, :serial_no => 10, :name => 'Allen')
# SELECT `students`.* FROM `students` WHERE `students`.`name` = 'Allen' AND `subscriptions`.`serial_no` = 10 AND `subscriptions`.`classroom_id` = 1
We can use "where" with an Array to force the condition format, like this:
Student.where(["classroom_id = ? AND serial_no = ? AND name = ?", 1, 10, 'Allen'])
# SELECT `students`.* FROM `students` WHERE (classroom_id = 1 AND serial_no = 10 AND name = 'Allen')
But we would lose the flexibility of using the attribute hash. Actually, we only want to force the first 2 conditions(i.e., classroom_id and serial_no) in an order.
Is there any way to force the first 2 conditions in an order and use a given hash to assign the following conditions?
UPDATE:
Why I care the order of conditions?
Because I use multi-column index for (classroom_id, serial_no). The index works if we use the following two queries:
SELECT * FROM students WHERE classroom_id = 1 AND serial_no = 10 AND name = 'Allen'
SELECT * FROM students WHERE classroom_id = 1 AND name = 'Allen'
but would not work(or not fully utilized) with these two:
SELECT * FROM students WHERE name = 'Allen' AND classroom_id = 1 AND serial_no = 10
SELECT * FROM students WHERE serial_no = 10 AND classroom_id = 1 AND name = 'Allen'
I think you can use this syntax to do what you want:
Student.where(:classroom_id => 1).where(:serial_no => 10).where(:name => 'Allen')
#OR
Student.where(:classroom_id => 1).where(:serial_no => 10, :name => 'Allen')
I hope it helps
Since when you are querying a database, you are basically dealing with sets, I would recommend you to read about Boolean algebra
Answering your question, it doesn't matter the order, because you are dealing only with AND operation, so A and B and C is the same as C and A and B.
So forget the order, if you are not getting the results you want, look elsewhere, because it's not the order fault.
Update:
Have you tried chaining where clauses like:
Student.where(:classroom_id => 1, :serial_no => 10).where(:name => 'Allen')

Yii eager loading — Fatal error: Out of memory

I have a problem with Yii eager loading.
I open user profile page and use:
$model=User::model()->with('routes', 'likes', 'comments', 'questions', 'cityname')->findByPk($id);
Relations is:
public function relations()
{
return array(
'routes'=>array(self::HAS_MANY, 'Route', 'author_id', 'order'=>'routes.id DESC'),
'questions'=>array(self::HAS_MANY, 'Question', 'author_id', 'order'=>'questions.id DESC'),
'comments'=>array(self::HAS_MANY, 'Comment', 'author_id', 'order'=>'comments.id DESC',),
'likes'=>array(self::HAS_MANY, 'Like', 'author_id', 'order'=>'likes.id DESC'),
'cityname'=>array(self::BELONGS_TO, 'City', 'city'),
);
}
When i have around 70 (or more) comments in Comment table, i have error:
Fatal error: Out of memory (allocated 348651520) (tried to allocate 78 bytes) in /home/milk/kolyasya.ru/diplomyii/framework/db/CDbCommand.php on line 516
The interesting part of this problem, is if i comment any element of with(), for example:
$model=User::model()->with('routes', 'likes', 'comments', /* 'questions' */, 'cityname')->findByPk($id);
then all works as it should.
I checked all relations in all models and set ini_set('memory_limit', '512M'), but i can't find a source of the problem.
Maybe I need to use lazy loading?
You're suffering from exploding number of row combinations. Take a look at this question, it describes the same problem on a smaller scale. Basically, you are running a huge query with multiple one-to-many joins, similar to this:
SELECT ... FROM `User` `t`
LEFT JOIN `Route` routes ON t.id = routes.author_id
LEFT JOIN `Question` questions ON t.id = questions.author_id
LEFT JOIN `Comment` comments ON t.id = comments.author_id
LEFT JOIN `Like` likes ON t.id = likes.author_id
LEFT JOIN `City` city ON t.city = city.id
WHERE t.id = :id
ORDER BY routes.id DESC, questions.id DESC, comments.id DESC, likes.id DESC
You can take this query, modify it to SELECT COUNT(*) and run it in phpMyAdmin to see how many rows it returns. It will be equal to the number of routes multiplied by the number of questions multiplied by the number of comments multiplied by the number of likes created by this user.
In this situation, it would be a lot more efficient to fetch each HAS_MANY relation in a separate query. Yii can do that:
$model=User::model()
->with(array(
'routes' => array('together' => false),
'likes' => array('together' => false),
'comments' => array('together' => false),
'questions' => array('together' => false),
'cityname' => array(),
))
->findByPk($id);
If you do so, Yii will instead produce multiple SQL queries with less memory usage, similar to the following:
SELECT ... FROM `User` `t`
LEFT JOIN `City` `city` ON `t`.`city` = `city`.`id`
WHERE `t`.`id` = :id;
SELECT ... FROM `Route` `routes`
WHERE `author_id` = :id
ORDER by `routes`.`id` DESC;
SELECT ... FROM `Question` `questions`
WHERE `author_id` = :id
ORDER BY `questions`.`id` DESC;
SELECT ... FROM `Comment` `comments`
WHERE `author_id` = :id
ORDER BY `comments`.`id` DESC;
SELECT ... FROM `Like` `likes`
WHERE `author_id` = :id
ORDER BY `likes`.`id` DESC;
The results will be aggregated and returned to your code just like before.

How to implement a search query in NHibernate 3 (using NHibernate.Linq)

I'm trying to build a search query using NHibernate that will filter on parameters from several different tables and result in somewhat-reasonable SQL that can take advantage of NHibernate's lazy-loading.
Fromm reading various tips online, it seems that the latest-and-greatest way to do that is to use the QueryOver object to conditionally add in the parameters being used, as in the following snippet:
Hibernate.Criterion.QueryOver<Models.Site, Models.Site> query = NHibernate.Criterion.QueryOver.Of<Models.Site>();
if (!string.IsNullOrEmpty(state))
query = query.WhereRestrictionOn(r => r.State.StateName).IsInsensitiveLike("%" + state + "%");
if (startDate.HasValue)
query = query.Where(r => r.Events
.Where(e=>e.EventDate >= startDate.Value)
.Count() > 0
);
return query.GetExecutableQueryOver(currentSession).Cacheable().List();
(an event has a foreign-key to site)
I have two questions:
How do I filter the child objects, instead of just the parent? The sample code above gives me all the sites with a matching events, but within that site, I only want matching events. If I'm supposed to use a join or a subquery, how? I'm confused about maintaining my tree-like hierarchy with lazy-loading through a join or subquery.
Edit: this has been answered. Thanks psousa!
How do I add an or clause? I found reference to a Disjunction object, but it doesn't seem like that's available using the QueryOver method.
Edit:
I want to result in a list of sites (top level object) filtered by the site criteria, and each site should have its list of events filtered by the event criteria.
I expect it to generate SQL like the following:
SELECT *
FROM [site] s
LEFT JOIN [event] e ON s.siteID = e.siteID
WHERE e.eventDate > #eventDate
AND (s.stateCd = #state OR s.stateName LIKE #state)
I would do that query as such:
//use aliases. Optional but more practical IMHO
Site siteAlias = null;
Event eventAlias = null;
//use JoinAlias instead of JoinQueryOver to keep the condition at the "Site" level
var results = Session.QueryOver(() => siteAlias)
.JoinAlias(m => m.Event, () => eventAlias)
.Where(() => eventAlias.EventDate > eventDate)
.Where(() => siteAlias.StateCd == state || Restrictions.On(() => siteAlias.StateName).IsLike(state))
.List();
You mentioned the Disjunction class, and it may in fact be used with QueryOver, like:
var disjunction= new Disjunction();
disjunction.Add(() => siteAlias.StateCD == state);
disjunction.Add(Restrictions.On(() => siteAlias.StateName).IsLike(state));
The QueryOver query would be:
var results = Session.QueryOver(() => siteAlias)
.JoinAlias(m => m.Event, () => eventAlias)
.Where(() => eventAlias.EventDate > eventDate)
.Where(disjunction)
.List();
When using a join alias as suggested by psousa, you will get results in a strange combination of an object structure and a row structure, with the top-level objects being duplicated by the child objects that are attached to them. In order to get the results I was looking for, you can use TransformUsing and a DistinctRootEntityResultTransformer as shown in the following code:
Site siteAlias = null;
Event eventAlias = null;
var results = currentSession.QueryOver<Site>(() => siteAlias)
.JoinAlias(m => m.Event, () => eventAlias)
.Where(() => eventAlias.EventDate > eventDate)
.Where(() => siteAlias.StateCd == state || Restrictions.On(() => siteAlias.StateName).IsLike(state))
.TransformUsing(new NHibernate.Transform.DistinctRootEntityResultTransformer())
.List();

Rails, finding object with multiple dates in has_many assocation

I'm pretty new to Rails, and the problem I'm bumping into is described as followes:
First, there is a table with houses.
The houses have multiple dates associated with has_many :dates
Second there is the table with dates.
Dates have the association belongs_to :house
Now, in a search, a user can select a start and end-date.
And what I want to do, is search each house for it's dates, and check if these are between the start and end-date. If one or more of the dates is within the start/end date, it needs to be found.
These are my conditions to search a house for other criteria:
parameters.push( [ params[:house_category_id], "houses.house_category_id = ?" ] );
parameters.push( [ params[:province_id], "houses.province_id = ?" ] );
I've created a method to create valid conditions with these parameters.
Please tell me, if it's not clear enough, cause it's a little hard to explain.
I've tried searching for this, but it got me on all kind of other pages except the pages with an answer..
Thanks
Not sure if I understand your problem correctly, but according to what I understood, you can get the desired result using the following:
res = House.find(:all, :select => 'distinct houses.*', :joins => "INNER JOIN dates ON dates.house_id = houses.id", :conditions => "dates.date < '2011-01-12' AND dates.date > '2011-01-11'")
where '2011-01-12' & '2011-01-11' are the dates selected by the user.
Let me know if this is what you were looking for or post with more details.
Now another question pops in my mind, but I don't know if that is possible..
If you forget about the from-to for this situation, is there any possibility that a I can check this sort of associations like so:
A house has 2 dates attached.
A User selects 2 dates.
Now I only want to select the house if these 2 dates both correspond..
Like this:
#houses = House.find(:all, :select => 'distinct houses.*', :joins => "INNER JOIN dates ON dates.house_id = houses.id", :conditions => "dates.date = '2011-01-12' AND dates.date = '2011-01-11'")
I tried the code above and got an SQL like this:
SELECT distinct houses.* FROM `houses` INNER JOIN dates ON dates.house_id = houses.id WHERE (dates.date = '2011-01-15' AND dates.date = '2011-01-22')
But it doesn't seem to work, is this possible at all in a single query or single find( )?

Resources