linq.js group by, aggregate multiple fields - linq.js

I am trying to group my data by 2 properties, and sum two other properties for each group. My code is off just a bit, because I am getting the same sum for both the fields (value and quantity). What am I missing? Thanks!
The code -
var linq = Enumerable.from(data);
var result = linq
.groupBy(
"{ pCo: $.portCo , sName: $.secName }",
"$.value, $.quantity",
"{ portCo: $.pCo, security: $.sName, value: $$.sum($.value), quantity: $$.sum($.quantity) }",
"$.pCo + ' ' + $.secName")
.toArray();

Ok, after n trials and (n-1) errors, got it to work with the following syntax:
var linq = Enumerable.from(data);
var result = linq
.groupBy(
"{ pCo: $.portCo , sName: $.secName }",
null,
"{ portCo: $.pCo, security: $.sName, value: $$.sum('$.value'), quantity: $$.sum('$.quantity') }",
"$.pCo + ' ' + $.secName")
.toArray();
The rationale for the null is not clear to me, and i needed '$.x' quotes for the property names in the sum function.
Inspiration for the solution from Jeff's answer here -
https://stackoverflow.com/a/15647792/2011729

Related

How to specify sorting by multiple columns in TypeORM's find method?

As in the find-options documentation is written this can be achieved by something like this ...
userRepository.find({
order: {
name: "ASC",
id: "DESC",
},
})
will execute the following query
SELECT * FROM "user"
ORDER BY "name" ASC, "id" DESC
... I am wondering how the order of the columns can be specified. There is no possibility to put an array of FindOptionsOrders in. The method only accepts an object.
Isn't the order of a JS Record/Object's properties undeterminated?
What if I would like to be secure, that a collection gets ordered first by id and then by the name, if I just swap the properties in the object's definition?
userRepository.find({
order: {
id: "ASC",
nams: "DESC",
},
})
BTW: OK, as an ID will be unique is most cases, this makes no sense here - but it's just their example.
In my test it is working, the options object seems to maintain the order of set properties. But this extends my question to How is that possible?
"... technically order is not guaranteed, but practically it always is"
Source: Order of multiple ORDER BY statements (GitHub issue)
Have a look at how ORDER BY statement is constructred in current implementation in Typeorm's SelectQueryBuilder.ts in master branch:
**
* Creates "ORDER BY" part of SQL query.
*/
protected createOrderByExpression() {
const orderBys = this.expressionMap.allOrderBys
if (Object.keys(orderBys).length > 0)
return (
" ORDER BY " +
Object.keys(orderBys)
.map((columnName) => {
if (typeof orderBys[columnName] === "string") {
return (
this.replacePropertyNames(columnName) +
" " +
orderBys[columnName]
)
} else {
return (
this.replacePropertyNames(columnName) +
" " +
(orderBys[columnName] as any).order +
" " +
(orderBys[columnName] as any).nulls
)
}
})
.join(", ")
)
return ""
}

Sequelize | Node.js, search between two numbers

I'm working on an advanced search with multiple options with Sequelize and Node.js.
One of the options is between two prices.
const search2 = req.body.search2;
const title = req.body.title;
const price = req.body.price;
const prices = req.body.prices;
const prices2 = req.body.prices2;
const address = req.body.address;
Product.findAll({
where: { [Op.and]:
{ price: { [Op.like]: '%' + price + '%' },
category: { [Op.like]: '%' + title + '%' },
description: { [Op.like]: '%' + search2 + '%' },
Renting: { [Op.between]: [prices, prices2] },
address: { [Op.like]: '%' + address + '%' } } }
, order: [['createdAt', 'DESC']], limit, offset
})
const prices and const prices2 are two inputs which the user can write the min and max price.
In my Sequelize model I have column 'Renting' (for renting prices, its INTEGER).
it seems to be that everything is working fine,but the problem is that i get the between prices and prices2 '50' AND '200'. (for example if im searching between 50 and 200).
enter image for example
which is giving me empty results or wrong results for different numbers.
I noticed if i put numbers beside prices and prices2, for example i will write:
Renting: { [Op.between]: [50, 200] },
i will get the right results. 50 AND 200 (without '')
enter image for example
which is good. But it just if I defined the numbers by myself.
Can anyone please help me to find a solution how to get the right result using this two inputs (prices and prices2)?
i think you should convert prices and prices2 to INTEGER before use it in query, and you can update your code
Renting: { [Op.between]: (+prices, +prices2) },
because when you putting '50' , '250' can not match it and return wrong answer.
Just a minor modification to #mohammad javad ahmadi's answer:
Renting: { [Op.between]: [+prices, +prices2] },
Op.between expects array, not tuple.

How to match the two strings with and without including spaces

For example: In DB I've the string value like "cell phones". If I get the string value like "cellphones" from frontend. How can I compare it with DB string and get the related string values in response.
You can compare so:
let val1 = 'cell phones';
let val2 = 'cellphones';
console.log(val1.replace(/\s/g, '') === val2.replace(/\s/g, '')) // true
//OR
console.log(val1.split(' ').join('') === val2.split(' ').join('')) // true
If you need some aggregation trick then you can try this
db.collection.aggregate([
{ "$project": {
"name": {
"$reduce": {
"input": { "$split": ["$name", " "] },
"initialValue": "",
"in": { "$concat": ["$$value", "$$this"] }
}
}
}},
{ "$match": { "name": "cellphones" }}
])
You can test it
Here
You can first start by stripping out the spaces on both the strings before comparing them, for example:
let a = "cell phone";
let b = "cellphone";
let c = "cell phones"
const stripSpaces = s => s.replace(/\s/g, '');
// compare
console.log(stripSpaces(a) == stripSpaces(b)); // true
console.log(stripSpaces(a) == stripSpaces(c)); // false
Just remove those spaces from the response you are getting after query find then pass the response to require input field. Then match that string with front-end or input string. If both matches load the required content.
Suppose the collection name is Category. Then the sample query will be like this
Category.find().exec((err, categories) => {
var details=[]
var matchCategory=[]
categories.map((category,key)=>{
var obj ={}
obj.name = category.name.toLowerCase().replace(/\s/g, "")
details.push(obj);
})
if(details.length > 0){
var detailsLength=details.length
details.map((category,key)=>{
if(category.name=="cellphones"){ // match your input string
matchCategory.push(category)
}
detailsLength--
})
if(detailsLength==0){
resolve(matchCategory);
}
}
})
This may help you to reach out.
Answers below this question are good, like using where and Regex, but might be at their best if you got a small number of docs that you may want to query from.
If you got many docs, I'd suggest you:
1. Use an extra field like cellphone without any space, if the values of the original field are expected to be short.
2. Try using search engines, like ElasticSearch, or MongoDB's own text search, to find what you need, not only cell phone to cellphone, but mobile phone even smartphone. Actually, when you google something, the suggestions while you're typing are also coming from similar but more complex algorithms.
Given a document like this:
{
"text" : "cell phones"
}
You could use the $where operator like this:
db.collection.find({
$where: function() {
return this.text.replace(' ', '') == "cellphones"
}
});
I wouldn't necessarily recommend this for big collections (performance might not be great). However, even with big collections you could supposedly achieve some pretty ok performance by adding an extra filter on the "text" field to filter out all documents that don't start with the correct first character(s):
db.collection.find({
"text": { $regex: "^" + "cellphones".charAt(0) }, // this will use an index on the "text" field if available
$where: function() {
return this.text.replace(' ', '') == "cellphones"
}
});
Or perhaps even this version with yet another filter in the $where bit that checks the string lengths for a reduced number of string comparisons:
db.collection.find({
"text": { $regex: "^" + "cellphones".charAt(0) }, // this will use an index on the "text" field if available
$where: function() {
return this.text.length >= "cellphones".length // performance shortcut
&& this.text.replace(' ', '') == "cellphones"
}
});
You can first start by stripping out the spaces on both the strings before comparing them. I'm assuming you don't know which one has spaces before hand, so you will run all the values through the stripSpaces function, for example:
let a = "cell phone";
let b = "cellphone";
let c = "cell phones"
const stripSpaces = (s) => s.split(' ').join('');
// compare
console.log(stripSpaces(a) == stripSpaces(b)); // true
console.log(stripSpaces(a) == stripSpaces(c)); // false
try replacing empty string from query string first, and then compare to the field as
db.test.find(function() {
return this.data(function(elm) {
"cell phones".replace(/ /g,"") == elm.name
});
});
May be it solves Your problem
Take Device_Names column contains
"cell phone"
"mobile phone"
"cellphone"
"laptop"
1.) Normal way:
select Device_Name from devices where Device_Name='cellphone' ;
result:
"cellphone"
which is third one
2.)By Remove spaces:
SELECT Device_Name FROM devices WHERE REPLACE(Device_Name, ' ', '')='cellphone'
result:
"cell phone"
"cellphone"
which includes first and third one
You can use of a regex when looking for your value, like :
cellphones|cell phones
Collection.find({
someName: {
$regex: new RegExp('cellphones|cell phones', ''),
},
});

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 }) ]
})

how to use in operator in dynamo db

I have a user table with a field username. I need to write something equivalent to this in dynamo db: Select * from user where username in('a','b','c');
Adding more from code prosepective i have usernames in an array say var arr=['a','b','c'];
I so far tried this which is giving me zero result
this.dynamo.client.scanAsync({
TableName: this.dynamo.table('users'),
FilterExpression: 'username IN (:list)',
ExpressionAttributeValues: {
':list': arr.toString()
}
}).then((response) => {
console.log(response);
return {
userFriends: result.Item.friends
};
});
When I pass one element in array it give me result searching passed single element in user table but its not working with more than one element in array.
The individual users should be given as comma separated String variables. JavaScript array is equivalent to List in AWS DynamoDB data type. The DynamoDB can't compare the String data type in database with List attribute (i.e. Array in JavaScript).
var params = {
TableName : "Users",
FilterExpression : "username IN (:user1, :user2)",
ExpressionAttributeValues : {
":user1" : "john",
":user2" : "mike"
}
};
Construct the object from array for FilterExpression:-
Please refer the below code for forming the object dynamically based on Array value.
var titleValues = ["The Big New Movie 2012", "The Big New Movie"];
var titleObject = {};
var index = 0;
titleValues.forEach(function(value) {
index++;
var titleKey = ":titlevalue"+index;
titleObject[titleKey.toString()] = value;
});
var params = {
TableName : "Movies",
FilterExpression : "title IN ("+Object.keys(titleObject).toString()+ ")",
ExpressionAttributeValues : titleObject
};
Note:-
I don't think IN clause with 1000s of usernames is a good idea in terms of performance.

Resources