sqlMessage: "Column 'updated_at' in where clause is ambiguous" - NodeJS Sequelize - sql

created_at and updated_at
my code :
if(keyword){
let keywordSplit = keyword.split('&');
console.log('split', keywordSplit);
keywordSplit.map(el => {
const data = el.split("=");
advanceSearchCondition.push({
[`$${data[0]}$`]: { // here is column
[Op.substring]: `${data[1]}` // here is '%keyword%'
// [Op.like]: Sequelize.literal(`\'%${data[1]}%\'`)
}
});
})
}
did you know why ? because all of my data have created_at and updated_at also column name and description is ambiguous, i have tried refactor all name and its work , but i think how about created_at and updated_at ? should i refactor ???
how to ignore the ambiguous for specify for example created_at for nodeJS ,
i really neeeded the column but i cant query operation for the column ,
oh ya , i make these project to snake_case on configuration sequelize.
also when i tried remove $ on column its not counter the error
but when i tried like these
updated_at: {
[Op.substring]: `${data[1]}`
// [Op.like]: Sequelize.literal(`\'%${data[1]}%\'`)
}
its work no ambiguous
updated_at: // not ambiguous
vs
[`$${data[0]}$`]: // ambiguous
vs
[`${data[0]}`]: // ambiguous
i know i can handle with if condition
if (data[0] == 'created_at') {
advanceSearchCondition.push({
created_at: {
[Op.substring]: `${data[1]}`
}
});
} else if (data[0] == 'updated_at') {
advanceSearchCondition.push({
updated_at: {
[Op.substring]: `${data[1]}`
}
});
} else {
advanceSearchCondition.push({
[`$${data[0]}$`]: {
[Op.substring]: `${data[1]}`
}
});
}
but i looking for the best practice any other solution ?

Related

Sequelize allownull constraint conditionally

I need a migration in sequelize to change a column. How can I use allowNull constraint on a column based on the value of another column?
For example consider I have columns A and B. In the migration I wanna have something like below:
queryInterface.changeColumn('book', ['A'], {
allowNull: false,
where: { B: true }
});
But as I see in the examples, we can't use 'where' in changeColumn.
I think you must use customValidator for this problem like this :
queryInterface.changeColumn('book', ['A'], {
allowNull: true,
validate: {
customValidator(value) {
if (value === null && this.B) {
throw new Error("A not be null if B === true");
}
}
}
});

How to loop through an array containing objects and do comparison

I am using ionic 4. I get the result from the API then get the result show like this
[
{"name":John,"age":20},
{"name":Peter,"age":35},
{"name":Alex,"age":15}
]
But I want to get the name only to check whether have same name with my condition or not. But I cannot straight a way get the result from the API, I need to hard code to do comparison. Here is my code:
this.http.get(SERVER_URL).subscribe((res) => {
const data = [
{ name: John, age: 21 },
{ name: Thomas, age: 25 },
];
const ppl= data.find(people=> people.name === 'alex');
console.log(ppl);
});
So, My first question is How to get the name from the API directly, not like now I hard code the result from API. My Second Question is when I do comparison I want to show the result 'already exist' or 'can use this name'. Because if I write my code like this I will get the error Type 'void' is not assignable to type 'boolean':
const ppl= data.find((people)=> {
if(people.name === 'alex') {
this.text = 'already exist'
} else {
this.text = 'can use this name'
}});
console.log(ppl);
Anyone can help me? Thank you very much
Instead of defining data, use the contents of the response; res will have the exact same contents that you are declaring in data.
this.http.get(SERVER_URL).subscribe(res => {
// If successful, res is an array with user data like the following
// [
// {name: "John", age: 21},
// {name: "Thomas", age: 25},
// ...
// ]
if (res.find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
});
Taken from the MDN docs on Array.prototype.find():
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
In that case
res.find(user => user.name === 'alex')
will return a user object if any of the usernames match alex, or undefined if none of the user.name attributes match alex.
undefined evaluates to false and a user object evaluates to true in the conditional.
Keep in mind that you are comparing strings with ===, so, for example, Alex will not match alex, if you want to look into other ways to compare strings, have a look at this question.
You also might want to handle errors, how you handle them is up to you, and it will depend on the response, but you can access the error inside your subscribe like this:
this.http.get(SERVER_URL).subscribe(res => {
if (res.find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
}, error => {
console.log(error);
}, () => {
// There is also a 'complete' handler that triggers in both cases
});
Edit. API returns Object not array
If your API returns an Object instead of an array like in your question, you can still iterate over the properties
this.http.get(SERVER_URL).subscribe(res => {
// If successful, res is an array with user data like the following
// {
// key1: {name: "John", age: 21},
// key2: {name: "Thomas", age: 25},
// ...
// }
let match = false;
Object.keys(res).forEach(key => {
if (res[key].name === 'alex') {
match = true;
}
});
if (match) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
});
Instead of Object.keys() you could use Object.values() to get an array with user objects, then use find() as before, but that seems less efficient, something like this:
if (Object.values(res).find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}

Why is Date query with aggregate is not working in parse-server?

I want to query user where updatedAt is less than or equal today using aggregate because I'm doing other stuff like sorting by pointers.
I'm using cloud code to define the query from the server.
I first tried using mongoDB Compass to check my query using ISODate and it works, but using it in NodeJS seems not working correctly.
I also noticed about this problem that was already fix, they say. I also saw their tests.
Here's a link to that PR.
I'm passing date like this:
const pipeline = [
{
project: {
_id: true,
process: {
$substr: ['$_p_testdata', 12, -1]
}
}
},
{
lookup: {
from: 'Test',
localField: 'process',
foreignField: '_id',
as: 'process'
}
},
{
unwind: {
path: '$process'
}
},
{
match: {
'process._updated_at': {
$lte: new Date()
}
}
}
];
const query = new Parse.Query('data');
return query.aggregate(pipeline);
I expect value to be an array with length of 4 but only give me empty array.
I was able to fetch data without match date.
Please try this:
const pipeline = [
{
match: {
'editedBy.updatedAt': {
$lte: new Date()
}
}
}
];

Waterline ORM equivalent of insert on duplicate key update

I have a table user_address and it has some fields like
attributes: {
user_id: 'integer',
address: 'string' //etc.
}
currently I'm doing this to insert a new record, but if one exists for this user, update it:
UserAddress
.query(
'INSERT INTO user_address (user_id, address) VALUES (?, ?) ' +
'ON DUPLICATE KEY UPDATE address=VALUES(address);',
params,
function(err) {
//error handling logic if err exists
}
Is there any way to use the Waterline ORM instead of straight SQL queries to achieve the same thing? I don't want to do two queries because it's inefficient and hard to maintain.
The answer above is less than ideal. It also has the method as part of the attributes for the model, which is not correct behavior.
Here is what the ideal native solution looks like that returns a promise just like any other waterline model function would:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
return UserAddress.update({user_id: user_id}, {address: address});
} else {
// UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
Then you can just use it like:
UserAddress.updateOrCreate(id, address).then(function(ua) {
// ... success logic here
}).catch(function(e) {
// ... error handling here
});
Make a custom model method that does what you want using Waterline queries isntead of raw SQL. You will be doing two queries, but with Waterline syntax.
Example below (if you don't know about deferred objects then just use callback syntax, but the logic is the same):
var Q = require('q');
module.exports = {
attributes: {
user_id: 'integer',
address: 'string',
updateOrCreate: function (user_id, address) {
var deferred = Q.defer();
UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
// UserAddress exists. Update.
ua.address = address;
ua.save(function (err) {deferred.resolve();});
} else {
// UserAddress does not exist. Create.
UserAddress.create({user_id: user_id, address: address}).done(function (e, ua) {deferred.resolve();});
}
}).fail(function (err) {deferred.reject()});
return deferred.promise;
}
};
#Eugene's answer is good but it will always run 2 operations: findOne + update or create. I believe we can optimize it further because if the record exists we just need to run update. Example:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.update({user_id: user_id}, {address: address})
.then(function(ua){
if(ua.length === 0){
// No records updated, UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
BTW, there is an open request to implement .updateOrCreate in waterline: #790

breezejs addEntityType issue

I'm new to breezejs. I am trying to define my entity type in the client without getting metadata from the server. I have a property called ID in the server entity.
I've defaulted the naming convention in the client side to camel case using the following code.
breeze.NamingConvention.camelCase.setAsDefault();
so, I started to map the entity as follows
store.addEntityType({
shortName: "Photo",
namespace: "MyProj.Models",
dataProperties: {
id: {
dataType: DataType.Guid,
isNullable: false,
isPartOfKey: true
},
title: {
dataType: DataType.String
},
description: {
dataType: DataType.String
},
createdDate: {
dataType: DataType.DateTime
},
}
});
This worked all fine, except the id field is not getting the proper value. instead, it has the default value set by the breeze datatype ctor which is equals to Guid.Empty.
by stepping through breezejs debug script, I found out that it looks for a property name called Id in the data that comes from the ajax request. But it can't find it as the property is ID so it initialize it to empty guid string. I assumed that by setting nameOnServer property of the dataProperty id, I will be able to fix it.
store.addEntityType({
shortName: "Photo",
namespace: "MyProj.Models",
dataProperties: {
id: {
dataType: DataType.Guid,
isNullable: false,
nameOnServer: 'ID',
isPartOfKey: true
},
title: {
dataType: DataType.String
},
description: {
dataType: DataType.String
},
createdDate: {
dataType: DataType.DateTime
},
}
});
But it didn't work.
Further digging through the breez.debug.js code, in the method updateClientServerNames on line 7154, it seems it ignores the nameOnServer that I have defined.
Am I missing something here?
Okay, Feel like I spent my whole life through breeze documentation. Anyways, Finally solved the issue. To be honest, this wasn't a problem in breeze (but I wonder why it doesn't override the actual nameOnServer when I provide one). It's an error made by one of the developers in the early stage of the database implementation (probably 6 years ago). If the database adhered to Pascal Case naming convention, things would have worked perfectly fine.
As a solution I wrote a custom naming convention which corrects the naming convention error when it has ID in the name and combines it with camelCase naming convention.
var createInconsistenIDConvention = function () {
var serverPropertyNameToClient = function (serverPropertyName, prop) {
if (prop && prop.isDataProperty && (prop.nameOnServer && prop.nameOnServer === "ID")) {
return "id";
} else {
var firstSection = serverPropertyName.substr(0, 1).toLowerCase();
var idSection = "";
if (serverPropertyName.substr(1).indexOf("ID") != -1) {
firstSection += serverPropertyName.substr(1, serverPropertyName.substr(1).indexOf("ID")).toLowerCase() + "Id";
} else {
firstSection += serverPropertyName.substr(1);
}
return firstSection;
}
}
var clientPropertyNameToServer = function (clientPropertyName, prop) {
if (prop && prop.isDataProperty && (prop.nameOnServer && prop.nameOnServer.indexOf("ID") != -1)) {
return prop.nameOnServer;
} else {
return clientPropertyName.substr(0, 1).toUpperCase() + clientPropertyName.substr(1);
}
}
return new breeze.NamingConvention({
name: "inconsistenID",
serverPropertyNameToClient: serverPropertyNameToClient,
clientPropertyNameToServer: clientPropertyNameToServer
});
};
Not sure if the way I've used nameOnServer property is not correct. I couldn't find any documentation on that in breeze website.
please note that the above code only consider situations like ID, CountryID, GameID, PersonID etc.
Problem solved for now.