Gorm find subset in HasMany - search

I have the following relation in which I want to search
class Order {
static hasMany = [articles:Article]
}
class Article {
}
In my search I select N articles and I want to find all Orders that contain all the N selected articles. So far I was only able to find all Orders that contain one of the selected Articles. It would be great if anyone can help me on this.

Order.executeQuery("select o from Order o join o.articles as a where a in (:articleList)", [articleList: articleList])

Related

ArangoDB populate relation as field over graph query

I recently started using Arango since I want to make use of the advantages of graph databases. However, I'm not yet sure what's the most elegant and efficient approach to query an item from a document collection and applying fields to it that are part of a relation.
I'm used to make use of population or joins in SQL and NoSQL databases, but I'm not sure how it works here.
I created a document collection called posts. For example, this is a post:
{
"title": "Foo",
"content": "Bar"
}
And I also have a document collection called tags. A post can have any amount of tags, and my goal is to fetch either all or specific posts, but with their tags included, so for example this as my returning query result:
{
"title": "Foo",
"content": "Bar",
"tags": ["tag1", "tag2"]
}
I tried creating those two document collections and an edge collection post-tags-relation where I added an item for each tag from the post to the tag. I also created a graph, although I'm not yet sure what the vertex field is used for.
My query looked like this
FOR v, e, p IN 1..2 OUTBOUND 'posts/testPost' GRAPH post-tags-relation RETURN v
And it did give me the tag, but my goal is to fetch a post and include the tags in the same document...The path vertices do contain all tags and the post, but in separate arrays, which is not nice and easy to use (and probably not the right way). I'm probably missing something important here. Hopefully someone can help.
You're really close - it looks like your query to get the tags is correct. Now, just add a bit to return the source document:
FOR post IN posts
FILTER post._key == 'testPost'
LET tags = (
FOR v IN 1..2 OUTBOUND post
GRAPH post-tags-relation
RETURN v.value
)
RETURN MERGE(
post,
{ tags }
)
Or, if you want to skip the FOR/FILTER process:
LET post = DOCUMENT('posts/testPost')
LET tags = (
FOR v IN 1..2 OUTBOUND post
GRAPH post-tags-relation
RETURN v.value
)
RETURN MERGE(
post,
{ tags }
)
As for graph definition, there are three required fields:
edge definitions (an edge collection)
from collections (where your edges come from)
to collections (where your edges point to)
The non-obvious vertex collections field is there to allow you to include a set of vertex-only documents in your graph. When these documents are searched and how they're filtered remains a mystery to me. Personally, I've never used this feature (my data has always been connected) so I can't say when it would be valuable, but someone thought it was important to include.

loopback relational database hasManyThrough pivot table

I seem to be stuck on a classic ORM issue and don't know really how to handle it, so at this point any help is welcome.
Is there a way to get the pivot table on a hasManyThrough query? Better yet, apply some filter or sort to it. A typical example
Table products
id,title
Table categories
id,title
table products_categories
productsId, categoriesId, orderBy, main
So, in the above scenario, say you want to get all categories of product X that are (main = true) or you want to sort the the product categories by orderBy.
What happens now is a first SELECT on products to get the product data, a second SELECT on products_categories to get the categoriesId and a final SELECT on categories to get the actual categories. Ideally, filters and sort should be applied to the 2nd SELECT like
SELECT `id`,`productsId`,`categoriesId`,`orderBy`,`main` FROM `products_categories` WHERE `productsId` IN (180) WHERE main = 1 ORDER BY `orderBy` DESC
Another typical example would be wanting to order the product images based on the order the user wants them to
so you would have a products_images table
id,image,productsID,orderBy
and you would want to
SELECT from products_images WHERE productsId In (180) ORDER BY orderBy ASC
Is that even possible?
EDIT : Here is the relationship needed for an intermediate table to get what I need based on my schema.
Products.hasMany(Images,
{
as: "Images",
"foreignKey": "productsId",
"through": ProductsImagesItems,
scope: function (inst, filter) {
return {active: 1};
}
});
Thing is the scope function is giving me access to the final result and not to the intermediate table.
I am not sure to fully understand your problem(s), but for sure you need to move away from the table concept and express your problem in terms of Models and Relations.
The way I see it, you have two models Product(properties: title) and Category (properties: main).
Then, you can have relations between the two, potentially
Product belongsTo Category
Category hasMany Product
This means a product will belong to a single category, while a category may contain many products. There are other relations available
Then, using the generated REST API, you can filter GET requests to get items in function of their properties (like main in your case), or use custom GET requests (automatically generated when you add relations) to get for instance all products belonging to a specific category.
Does this helps ?
Based on what you have here I'd probably recommend using the scope option when defining the relationship. The LoopBack docs show a very similar example of the "product - category" scenario:
Product.hasMany(Category, {
as: 'categories',
scope: function(instance, filter) {
return { type: instance.type };
}
});
In the example above, instance is a category that is being matched, and each product would have a new categories property that would contain the matching Category entities for that Product. Note that this does not follow your exact data scheme, so you may need to play around with it. Also, I think your API query would have to specify that you want the categories related data loaded (those are not included by default):
/api/Products/13?filter{"include":["categories"]}
I suggest you define a custom / remote method in Product.js that does the work for you.
Product.getCategories(_productId){
// if you are taking product title as param instead of _productId,
// you will first need to find product ID
// then execute a find query on products_categories with
// 1. where filter to get only main categoris and productId = _productId
// 2. include filter to include product and category objects
// 3. orderBy filter to sort items based on orderBy column
// now you will get an array of products_categories.
// Each item / object in the array will have nested objects of Product and Category.
}

How to query this with ORM?

I've using Kohana for a couple of weeks. One thing I noticed is that Kohana is missing eager loading (as far as I know). Let's say I have the following tables.
Subjects
id
name
Chapters
id
subject_id
name
Videos
id
chapter_id
name
When a user opens a subject page, I want to display all the chapters and videos. With ORM, I can do
$tutorials = ORM::factory('subject')->where('id','=', 1)->find();
foreach($tutorials as $tutorial)
{
$chapters = $tutorial->chapters->find_all();
foreach($chapters as $chapter)
{
$videos = $chapter->videos->find_all();
}
}
The above code is not efficient since it makes too many queries.
I thought about using join or database query builder, but both of them do not return a model object as their results. I also looked into with(), but it seems like it only works with one-to-one relationship.
using join on an ORM object returns an OPM object, but it doesn't return the data from the joining tables.
What would be my best option here? I would like to minimize # of queries and also want to get ORM objects a result. Whatever it would be, should return all the columns from tutorials, chapters, and videos.
First of all, your code is excess. ORM method find() returns 1 Model_Subject object. See
$chapters = ORM::factory('subject', 1)->chapters->find_all();
foreach($chapters as $chapter)
{
$videos = $chapter->videos->find_all();
}
With DB builder you can make just 2 requests. First get array of all chapters ids:
$chapters = DB::select('id')
->from('chapters')
->where('subject_id', '=', '1')
->execute()
->as_array(NULL, 'id');
Second - get all videos by ids as Model_Video object
$videos = DB::select('id')
->from('videos')
->where('chapter_id', 'IN', $chapters)
->as_object('Model_Video')
->execute()
->as_array();
So I guess you want something like this.
$videos = ORM::factory('Video')
->join(array('chapters', 'chapter'), 'LEFT')->on('video.chapter_id', '=', 'chapter.id')
->join(array('subjects', 'subject'), 'LEFT')->on('chapter.subject_id', '=', 'subject.id')
->where('subject.id', '=', $id)
->find_all();
Come to think of it, if the video belongs_to chapter belongs_to subject, try the following using with():
$videos = ORM::factory('Video')
->with('chapter:subject') // These are the names of the relationships. `:` is separator
// equals $video->chapter->subject;
->where('subject.id', '=', $id)
->find_all();
With things like this it often helps to think 'backwards'. You need the videos on that subject so start with the videos instead of the subject. :)
EDIT: The drawback of the second function is that it is going to preload all the data, it might be shorter to write but heavier on the server. I'd use the first one unless I need to know the subject and chapter anyway.

Kohana 3.2 ORM questions

Let's summarize what i want to ask.
I have a category table and a news table.
So a category can have many news as well as sub categories. If i delete a category, the code need to find the category's sub categories as well as news related to that category and it's sub categories to delete too. Currently my code looks like so(It works well at the moment):
The relationship:
public $_has_many = array(
'news' => array(
'model' => 'news',
'foreign_key' => 'cat_id'
)
);
The code to delete:
/*
* #param $ids: The array of category id that we want to delete
*/
public function delete_items($ids){
if(!is_array($ids))
$ids = array($ids);
if(is_array($ids)){
$recursive = new System_Recursive();
/*
* list_items() method will simply return all records for the category table
*/
$source = $this->list_items(null);
/*
* Loop through the category ids
*/
foreach($ids as $id){
$result = $this->where('id', '=', $id)->find();
if($result->loaded()){
// If category found, then find all the news related to that category
$main_category_news = $result->news->find_all();
if($main_category_news){
// Loop through all the news and proccess the delete method
foreach($main_category_news as $main_news){
$main_news->delete();
}
}
/*
* The find_children() method returns all sub categories of the current category ($result)
*/
$recursive->find_children($source, $result->id, $arr_children, false);
if($arr_children){
// If any sub categories found, continue to loop :((, terrible
foreach($arr_children as $child){
$this->clear();
$child_result = $this->where('id', '=', $child)->find();
if($child_result->loaded()){
/*
* Again, find news related to this sub category and then loop through the news to do single delete
*/
$child_news = $child_result->news->find_all();
foreach($child_news as $c){
$c->delete();
}
}
/*
* After deleting news for sub category,
* I use clear to prevent error from loaded object
* Then find "again" the sub category to delete it
*/
$this->clear();
$child_delete = $this->where('id','=',$child)->find();
if($child_delete->loaded()){
$child_delete->delete();
}
}
}
/*
* And finally for the main direct category
*/
$this->clear();
$result = $this->where('id', '=', $id)->find();
$result->delete();
}
}
}
There are so many many loops in the code, let's consider we delete about 5 categories in 50 categories all with 500 news related to each category. I don't know but i think this is going to take a whole day to complete the task.
So, can someone give me a hint of completing this code for the right way: how to make less code? How to reduce the loops? Is it possible to create a function to reuse in this case, for example if news has many tags, we would do the same thing here and just call that method and it's good to go?
Please help me with this. If you don't answer it, please also give me a reason for why you don't, so that i can make more meaningful questions.
Thank you
It could be faster if you write plain SQL query in your Model function, something like this
$query = DB::query(Database::DELETE, 'DELETE FROM news WHERE news.id IN
(
SELECT news.id FROM news
JOIN sub_category ON news.sub_category_id = sub_category.id
JOIN category ON sub_category.category_id = category.id
WHERE category.id = '.$this->id.'
)'
);
I'm not sure of your exact DB design, so you may need to alter SQL code, but you get main idea.
Let's assume you are using InnoDB (what is likely). You would not need any PHP code thanks to the foreign key constraints.
There are alot of tutorials out there, the solution is using
ON UPDATE CASCADE
ON DELETE CASCADE
when creating the parent's foreign key. When you update a parent all foreign keys will be updated as well (if necessary, so usually when the id changes), also if you delete a parent all children will be deleted as well.

Searching required data in couchdb

I have documents like,
{_id:1,
name:"john"
}
{_id:2,
name:"john boss"
}
{_id:3,
name:"jim"
}
I have to search the data where ever john is stored in documents. Suppose, if i search "john" the documents should get _id:1 & _id:2 related data. Please guide me to get the result.
I appreciate if any one provide the solutions.
I suggest a CouchDB view to show you all "words" from the "name" field.
function(doc) {
// map function: _design/example/_view/names
if(!doc.name) // Optionally do more testing for doc type, etc. here.
return
// Emit one row per word in the name field (first name, last name, etc.).
var words = doc.name.split(/\s+/)
for(var i = 0; i < words.length; i++)
emit(words[i].toLowerCase(), doc._id)
}
Now if you query /db/_design/example/_view/names?key="john", you will get two rows: one for doc id 1, and another for id 2. I also added a conversion to lower case, so searching for "john" will match people named "John".
Duplicates are possible: the same doc ID listed multiple times, e.g. for {"name":"John John"}; however you are guaranteed that all duplicate rows will be adjacent.
You can also add ?include_docs=true to your request to get the full document for each row.

Resources