Search string value inside an array of objects inside an object of the jsonb column- TypeORM and Nest.js - node.js

the problem I am facing is as follows:
Search value: 'cooking'
JSON object::
data: {
skills: {
items: [ { name: 'cooking' }, ... ]
}
}
Expected result: Should find all the "skill items" that contain 'cooking' inside their name, using TypeORM and Nest.js.
The current code does not support search on the backend, and I should implement this. I want to use TypeORM features, rather than handling it with JavaScript.
Current code: (returns data based on the userId)
const allItems = this.dataRepository.find({ where: [{ user: { id: userId } }] })
I investigated the PostgreSQL documentation regarding the PostgreSQL functions and even though I understand how to create a raw SQL query, I am struggling to convert this to the TypeORM equivalent.
Note: I researched many StackOverflow issues before creating this question, but do inform me If I missed the right one. I will be glad to investigate.
Can you help me figure out the way to query this with TypeORM?
UPDATE
Let's consider the simple raw query:
SELECT *
FROM table1 t
WHERE t.data->'skills' #> '{"items":[{ "name": "cooking"}]}';
This query will provide the result for any item within the items array that will match exact name - in this case, "cooking".
That's totally fine, and it can be executed as a raw request but it is certainly not easy to maintain in the future, nor to use pattern matching and wildcards (I couldn't find a solution to do that, If you know how to do it please share!). But, this solution is good enough when you have to work on the exact matches. I'll keep this question updated with the new findings.

use Like in Where clause:
servicePoint = await this.servicePointAddressRepository.find({
where: [{ ...isActive, name: Like("%"+key+"%"), serviceExecutive:{id: userId} },
{ ...isActive, servicePointId: Like("%"+key+"%")},
{ ...isActive, branchCode: Like("%"+key+"%")},
],
skip: (page - 1) * limit,
take: limit,
order: { updatedAt: "DESC" },
relations:["serviceExecutive","address"]
});
This may help you! I'm matching with key here.

Related

Usage of TSVECTOR and to_tsquery to filter records in Sequelize

I've been trying to get full search text to work for a while now without any success. The current documentation has this example:
[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // match text search for strings 'fat' and 'rat' (PG only)
So I've built the following query:
Title.findAll({
where: {
keywords: {
[Op.match]: Sequelize.fn('to_tsquery', 'test')
}
}
})
And keywords is defined as a TSVECTOR field.
keywords: {
type: DataTypes.TSVECTOR,
},
It seems like it's generating the query properly, but I'm not getting the expected results. This is the query that it's being generated by Sequelize:
Executing (default): SELECT "id" FROM "Tests" AS "Test" WHERE "Test"."keywords" ## to_tsquery('test');
And I know that there are multiple records in the database that have 'test' in their vector, such as the following one:
{
"id": 3,
"keywords": "'keyword' 'this' 'test' 'is' 'a'",
}
so I'm unsure as to what's going on. What would be the proper way to search for matches based on a TSVECTOR field?
It's funny, but these days I am also working on the same thing and getting the same problem.
I think part of the solution is here (How to implement PostgresQL tsvector for full-text search using Sequelize?), but I haven't been able to get it to work yet.
If you find examples, I'm interested. Otherwise as soon as I find the solution that works 100% I will update this answer.
What I also notice is when I add data (seeds) from sequelize, it doesn't add the lexemes number after the data of the field in question. Do you have the same behavior ?
last thing, did you create the index ?
CREATE INDEX tsv_idx ON data USING gin(column);

MongoDB Realm NodeJS SDK $project syntax?

I'm trying to do a simple query where some collection data is returned. I want to filter out the _id field in the results.
From my understanding (based on the documentation) the syntax to do so should look like this:
myCollection.findOne(
{ name: hostName },
{ $project: { _id: 0 } }
)
However when I do this the filter has not been applied, e.g.:
{
_id: XXXXXXXXXXXXXXXXXXXX,
name: 'the name',
...
}
Can anybody point me in the right direction?
There are other options for $project. Below one provides the data without _id
myCollection.findOne({ name: hostName }, { '_id': 0 },(err,res)
In case anyone else comes across this, it turns out that the documentation is inaccurate and does not reflect the current state of the SDK.
For reference, see this GitHub Issue where it was confirmed by a Realm dev: https://github.com/realm/realm-js/issues/3275

Sequelize: full text search using symbol based operators

So I'm trying to implement postgresql full text search through Sequelize 3 but there're a few problems including that, I couldn't add text search functions in current where clause:
where: {
published: true,
and: where(
fn("tsmatch",
col("tokens"),
fn("plainto_tsquery", query),
),
true,
)
},
I know we can use raw sql statement but the problem is that existing implementation is based on Sequelize operators and that's what Sequelize recommends for security.
Found a good resource for adding and reusing a function for it here but am stuck in combining it with other query params.
Ok, I've ended up using where clause again:
where: {
published: true,
where: where(
fn("tsmatch",
col("textTokens"),
fn("plainto_tsquery", text),
),
true,
)
},
Hope this would help others facing similar issue.
Sequelize version 6.5.0+ supports the TSVECTOR datatype and Op.match:
Model.findAll({
where: {
textTokens: { [Op.match]: sequelize.fn('to_tsquery', query) }
}
})

Storing and querying PostgreSQL database entities with multiple related entities?

Designing a PostgreSQL database that will be queried by a Node API using Sequelize. Currently, I have a table called recipes that has columns called ingredients and instructions. Those columns are stored for a given as an array of strings like {Tomatoes, Onions}.
That method of storage worked fine for simply fetching and rendering a recipe on the client side. But it wasn't working well for fuzzy search querying because, using Sequelize all I could do was ingredients: { [Op.contains] : [query] }. So if a user typed tomatoes there was no way to write a "fuzzy" search query that would return a recipe with an ingredient Tomatoes.
And then I read this in the PostgreSQL documentation:
Arrays are not sets; searching for specific array elements can be a sign of database misdesign. Consider using a separate table with a row for each item that would be an array element. This will be easier to search, and is likely to scale better for a large number of elements.
Now I'm considering storing ingredients and instructions as separate tables, but I have a couple of questions.
1) As a recipe can have multiple ingredients related to it, should I just use a foreign key for each ingredient and the Sequelize hasMany relationship? That seems correct to me, except that I'm now potentially duplicating common ingredients each time a new recipe is created that uses that ingredient.
2) What would be the best way to write a fuzzy search query so that a user could search the main columns of the recipes table (e.g. title, description) and additionally apply their query to the instructions and ingredients tables?
Essentially I'd like to end up with a fuzzy search query applied to the three tables that looks something like this...
const recipes = await req.context.models.Recipe.findAll({
where: {
[Op.or]: [
{ title: { [Op.iLike]: '%' + query + '%' } },
{ description: { [Op.iLike]: '%' + query + '%' } },
{ ingredients: { ingredient: { [Op.iLike]: '%' + query + '%' } } },
{ instructions: { instruction: { [Op.iLike]: '%' + query + '%' } } }
]
}
});
Thanks!
I have done this, i happen to use graphql in my node layer with sequelize, and i have filter objects that do this type of thing. You'll just need some include statements in your Recipie.findAll.. after your initial where clause where you evaluate whether you are searching title or description or both type thing. i sent my search params in with prefix's i could strip off that told me what sequelize op's i would want to use on them and just ran my args through a utility method to create my where clause, but i know there are many ways to skin that cat. i just did not want to clutter up my resolvers with tonnes of hardcoded ops and conditional clauses was all.... your include might look something like this
include: [{
model: models.Ingredient,
as: 'Ingredients',
through: { some join table specifying keys where necessary since this
is many to many }
where: {some conditional code around your search param},
}, {
model: models.Instruction,
as: 'Instructions',
where: {some conditional code around your search param},
}],
There is good documentation around multiple includes, or nested includes in the sequelize docs, but from what i see above you have a fairly good understanding of what you need to do. To uncomplicate things a bit, i'd start with just searching on your fields from recipie (title, description) before you add the includes and get that working, then it will be a little clearer how you want to form your where clauses.
alternativley.. you can skip the includes and write associations in your models and call them with getters and pass the where clauses to those... i do that as well and again well documented stuff now.. Sequelize has really upped their game
Recipie.associate = function (models) {
models.Recipie.hasMany(models.Ingredient, { as: 'Ingredients', through: "recipie_ingredient" foreignKey: 'recipie_id'});
};
now you have a getter for Ingredients, and if you declare belongsToMany targetting back at Recipie in the Ingredient model then you'll have a getter there as well, and you can pass your search string to that via where clause and get all recipies that have a given ingredient or ingredient list type thing.... Clear as mud?

Is there a simple way to make Sequelize return it's date/time fields in a particular format?

We need to have sequelize return dates in a particular format, not the default one. As far as I can tell, there is no way to set that up in options, or any other way. Short of manually updating the dates every time after they are retrieved, anyone been able to solve this easily? Or am I missing something?
You can, use the Sequelize fn method. From the API Reference, the fn function will help create an object representing a SQL function in your query.
For example:
model.findAll({
attributes: [
'id',
[sequelize.fn('date_format', sequelize.col('date_col'), '%Y-%m-%d'), 'date_col_formed']
]})
.then(function(result) {
console.log(result);
});
Will return data values:
[
{"id": 1, "date_col_formed": "2014-01-01"},
{"id": 2, "date_col_formed": "2014-01-02"}
// and so on...
]
21/06/2019
A little bit late but providing an update.
Sequelize is a powerful ORM (I am not saying is the best solution out there) but has a very bad documentation.
Anyway if you want to have this configured in your models one way apart from having to repeat this across your queries as other responses state you could be doing:
const Test = sequelize.define('test', {
// attributes
name: {
type: DataType.STRING,
allowNull: false
},
createdAt: {
type: DataType.DATE,
//note here this is the guy that you are looking for
get() {
return moment(this.getDataValue('createdAt')).format('DD/MM/YYYY h:mm:ss');
}
},
updatedAt: {
type: DataType.DATE,
get() {
return moment(this.getDataValue('updatedAt')).format('DD/MM/YYYY h:mm:ss');
}
}
If you are using dates to provide info as: last updated, first login, last login. This is the way to go.
The function get returns the new formatted element so it should not be restricted to dates either! Just bear in mind that this will slow your query so use cautiously. ;)
more info (i know i know but docs is the name of the game): https://sequelize.org/docs/v6/core-concepts/getters-setters-virtuals/
you can define custom instance methods getDate/setDate which would translate date between sequelize internal representation and desired format like so https://sequelize.org/master/manual/model-basics.html#taking-advantage-of-models-being-classes
If you are wondering to cast it as VARCHAR :
attributes:{include:[[sequelize.cast(sequelize.col('dob'), 'VARCHAR') , 'dob']],exclude:['dob']}
In case of Model that we create using Sequelize CLI
try something like this
var sequelize= require('../models');
model.findAll({
attributes: [
'id',
'title'
[sequelize.Sequelize.fn('date_format', sequelize.Sequelize.col('col_name'), '%d %b %y'), 'col_name']
]}.then(function(result))
{ // dateformate=04 Nov 2017
console.log(result)
}
visit this link for formate
To format Date, you can use Sequelize.fn method. I tried all mentioned methods but it won't work with date_formate. Try to create with bellow one.
model.findAll({
attributes: [
'id',
[sequelize.fn('FORMAT', sequelize.col('col_name'), 'yyyy-mm-dd'), 'col_name']
]})
.then(function(result) {
console.log(result);
});
here, i used 'FORMAT' instead of 'DATE_FORMAT', because it through error :
'date_format' is not a recognized built-in function name in api call.
For me, date_format and Format functions in above answers do not work.
I solved as below:
attributes: [
[sequelize.literal('date("dateTime")'), 'dateWithoutTime'],
],

Resources