Im trying to get a random entry from a table and its associations.
Recipe.findAll({
order: [
[sequelize.fn('RANDOM')]
],
where: {
'$RecipeCategory.name$': category
},
include:[
{
model: models.Category,
as: 'RecipeCategory',
},{
model: models.Product,
}],
subQuery:false,
limit:1})
With the above code I'm getting a random entry of Recipe and its associations with limit 1. For example , it returns only 1 product and I need all products. I need to get one recipe with all products.
Any suggestions?
If I remove the subQuery option , I receive this:
SequelizeDatabaseError: missing FROM-clause entry for table "RecipeCategory"
Im searching 5 days for the solution and I think that I have check all related answers in this.'
edit: generated query
SELECT "Recipe"."id", "Recipe"."name", "Recipe"."description", "Recipe"."pic", "Recipe"."createdAt", "Recipe"."updatedAt", "RecipeCategory"."id" AS "RecipeCategory.id", "RecipeCategory"."name" AS "RecipeCategory.name", "RecipeCategory"."description" AS "RecipeCategory.description", "RecipeCategory"."createdAt" AS "RecipeCategory.createdAt", "RecipeCategory"."updatedAt" AS "RecipeCategory.updatedAt", "RecipeCategory.Recipes_Categories"."createdAt" AS "RecipeCategory.Recipes_Categories.createdAt", "RecipeCategory.Recipes_Categories"."updatedAt" AS "RecipeCategory.Recipes_Categories.updatedAt", "RecipeCategory.Recipes_Categories"."CategoryId" AS "RecipeCategory.Recipes_Categories.CategoryId", "RecipeCategory.Recipes_Categories"."RecipeId" AS "RecipeCategory.Recipes_Categories.RecipeId", "Products"."id" AS "Products.id", "Products"."name" AS "Products.name", "Products"."protein" AS "Products.protein", "Products"."fat" AS "Products.fat", "Products"."carbohydrates" AS "Products.carbohydrates", "Products"."salt" AS "Products.salt", "Products"."min" AS "Products.min", "Products"."max" AS "Products.max", "Products"."vegan" AS "Products.vegan", "Products"."piece" AS "Products.piece", "Products"."createdAt" AS "Products.createdAt", "Products"."updatedAt" AS "Products.updatedAt", "Products.Product_Recipe"."position" AS "Products.Product_Recipe.position", "Products.Product_Recipe"."createdAt" AS "Products.Product_Recipe.createdAt", "Products.Product_Recipe"."updatedAt" AS "Products.Product_Recipe.updatedAt", "Products.Product_Recipe"."ProductId" AS "Products.Product_Recipe.ProductId", "Products.Product_Recipe"."RecipeId" AS "Products.Product_Recipe.RecipeId"
FROM
"Recipes" AS "Recipe"
LEFT OUTER JOIN (
"Recipes_Categories" AS "RecipeCategory.Recipes_Categories"
INNER JOIN
"Categories" AS "RecipeCategory" ON "RecipeCategory"."id" = "RecipeCategory.Recipes_Categories"."CategoryId"
) ON "Recipe"."id" = "RecipeCategory.Recipes_Categories"."RecipeId"
LEFT OUTER JOIN (
"Product_Recipes" AS "Products.Product_Recipe"
INNER JOIN "Products" AS "Products" ON "Products"."id" = "Products.Product_Recipe"."ProductId"
) ON "Recipe"."id" = "Products.Product_Recipe"."RecipeId"
WHERE "RecipeCategory"."name" = 'meal-3'
ORDER BY RANDOM()
LIMIT 1;
It looks like using an include on the same level as where is not supported as of this thread in 2015. It's not clear what the solution would be in your case.
A relavant quote from the above thread by a sequelize contributor:
include.where won't since you need a negative query.
You could probably try a raw where. .and({filter_level: ...}, ['RAW QUERY']})
On a side note.. On the off chance that you aren't fully commited to using sequelize, I personally recommend using knex.js. I use it in production code. It's a good balanced library. You get a lot of the flexibility/ease of use of an ORM like sequelize, without losing too much sight/control of the query itself. It's very easy to add/run raw queries if the library doesn't support what you do.
This is pretty rough because I'm pretty sure that either sequelize
is overcomplicating this or your database structure is over complicated. This
shouldn't require 4 joins! Here's the general idea:
assume your db auto incrememts the recipes id
get number of recipes from recipes table, using COUNT()
generate a random number between 0 and COUNT(), this is the id of your random recipe
query the recipe table for for the recipe, left join on the products table to get, join on recipe id. This is a simple query that can be written easily in knex.
Profit! you now have a random recipe and all products required for the recipe.
Related
I am having some trouble wrapping my head around how to traverse a certain graph to extract some data.
Given a collection of "users" and a collection of "places".
And a "likes" edge collection to denote that a user likes a certain place. The "likes" edge collection also has a "review" property to store a user's review about the place.
And a "follows" edge collection to denote that a user follows another user.
How can I traverse the graph to fetch all the places that I like with my review of the place and the reviews of the users I follow that also like the same place.
for example, in the above graph. I am user 6327 and I reviewed both places(7968 and 16213)
I also follow user 6344 which also happens to have reviewed the place 7968.
How can I get all the places that I like and the reviews of the people that I follow who also reviewed the same place that I like.
an expected output would be something like the following:
[
{
name:"my name",
place: "place 1",
id: 1
review,"my review about place 1"
},
{
name:"my name",
place: "place 2",
id: 2
review,"my review about place 2"
},
{
name:"name of the user I follow",
place: "place 2",
id: 2
review,"review about place 2 from the user I follow"
}
]
There are a number of ways to do this query, and it also depends on where you want to add parameters, but for the sake of simplicity I've built this quite verbose query below to help you understand one way of approaching the problem.
One way is to determine the _id of your user record, then find all the _id's of the friends you follow, and then to work out all related reviews in one query.
I take a different approach below, and that is to:
Determine the reviews you have written
Determine who you follow
Determine the reviews the people you follow have written
Merge together your reviews with those of the people you follow
It is possible to merge these queries together more optimally, but I thought it worth breaking them out like this (and showing the output of each stage as well as the final answer) to help you see what data is available.
A key thing to understand about AQL graph queries is how you have access to vertices, edges, and paths when you perform a query.
A path is an object in it's own right and it's worth investigating the contents of that object to better understand how to exploit it for path information.
This query assumes:
users document collection contains users
places document collection contains places
follows edge collection tracks users following other users
reviews edge collection tracks reviews people wrote
Note: When providing an id on each record I used the id of the review, because if you know that id you can fetch the edge document and get the id of both the user and the place as well as read all the data about the review.
LET my_reviews = (
FOR vertices, edges, paths IN 1..1 OUTBOUND "users/6327" reviews
RETURN {
name: FIRST(paths.vertices).name,
review_id: FIRST(paths.edges)._id,
review: FIRST(paths.edges).review,
place: LAST(paths.vertices).place
}
)
LET who_i_follow = (
FOR v IN 1..1 OUTBOUND "users/6327" follows
RETURN v
)
LET reviews_of_who_i_follow = (
FOR users IN who_i_follow
FOR vertices, edges, paths in 1..1 OUTBOUND users._id reviews
RETURN {
name: FIRST(paths.vertices).name,
review_id: FIRST(paths.edges)._id,
review: FIRST(paths.edges).review,
place: LAST(paths.vertices).place
}
)
RETURN {
my_reviews: my_reviews,
who_i_follow: who_i_follow,
reviews_of_who_i_follow: reviews_of_who_i_follow,
merged_reviews: UNION(my_reviews, reviews_of_who_i_follow)
}
The first vertex in paths.vertices is the starting vertex (users/6327)
The last vertex in paths.vertices is the end of the path, e.g. who you follow
The first edge in paths.edges is the review that the user made of the place
Here is another more compact version of the query that takes a param, the _id of the user that is 'you'.
LET target_users = APPEND(TO_ARRAY(#user), (
FOR v IN 1..1 OUTBOUND #user follows RETURN v._id
))
LET selected_reviews = (
FOR u IN target_users
FOR vertices, edges, paths in 1..1 OUTBOUND u reviews
LET user = FIRST(paths.vertices)
LET place = LAST(paths.vertices)
LET review = FIRST(paths.edges)
RETURN {
name: user.name,
review_id: review._id,
review: review.review,
place: place.place
}
)
RETURN selected_reviews
I am trying to use the lowercase function to do string searching in Sequelize.
I manage to do it using the ilike.
My question is how to use the lowercase function in this scenario?
The findAll using ilike is as following:
Db.models.Person.findAll(where: {firstName: {$ilike: `somename`}});
How do I change it to lower(firstname) = lower('somename');
PostgreSQL generally uses case-sensitive collations. This means data that appears in each column is compared literally against your query.
You have several options:
1. Follow Ben's answer, and wrap your wrap columns and database in a sequelize.fn('lower') call.
Pros: No database changes.
Cons: You need to remember to use it for every query. Foregoes indexes (unless you've already created a functional index) and scans tables sequentially, resulting in slower look-ups with large tables. Quite verbose code.
2. Use ILIKE, to case-insensitively match a pattern
To find the name exactly:
Db.models.Person.findAll(where: {firstName: {$iLike: 'name'}});
To find a fragment, which may be contained within arbitrary characters:
Db.models.Person.findAll(where: {firstName: {$iLike: '%name%'}});
Pros: Easy to remember. No Sequelize function wrappers - it's a built-in operator, so syntax is neater. No special indexes or database changes required.
Cons: Slow, unless you start messing with extensions like pg_trgm
3. Define your text columns with the citext type, which implicitly compares lowercase
Defining your column types as 'citext' (instead of text or character varying) has the same practical effect of turning this:
select * from people where name = 'DAVID'
to this...
select * from people where LOWER(name) = LOWER('DAVID')
The PostgreSQL documentation shows this as an example of how to create your table with the citext type:
CREATE TABLE users (
nick CITEXT PRIMARY KEY,
pass TEXT NOT NULL
);
INSERT INTO users VALUES ( 'larry', md5(random()::text) );
INSERT INTO users VALUES ( 'Tom', md5(random()::text) );
INSERT INTO users VALUES ( 'Damian', md5(random()::text) );
INSERT INTO users VALUES ( 'NEAL', md5(random()::text) );
INSERT INTO users VALUES ( 'Bjørn', md5(random()::text) );
SELECT * FROM users WHERE nick = 'Larry';
TL;DR basically swap out your "text" columns for "citext".
The citext module comes bundled with PostgreSQL 8.4, so there's no need to install any extensions. But you do need to enable it on each database you use it with the following SQL:
CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;
And then in your Sequelize definitions, define a type attribute:
// Assuming `Conn` is a new Sequelize instance
const Person = Conn.define('person', {
firstName: {
allowNull: false,
type: 'citext' // <-- this is the only change
}
});
Then your searches against that column will always be case-insensitive with regular where = queries
Pros: No need to wrap your queries in ugly sequelize.fn calls. No need to remember to explicitly lowercase. Locale aware, so works across all character sets.
Cons: You need to remember to use it in your Sequelize definitions when first defining your table. Always activated - you need to know that you'll want to do case insensitive searching when defining your tables.
You can use native functions in the where clause:
Db.models.Person.findAll({
where: sequelize.where(
sequelize.fn('lower', sequelize.col('firstname')),
sequelize.fn('lower', 'somename')
)
});
which would translate to
select * from person where lower(firstname) = lower('somename');
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.
}
I’ve just started to use Cloudant and I just can’t get my head around the map functions. I’ve been fiddling with the data below but it isn’t working out as I expected.
The relationship is, a user can have many vehicles. A vehicle belongs to 1 user. The vehicle ‘userId’ is the key of the user. There is a bit of redundancy as in user the _id and userId is the same, guess later is not required.
Anyhow, how can I find for a/every user, the vehicles which belong to it? The closest I’ve come through trial and error is a result which displays the owner of every vehicle, but I would like it the other way round, the user and the vehicles belonging to it. All the examples I’ve found use another document which ‘joins’ two or more documents, but I don’t need to do that?
Any point in the right direction appreciated - I really have no idea.
function (doc) {
if (doc.$doctype == "vehicle")
{
emit(doc.userId, {_id: doc.userId});
}
}
EDIT: Getting closer. I'm not sure exactly what I was expecting, but the result seems a bit 'messy'. Row[0] is the user document, row[n > 0] are the vehicle documents. I guess it's fine when a startkey/endkey is used, but without the results are a bit jumbled up.
function (doc) {
if (doc.$doctype == 'user') {
emit([doc._id, 0], doc);
} else if (doc.$doctype == 'vehicle') {
emit([doc.userId, 1, doc._id], doc);
}
}
A user is described as,
{
"_id": "user:10",
"firstname": “firstnamehere",
"secondname": “secondnamehere",
"userId": "user:10",
"$doctype": "user"
}
a vehicle is described as,
{
"_id": "vehicle:4002”,
“name”: “avehicle”,
"userId": "user:10",
"$doctype": "vehicle",
}
You're getting in the right direction! You already got that right with the global IDs. Having the type of the document as part of the ID in some form is a very good idea, so that you don't get confused later (all documents are in the same "pot").
Here are some minor problems with your current solution (before getting to your actual question):
Don't emit the doc as value in emit(key, value). You can always ask for the document that belongs to a view row by querying with include_docs=true. Having the doc as view value increases the view indexes a lot. When you don't need a specific value, use emit(key, null).
You also don't need the ID in the emit value. You'll get the ID of the document that belongs to a view row as part of the row anyway.
View Collation
Now to your problem of aggregating the vehicles with their user. You got the basic pattern right. This pattern is called view collation, you can read more about it in the CouchDB docs (ignore that it is in the "Couchapp" section).
The trick with view collation is that you return two or more types of documents, but make sure that they are sorted in a way that allows for direct grouping. Thus it is important to understand how CouchDB sorts the view result. See the collation specification for more information on that one. An important key to understanding view collation is that rows with array keys are sorted by key elements. So when two rows have the same key[0], they sort by key[1]. If that's equal as well, key[2] is considered, and so on.
Your map function frist groups users and vehicles by user ID (key[0]). Your map function then uses the fact that 0 sorts before 1 in the second element of the key, so your view will contain the following:
user 1
vehicle of user 1
vehicle of user 1
vehicle of user 1
user 2
user 3
vehicle of user 3
user 4
etc.
As you can see, the vehicles of a user immediately follow their user. Thus you can group this result into aggregates without performing expensive sort or lookup operations.
Note that users are sorted according to their ID, and vehicles within users also according to their ID. This is because you use the IDs in the key array.
Creating Queries
Now that view isn't worth much if you can't query according to your needs. A view as you have it supports the following queries:
Get all users with their vehicles
Get a range of users with their vehicles
Get a single user with its vehicles
Get a single user without vehicles (you could also use the _all_docs view for that though)
Example query for "all users between user 1 and user 3 (inclusive) with their vehicles"
We want to query for a range, so we use startkey and endkey in the query:
startkey=["user:1", 0]
endkey=["user:3", 1, {}]
Note the use of {} as sentinel value, which is required so that the end key is larger than any row that has a key of ["user:3", 1, (anyConceivableVehicleId)]
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.