Knex.js multiple orderBy() columns - node.js

Is it possible to do multiple orderBy() columns?
knex
.select()
.table('products')
.orderBy('id', 'asc')
The orderBy() chainable only takes a single column key and a sort value, but how can I order by multiple columns?

You can call .orderBy multiple times to order by multiple columns:
knex
.select()
.table('products')
.orderBy('name', 'desc')
.orderBy('id', 'asc')

The original answer is technically correct, and useful, but my intention was to find a way to programatically apply the orderBy() function multiple times, here is the actual solution I went with for reference:
var sortArray = [
{'field': 'title', 'direction': 'asc'},
{'field': 'id', 'direction': 'desc'}
];
knex
.select()
.table('products')
.modify(function(queryBuilder) {
_.each(sortArray, function(sort) {
queryBuilder.orderBy(sort.field, sort.direction);
});
})
Knex offers a modify function which allows the queryBuilder to be operated on directly. An array iterator then calls orderBy() multiple times.

The Knex orderBy function also receives an array:
knex('users').orderBy(['email', 'age', 'name'])
or
knex('users').orderBy(['email', { column: 'age', order: 'desc' }])
or
knex('users').orderBy([{ column: 'email' }, { column: 'age', order: 'desc' }])

You can use the following solution to solve your problem:
const builder = knex.table('products');
sortArray.forEach(
({ field, direction }) => builder.orderBy(field, direction)
);

orderBy accepts an array of type:
[
{column: 'id', order: 'asc'},
{column: 'name', order: 'desc'},
{column: 'created_at', order: 'desc'},
]
i have a function that takes a param from the request:
sort=id,name,-created_at
and builds an array that is passed to the queryBuilder
columns is an array with the accepted values of table columns
sort(model, sorts, columns) {
let confirmed = true;
sorts = sorts.split(',')
sorts.forEach((sort: string) => {
sort = sort.replace('-', '')
sort = sort.replace(' ', '')
confirmed = columns.includes(sort)
if (!confirmed) {
let index = sorts.indexOf(sort)
sorts.splice(index, 1)
}
})
let sortsArr = [];
sorts.forEach((sort) => {
if (sort.startsWith('-')) {
sort = sort.replace('-', '')
sortsArr.push({column: model.tableName + '.' + sort, order: 'desc'})
} else {
sortsArr.push({column: model.tableName + '.' + sort, order: 'asc'})
}
})
return sortsArr;
}
and then use it like this in the query
const sortsArr = sort(model, sorts, model.columns);
knex('users').orderBy(sortsArr)

Related

How to update entities in a table from an array?

I'm trying to figure out how to update many elements at once. Suppose I have the following array:
[
{
id: 100,
order: 1,
},
{
id: 101,
order: 2,
},
{
id: 102,
order: 3,
},
]
I then transform this array, replacing the values of order. The resulting array becomes the following:
[
{
id: 102,
order: 1,
},
{
id: 101,
order: 2,
},
{
id: 100,
order: 3,
},
]
I use this on the frontend to render a list in the appropriate order, based on the value of order.
But how can I update these 3 entities in my database?
I can obviously make 3 UPDATE statements:
const promises = [];
newArray.forEach(({ id, order }) => {
promises.push(
// executeMutation is just a custom query builder
executeMutation({
query: `UPDATE my_table SET order = ${order} WHERE id = ${id}'`
})
)
})
await Promise.all(promises)
But is it possible to do this in one query?
You can do this using the UNNEST function. First you'll need to handle the query parameters properly. https://www.atdatabases.org/ does this for you, otherwise you need to separately pass a string with placeholders and then the values. If you use #databases, the code could look like:
await database.query(sql`
UPDATE my_table
SET order = updates_table.order
FROM (
SELECT
UNNEST(${newArray.map(v => v.id)}::INT[]) as id,
UNNEST(${newArray.map(v => v.order)}::INT[]) as order
) AS updates_table
WHERE my_table.id = updates_table.id
`);
The trick here is that UNNEST lets you take an array for each column and turn that into a kind of temporary table. You can then use that table to filter & update the records.

How to update a key of object with value in json column with knexjs?

I'm trying to update a column in users table the column type is json.
column name is test.
and the column consists of an object default value for example is
{a: "text", b: 0}
how to update let's say the object key b without changing the whole column
the code i'm using is
knexDb('users').where({
email: email
})
.update({
test: { b: 1 }
})
second solution
knexDb('users').where({
email: email
})
.update({
test: knexDb.raw(`jsonb_set(??, '{b}', ?)`, ['test', 1])
})
first solution changes the whole column cell and test will be only { b: 1 }
second solution doesn't work it give an error
function jsonb_set(json, unknown, unknown) does not exist
The expected result
is to manage to update only a certain key value in an object without changing the whole object.
PS
I also want to update an array that consists of objects like the above one for example.
[{a: "text", b: 0}, {c: "another-text", d: 0}]
if i use the code above in kenxjs it'll update the whole array to only {b: 1}
PS after searching a lot found that in order to make it work i need to set column type to jsonb, in order the above jsonb_set() to work
but now i'm facing another issue
how to update multiple keys using jsonb_set
knexDb('users').where({
email: email
})
.update({
test: knexDb.raw(`jsonb_set(??, '{b}', ?)`, ['test', 1]),
test: knexDb.raw(`jsonb_set(??, '{a}', ?)`, ['test', "another-text"]),
})
the first query key b is now not updating, in fact all updates don't work except the last query key a, so can some explain why ?
Your issue is that you're overwriting test. What you're passing into update is a JS object (docs). You cannot have multiple keys with identical values (docs). You'll have to do something like this where you make 1 long string with all your raw SQL as the value to test.
knexDb('users').where({
email: email
})
.update({
test: knexDb.raw(`
jsonb_set(??, '{a}', ?)
jsonb_set(??, '{b}', ?)
`,
['test', "another-text", 'test', 1])
})
Probably a better option exists - one that would be much more readable if you have to do this for several columns is something like what I have included below. In this example, the column containing the jsonb is called json.
const updateUser = async (email, a, b) => {
const user = await knexDb('users')
.where({ email })
.first();
user.json.a = a;
user.json.b = b;
const updatedUser = await knexDb('users')
.where({ email })
.update(user)
.returning('*');
return updatedUser;
}
Update/insert a single field in a JSON column:
knex('table')
.update( {
your_json_col: knex.jsonSet('your_json_col','$.field', 'new value')
})
.where(...)
Update/insert multiple fields
Option 1 (nested)
knex('table')
.update( {
your_json_col: knex.jsonSet(knex.jsonSet('your_json_col','$.field1', 'val1')
'$.field2', 'val2')
})
.where(...)
Option 2 (chained)
knex('table')
.update( {
your_json_col: knex.jsonSet('your_json_col','$.field1', 'val1')
})
.update( {
your_json_col: knex.jsonSet('your_json_col','$.field2', 'val2')
})
.where(...)

Searching an array of items in NetSuite using SuiteScript 2

I am attempting to write a suitescript search that allows me to pass an array of identifiers to a particular field in Netsuite and return the results. I have tried 'ANYOF', 'ALLOF' and "WITHIN' but I keep getting errors
Here is my code so far:
if(params.type=='sku'){
var filter_name = 'itemid';
}else{
var filter_name = 'upccode';
}
var filters = [
search.createFilter({
name: filter_name,
operator: search.Operator.ANYOF,
values: ['HERHR5201','HERHR5202','HERHR5203']
}),
];
var s = search.create({
'type': record.Type.INVENTORY_ITEM,
'filters':filters,
}).run();
s = s.getRange(0,100);
return JSON.stringify(s);
Does anyone know the right sequence to create a multiple search of itemid's? Also, for a bonus, is there a way to have the resultset return the columns I need verses just the ideas? Do I need to createColumn?
You cannot use ANYOF, ALLOF, etc. when filtering a search on a text field. You'll need to create a filter expression with ORs to search on multiple values.
I would do this:
if(params.type=='sku'){
var filter_name = 'itemid';
}else{
var filter_name = 'upccode';
}
var filters = [
[filter_name, 'is', 'HERHR5201'], 'OR',
[filter_name, 'is', 'HERHR5202'], 'OR',
[filter_name, 'is', 'HERHR5203']
];
var s = search.create({
'type': record.Type.INVENTORY_ITEM,
'filters':filters
}).run();
As far as returning specific columns from your search, you'll need to use search.createColumn(), as you point out. So it'd be something like:
//Previous code...
var s = search.create({
'type': record.Type.INVENTORY_ITEM,
'filters':filters,
'columns': [search.createColumn({name: 'internalid'}),
search.createColumn({name: 'upccode'}),
search.createColumn({name: 'itemid'})
/*Other columns as needed*/]
}).run();
The provided answer is correct, however based on your example code provided I am assuming that the search needs to be created somewhat dynamically. Meaning the 'array of identifiers' you mention will not always be the same, nor will they always be the same length. In order to create a search that is completely dynamic based on incoming 'array of identifiers' you would need to get pretty creative. In the below solution I am assuming the function parameter 'params' is an object with a 'type' property, and an arrIn (array of strings) property. The search below uses the formula function 'DECODE', a description of which can be found here.
function execute(params) {
var filter_name;
var itemSearchObj;
var stringArr = '';
var arrIn = params.arrIn;
var i;
var count;
// create search filter type
filter_name = params.type === 'sku' ? 'itemid' : 'upccode';
// create stringArr using incoming arrIn
for (i = 0; arrIn && arrIn.length > i; i += 1) {
stringArr += i > 0 ? ", '" + arrIn[i] + "', 'true'" : "'" + arrIn[i] + "', 'true'";
}
if (arrIn.length > 0) {
itemSearchObj = nsSearch.create({
type: 'item',
filters: [
["formulatext: DECODE({" + filter_name + "}," + stringArr + ")", "is", 'true']
],
columns: [
'itemid', // dont need to get fancy here, just put the internal id of whichever fields you want in the columns
'description'
]
});
count = itemSearchObj.runPaged().count;
itemSearchObj.run().each(function (result) {
// Do things for each result
});
}
}
I found this quite a bit of a curveball as I was overthinking it. The search requires an array:
results = search.create({
type: search.Type.CUSTOMER,
filters: [],
columns: [ search.createColumn({ name: "internalid", sort: search.Sort.ASC }) ]
})
This filter array consists of a search term array and logical operators (AND, OR).
['fieldid', 'is', yourValue], // search term array
'AND' // logical operator
So you can create your own filter array, by pushing in the search terms and logical operators as required.
For example if you want to return the internal id for customer emails in an array:
let email = ['abc#test.com', 'def#test.com'];
let myFilter = createFilter(email);
function createFilter(email){
let filter = [];
email.forEach((result, index) => {
if(index > 0)
filter.push('OR'); // Logical Operator
filter.push(['email', 'is', result]);
});
return filter;
}
Variable myFilter will now equal:
[ ['email', 'is', 'abc#test.com'], 'OR', ['email', 'is', 'def#test.com'] ]
And can be directly referenced as the filter in the search.
let results = search.create({
type: search.Type.CUSTOMER,
filters: myFilter,
columns: [ search.createColumn({ name: "internalid", sort: search.Sort.ASC }) ]
})

sequelize findAll sort order in nodejs

I'm trying to output all object list from database with sequelize as follow and want to get data are sorted out as I added id in where clause.
exports.getStaticCompanies = function () {
return Company.findAll({
where: {
id: [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680]
},
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at']
});
};
But the problem is after rendering, all data are sorted out as follow.
46128, 53326, 2865, 1488, 45600, 61680, 49569, 1418, ....
As I found, it's neither sorted by id nor name. Please help me how to solve it.
In sequelize you can easily add order by clauses.
exports.getStaticCompanies = function () {
return Company.findAll({
where: {
id: [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680]
},
// Add order conditions here....
order: [
['id', 'DESC'],
['name', 'ASC'],
],
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at']
});
};
See how I've added the order array of objects?
order: [
['COLUMN_NAME_EXAMPLE', 'ASC'], // Sorts by COLUMN_NAME_EXAMPLE in ascending order
],
Edit:
You might have to order the objects once they've been recieved inside the .then() promise. Checkout this question about ordering an array of objects based on a custom order:
How do I sort an array of objects based on the ordering of another array?
If you want to sort data either in Ascending or Descending order based on particular column, using sequlize js, use the order method of sequlize as follows
// Will order the specified column by descending order
order: sequelize.literal('column_name order')
e.g. order: sequelize.literal('timestamp DESC')
If you are using MySQL, you can use order by FIELD(id, ...) approach:
Company.findAll({
where: {id : {$in : companyIds}},
order: sequelize.literal("FIELD(company.id,"+companyIds.join(',')+")")
})
Keep in mind, it might be slow. But should be faster, than manual sorting with JS.
You can accomplish this in a very back-handed way with the following code:
exports.getStaticCompanies = function () {
var ids = [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680]
return Company.findAll({
where: {
id: ids
},
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at'],
order: sequelize.literal('(' + ids.map(function(id) {
return '"Company"."id" = \'' + id + '\'');
}).join(', ') + ') DESC')
});
};
This is somewhat limited because it's got very bad performance characteristics past a few dozen records, but it's acceptable at the scale you're using.
This will produce a SQL query that looks something like this:
[...] ORDER BY ("Company"."id"='46128', "Company"."id"='2865', "Company"."id"='49569', [...])
May be a little late but want to mention an approach.
Sorting based on the [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680] can be done using ARRAY_POSITION function of postgreSQL.
const arr = [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680];
const ord = [sequelize.literal(`ARRAY_POSITION(ARRAY[${arr}]::integer[], "id")`)];
return Company.findAll({
where: {
id: arr
},
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at'],
order: ord,
});
I don't think this is possible in Sequelize's order clause, because as far as I can tell, those clauses are meant to be binary operations applicable to every element in your list. (This makes sense, too, as it's generally how sorting a list works.)
So, an order clause can do something like order a list by recursing over it asking "which of these 2 elements is older?" Whereas your ordering is not reducible to a binary operation (compare_bigger(1,2) => 2) but is just an arbitrary sequence (2,4,11,2,9,0).
When I hit this issue with findAll, here was my solution (sub in your returned results for numbers):
var numbers = [2, 20, 23, 9, 53];
var orderIWant = [2, 23, 20, 53, 9];
orderIWant.map(x => { return numbers.find(y => { return y === x })});
Which returns [2, 23, 20, 53, 9]. I don't think there's a better tradeoff we can make. You could iterate in place over your ordered ids with findOne, but then you're doing n queries when 1 will do.
if required, databases order their output by the generic order of values in the order by fields.
if your order is not like this, you may add to the select an order_field, and give it a value based upon the value in id:
case
when id=46128 then 0
when id=2865 then 1
when id=49569 then 2
end as order_field
and order by order_field.
if there are lots of values, you may stuff them in their original order in a temporary table with an identity primary key order_field, and inner join your select to that temporary table by your value field, ordering by order_field.
i don't know how to do this in sequelize, but found here answers on how it does things that i needed.
Worked for me by using "" quotes surrounding the property name.
For Debugging You can see what is the query that is getting generated by sequelize and then try to run it on the particular DB console.
In My Case I was not able to sort the data by last updatedAt column
Code Snippet :
exports.readAll = (req, res) => {
console.log("Inside ReadAll Data method");
let data;
if (!req.body) {
data = CompanyModel.findAll({ order: [[sequelize.literal('"updatedAt"'), 'DESC']]});
} else {
data = CompanyModel.findAll({ order: [[sequelize.literal('"updatedAt"'), 'DESC']]});
}
data
.then((data) => {
res.send(
data
);
})
.catch((err) => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving data.",
});
});
};
SQL Query getting formed in my case :
Inside ReadAll Data method
Executing (default): SELECT "company_name", "company_id", "on_record", "createdAt", "updatedAt" FROM "companies" AS "company" ORDER BY "updatedAt" DESC;
Incase anyone is using uuid type, using example by #Agniveer from above but modified
const ids = [
'f01a057e-5646-4527-a219-336804317246',
'ee900087-4910-42b4-a559-06aea7b4e250',
'b363f116-1fc5-473a-aed7-0ceea9beb14d'
];
const idsFormat = `'${ids.join("','")}'`;
const order = [sequelize.literal(`ARRAY_POSITION(ARRAY[${idsFormat}]::uuid[], "<insert_table_name>"."id")`)];

Order Bookshelf.js fetch by related column value

I'm using Bookshelf.js/Knex.js, fetching a model (call it user) with a related child model (call it company).Can I order by a field on the child model - company.name?
Also, if that's possible, can I multi sort, say company.name descending then lastName ascending
Here's my current code, which only works on root model fields. qb.orderBy('company.name', 'desc') doesn't work.
users.query(function(qb) {
qb.orderBy('lastName', 'asc');
})
.fetch({withRelated: ['company']})
.then(success, error);
Try the following:
users
.fetch({withRelated: [
{
'company': function(qb) {
qb.orderBy("name");
}
}
]})
.then(success, error);
I got the idea from https://github.com/tgriesser/bookshelf/issues/361
You can do it like this without the need of a function:
users.query(function(qb) {
qb.query('orderBy', 'lastName', 'asc');
})
.fetch({withRelated: ['company']})
.then(success, error);
Found here: Sort Bookshelf.js results with .orderBy()
I think I solved it by doing this:
let postHits =
await posts
.query(qb => qb
.innerJoin('post_actor_rel', function () {
this.on('post.id', '=', 'post_actor_rel.post_id');
})
.innerJoin('actor', function () {
this.on('post_actor_rel.actor_id', '=', 'actor.id');
})
.orderByRaw('actor.name ASC')
.groupBy('id')
)
.fetchPage({
withRelated: ['roles', 'themes', 'activity_types', 'subjects', 'educational_stages', 'images', 'documents', 'actors'],
limit,
offset
},
);
I modify the query by inner joining with the desired tables and after sorting (using orderByRaw since I will need to add some more sorting that I think is not possible with orderBy) I group by the post's id to get rid of the duplicate rows. The only problem is that it's not defined which actor name (of several possible) is used for the sorting.

Resources