KnexJS with multiple tables and aliases - mysql

I've been playing with KnexJS lately and I've got most of what I need from KnexJS documentation, however I have a bit more complex MySQL query that I can't 'port' on my own to Knex. I know there's an option to use .raw(), however I'd like to avoid that if possible.
My working MySQL query looks like this:
SELECT A.profile_id, C.model_name, D.brand_name, A.car_plate
FROM carsdb.profiles_has_cars A,
carsdb.profiles B,
carsdb.brands_cars C,
carsdb.brands D
WHERE A.profile_id = B.user_id AND
A.car_id = C.id AND
A.brand_id = D.id;
What I got so far is:
return new Promise((resolve, reject) => {
db({
A: "profiles_has_cars",
B: "profiles",
C: "brands_cars",
D: "brands"
})
.select("A.profile_id", "C.model_name", "D.brand_name", "A.car_plate")
.where({
"A.profile_id": userId,
"A.car_id": "C.id",
"A.brand_id": "D.id"
})
.then(results => {
if (results > 0) {
resolve(results);
} else {
reject("There is a problem with a query");
}
});
});
I've also tried using an object as an argument in .where(), but that didn't do anything either.
Any help or suggestion?

Oh, I've got it. knex doesn’t understand that .where values are actually references to another fields and interpret them as strings.
Try to replace such with one more for each .whereRaw("A.car_id = C.id"). (For references only, not for actual values).

Related

Query across two tables in sequelize

I am quite new to sequelize and mySQL and feel like I have tried everything in order to pass a search term ('query') to both the books table (searching against titles) and the authors table (searching against first_name or last_name). In the event of matching any of those values substrings it is to return the whole book and author information as a JSON object. When I just have the query focused on book title, it returns everything just fine. The problem comes in when I try to pass in Author columns. I have tried aliasing, nesting where clauses, everything I can think of to do and nothing I come across on here or online seems to help me figure it out.
search: (req, res) => {
const { query } = req.query;
Book.findAll({
include: [Author],
where: {
[Op.or]: [
{ title: { [Op.substring]: query } },
]},
})
.then((Book) => res.json(Book))
.catch((err) => {
console.log(err);
res.status(500).json(err);
});
},
Here is the working code. In the where clause, I want to do { first_name: { [Op.substring]: query } }, for example but it isn't accessing the Author table. In the include statement I have tried aliasing and calling it in the where clause, but that throws a aliasing error saying I have already declared an alias (Author) but when I try to use that as { 'Author.first_name' { [Op.substring]: query } }, it returns that there is no Book.Author.first_name.
I am probably missing something simple, so anyone that might be able to help, let me know what I am doing wrong here!
Solved and it was super easy. I was missing the syntax for accessing the separate table which is '$Author.first_name$'.

Sails js: select records from mysql?

I am trying to select all records from table called fairCustomer where business_type_id equal array of value.
I am using sailsjs for server and mysql for DB, in frontend I am using nativescript with typescript.
and this is my code:
filterShopType(){
this.service.serviceName = 'fairCustomers';
this.service.Get({ populate:'country_id,business_type_id',where:{ business_type_id:{ in:this.business_ids_filter } }, limit:20 }).then((data: any) => {
this.fairCustomers.splice(0, this.fairCustomers.length);
this.fairCustomers.push(data);
this.Refresh('fairCustomers');
}).catch((err) => {
console.log('failed get fairCustomers from server ', err);
});
}
service refer to http.request(), where i am using it in another place in my code.
business_ids_filter is an array of ids.
When I run this code the I am getting this error:
"message": "Could not parse the provided where clause. Refer to the Sails documentation for up-to-date info on supported query language syntax:\n(http://sailsjs.com/documentation/concepts/models-and-orm/query-language)\nDetails: Unrecognized sub-attribute modifier (in) for business_type_id. Make sure to use a recognized sub-attribute modifier such as startsWith, <=, !, etc. )",
and if I removed where I got an error result.
please anyone have any idea or solution?
you may try with a native query as described here, https://sailsjs.com/documentation/reference/waterline-orm/datastores/send-native-query
As far as I can tell, you could simply provide an array of items. Matches against ANY of the items in the array means that the record should be returned as part of the result. Not sure if it works in a where-clause though.
Docs:Query Language (scroll to: In Modifier)
I do share your confusion as to why the given query does not work though, as the docs state that in should be valid, but perhaps it's not valid inside where:{...}? And I am assuming you are using .find() inside .Get(...)? Simply proxying the query unchanged through to .find()?
filterShopType(){
this.service.serviceName = 'fairCustomers';
this.service.Get({
populate:'country_id, business_type_id',
business_type_id: this.business_ids_filter,
limit:20
})
.then((data: any) => {
this.fairCustomers.splice(0, this.fairCustomers.length);
this.fairCustomers.push(data);
this.Refresh('fairCustomers');
}).catch((err) => {
console.log('failed get fairCustomers from server ', err);
});
}
Now, you of course need to make sure that the array actually is an array if you use in:.
It is working with me by using [or] instead of [in], this is the code
filterShopType(){
if(this.business_ids_filter){
this.service.serviceName = 'fairCustomers';
var arr = [];
this.business_ids_filter.forEach( (id) => {
arr.push({ business_type_id: id })
});
let where = {};
if(arr.length > 0){
where = {or: arr};
}
this.service.Get({ populate: 'country_id,business_type_id',where:where , limit:20 }).then((data: any) => {
this.filterItems.splice(0, this.filterItems.length);
this.filterItems.push(data);
this.Refresh('filterItems');
}).catch((err) => {
console.log('failed get fair Customers from server ', err);
});
}else{
this.set('isFilter', false);
}
}

Mysql native spatial functions with sequelize and node js

I'm storing a location of type POINT in my mysql database:
module.exports = function (sequelize, DataTypes) {
var Promotion = sequelize.define('promotion', {
userId: {
type: DataTypes.STRING,
allowNull: false
},
location: {
type: DataTypes.GEOMETRY('POINT'),
allowNull: false
}
It looks like the point is being stored correctly:
I'm trying find all rows within a set of bounds using the mysql native ST_MakeEnvelope function. I was using this with postgresql and it worked perfectly, but when I switch to mysql it start throwing errors:
if (northEastLng &&
northEastLat &&
southWestLat &&
southWestLng)
{
where.location = {
$overlap: db.sequelize.fn('ST_MakeEnvelope', southWestLng, southWestLat, northEastLng, northEastLat)
}
}
promotion.findAll({
where,
})
.then(promoters => {
res.json(promoters)
})
.catch(err => {
console.log('ERROR: ', err)
})
The error:
Error: ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT:
Incorrect parameter count in the call to native function 'ST_MakeEnvelope'
So I tried passing in two points instead:
const POINT_ONE = `POINT(${southWestLng} ${southWestLat})`
const POINT_TWO = `POINT(${northEastLng} ${northEastLat})`
where.location = {
$overlap: db.sequelize.fn('ST_MakeEnvelope', POINT_ONE, POINT_TWO)
}
Now I'm getting this error:
SequelizeDatabaseError: UNKNOWN_CODE_PLEASE_REPORT:
Geometry byte string must be little endian.
Been searching around, and not too sure where to go from here. How do I use the ST_MakeEnvelope function with sequelize to query?
Edit
Adding the generated sql from piotrbienias response:
SELECT `promotion`.`id`, `promotion`.`userId`, `promotion`.`title`, `promotion`.`description`, `promotion`.`startDate`, `promotion`.`endDate`, `promotion`.`isIndefinite`, `promotion`.`isApproved`, `promotion`.`status`, `promotion`.`reach`, `promotion`.`trustRanking`, `promotion`.`isLocationBased`, `promotion`.`address`, `promotion`.`city`, `promotion`.`state`, `promotion`.`zip`, `promotion`.`location`, `promotion`.`createdAt`, `promotion`.`updatedAt`, `promotion`.`categoryId
`, `promotionImages`.`id` AS `promotionImages.id`, `promotionImages`.`url` AS `promotionImages.url`, `promotionImages`.`publicId` AS `promotionImages.publicId`, `promotionImages`.`secureUrl` AS `promotionImages.secureUrl`, `promotionImages`.`isApproved` AS `promotionImages.isApproved`, `promotionImages`.`createdAt` AS `promotionImages.createdAt`, `promotionImages`.`updatedAt` AS `promotionImages.updatedAt`, `promotionImages`.`promotionId` AS `promotionImages.promotionId`, `category`.`id` AS `cat
egory.id`, `category`.`title` AS `category.title` FROM `promotions` AS `promotion` LEFT OUTER JOIN `promotionImages` AS `promotionImages` ON `promotion`.`id` = `promotionImages`.`promotionId` LEFT OUTER JOIN `categories` AS `category` ON `promotion`.`categoryId` = `category`.`id` WHERE `promotion`.`location` && ST_MakeEnvelope(ST_GeomFromText('POINT(-80.30252222253421 25.802030960352745)'), ST_GeomFromText('POINT(-80.30252222253421 25.802030960352745)'));
I am not familiar with the Geometry part of MySQL, but shouldn't you use the ST_GeomFromText function on those points before using them?
where.location = {
$overlap: db.sequelize.fn(
'ST_MakeEnvelope',
db.sequelize.fn('ST_GeomFromText', POINT_ONE),
db.sequelize.fn('ST_GeomFromText', POINT_TWO),
)
}
};
Just as it is presented in the example of ST_MakeEnvelope function (and it takes two parameters, so that may be the reason of your first error Incorrect parameter count in the call to native function 'ST_MakeEnvelope'). And, as I said on the beginning, I am definitely not an expert in these, just my suggestion after taking a look at the MySQL documentation.
EDIT
Below is description of $overlap from Sequelize documentation
$overlap: [1, 2] // && [1, 2] (PG array overlap operator)
I think that you should construct your Sequelize query differently, without the $overlap. I suppose that you should use ST_Contains function and your where object should be as follows
{
where: db.sequelize.where(
db.sequelize.fn(
'ST_Contains',
db.sequelize.fn(
'ST_MakeEnvelope',
db.sequelize.fn('ST_GeomFromText', POINT_ONE),
db.sequelize.fn('ST_GeomFromText', POINT_TWO)
),
db.sequelize.col('promotion.location')
),
'=',
1
)
}
In my opinion the SQL you want should be something like:
WHERE ST_Contains(POLYGON(...), location)
Above Sequelize query would generate
WHERE ST_Contains(ST_MakeEnvelope(ST_GeomFromText(POINT_ONE), ST_GeomFromText(POINT_TWO)), location) = 1;
Which should check if polygon created from two points contains value of location column. ST_Contains returns 1 or 0, so I think that = 1 condition should be ok in this case.

Specified method is not supported MySql Entity Framwork 6

I am trying to run the following Linq query from MySQL client
query = query.Where(c => c.CustomerRoles
.Select(cr => cr.Id)
.Intersect(customerRoleIds)
.Any()
);
This code looks okay, but gives the error:
System.NotSupportedException: Specified method is not supported.at MySql.Data.Entity.SqlGenerator.Visit(DbIntersectExpression expression)
This looks to me like an issue with .Intersect. Can anybody tell me the cause of this error and how to fix it?
i think #GertArnold's post is a correct and best of the answers, but i'm wonder why have you gotten NotSupportedException yet ? so the problem should not be from intersect probably.
where is customerRoleIds come from ? is it IQueryable<T> ?
break the query, and complete it step by step.
if you don't get exception at this lines:
var a = query.Select(c => new {
c,
CustomerRoleIDList = c.CustomerRoles.Select(cr => cr.Id).AsEnumerable()
})
.ToList();
var b = customerRoleIds.ToList();
you must get the result by this:
var b = query.Where(c => c.CustomerRoles.any(u => customerRoleIds.Contains(u.Id)))
.ToList();
if you get exception by above query, you can try this final solution to fetch data first, but note by this, all data will be fetched in memory first:
var a = query.Select(c => new {
c,
CustomerRoleIDList = c.CustomerRoles.Select(cr => cr.Id).AsEnumerable()
})
.ToList();
var b = a.Where(c => c.CustomerRoleIDList.any(u => customerRoleIds.Contains(u)))
.Select(u => u.c)
.ToList();
Using Intersect or Except is probably always troublesome with LINQ to a SQL backend. With Sql Server they may produce horrible SQL queries.
Usually there is support for Contains because that easily translates to a SQL IN statement. Your query can be rewritten as
query = query.Where(c => c.CustomerRoles
.Any(cr => customerRoleIds.Contains(cr.Id)));
I don't think that customerRoleIds will contain many items (typically there won't be hundreds of roles), otherwise you should take care not to hit the maximum number of items allowed in an IN statement.
query.Where(c => c.CustomerRoles
.Any(v=>customerRoleIds.Any(e=>e == v.Id))
.Select(cr => cr.Id))
.ToList();
Try adding toList() before intersect, that should enumerate results locally instead running on MySql, you will get performance hit thought.
query = query.Where(c => c.CustomerRoles.Select(cr => cr.Id)).ToList().Intersect(customerRoleIds);

LINQ to SQL select all fields in a table but with a distinct column

I need to return a list of counties, but I need to filter out duplicate phone code values. For some reason I'm having trouble with the syntax. Can anyone show me how to do this? Should I be using the group by instead?
Group by would work if you need the actual entity.
var query = db.Counties.GroupBy( c => new { c.CountyName, c.PhoneCode } )
.Select( g => g.FirstOrDefault() );
Or if you are constructing it for a view model and only need the data, you could use Distinct. The following creates an anonymous type that could be used to populate the model.
var query = db.Counties.Select( c => new { c.CountyName, c.PhoneCode } )
.Distinct();