I have a SQL query (Postgres) containing several joins that I'm having trouble converting into a single Knex.js statement. Here's the SQL query:
SELECT
User.id, User.name, User.email,
Role.name AS r_name,
UserPofile.id AS p_id, UserPofile.date_of_birth AS p_dob,
AuthToken.id AS at_id, AuthToken.token AS at_token, AuthToken.platform AS at_platform
FROM public."user" User
LEFT JOIN public."user_role" UserRole ON User.id = UserRole.user_id
LEFT JOIN public."role" Role ON UserRole.role_id = Role.id
LEFT JOIN public."application" Application ON UserProfile.app_id = Application.id
LEFT JOIN public."user_profile" UserProfile ON User.id = UserProfile.user_id
LEFT JOIN public."auth_token" AuthToken ON User.id = AuthToken.user_id
WHERE
User.email LIKE 'some#email.com' AND
Application.name LIKE 'awesome-application' AND
AuthToken.platform LIKE 'mobile';
Here's my Knex.js code:
return knex('user').where({ email:'some#email.com' })
.select([
'user.id', 'user.name', 'user.email' // User
'role.name AS rName' // Roles
'user_profile.id AS pId', 'user_profile.date_of_birth AS pDob' // UserProfiles
'auth_token.id AS atId' 'auth_token.platform AS atPlatform', 'auth_token.token AS atToken' // AuthTokens
])
.leftJoin('user_profile', 'user_profile.user_id', 'user.id')
.leftJoin('user_role', 'user_role.user_id', 'user.id')
.leftJoin('role', 'role.id', 'user_role.role_id')
.leftJoin('auth_token', 'auth_token.user_id', 'user.id')
.then(users => {
users = users.filter(user => {
return user.pApp_id === appId && user.atApp_id === appId && user.atPlatform === platform;
});
return users;
});
This produces the same result that the SQL query does, but the problem is that I have to filter the returned users in the .then() clause of the Knex call because I don't know how to add WHERE conditions for the Application.name and AuthToken.platform.
Question:
Can someone please help me figure out how to structure my Knex code's .where() clause to have it be equivalent to the SQL query?
Notes:
I don't know how to console.log the SQL queries that Knex produces, therefore I'm not entirely sure that my current Knex code will produce the SQL query above it (minus the correct WHERE clause). I have checked though that it does in fact return the same results, by running the query in PgAdmin and console.log()ing the users returned in from the Knex function.
I haven't included the CREATE TABLE / Knex migrations that defined the tables and columns used in this question, because to me it didn't feel necessary, and I don't want to make an already long question even longer. But if you need to see it, please don't hesitate to let me know. I'll gladly include it.
To debug you can use .toSQL() to debug your knex queries documentation.
Also, nice cheatsheet
As hot fix solution you can use .raw() and paste your SQL code there.
About WHERE conditions, you can just chain them in the end of your knex query.
Something like this:
return knex('user')
.select('user.id', 'user.name', 'user.email',
'role.name AS rName'
'user_profile.id AS pId', 'user_profile.date_of_birth AS pDob',
'auth_token.id AS atId' 'auth_token.platform AS atPlatform', 'auth_token.token AS atToken'
)
.leftJoin('user_profile', 'user_profile.user_id', 'user.id')
.leftJoin('user_role', 'user_role.user_id', 'user.id')
.leftJoin('role', 'role.id', 'user_role.role_id')
.leftJoin('auth_token', 'auth_token.user_id', 'user.id')
.where('user.email', 'like', '%some#email.com%')
.andWhere('application.name', 'like' '%awesome-application%')
//...etc
Simplest solution is use .whereRaw():
knex whereRaw
Example:
.leftJoin('auth_token', 'auth_token.user_id', 'user.id')
.whereRaw('User.email LIKE ?', `${your_param_here}`)
.andWhereRaw('AuthToken.platform LIKE ?', `${your_param_here}`)
Related
I am working in yii2.
There are employee and company table employee contains company_id.
I have a filter search running properly If I use joinWith()
$query = Employee::find();
$query->joinWith(['company']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
'sort' => false,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
//and below is the filterwhere
$query->andFilterWhere(['like', 'company.name', $this->company_id]);
But issue came when I make a query using with()
$query = Employee::find()->with(['company']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
'sort' => false,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
//when query contain with() then this filter is not working.
$query->andFilterWhere(['like', 'company.name', $this->company_id]);
This gives error when I use with()
Database Exception – yii\db\Exception
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'company.name' in 'where clause'
The SQL being executed was: SELECT COUNT(*) FROM `employee` WHERE `company`.`name` LIKE '%1%'
Here is the relation in employee with company:
public function getCompany(){
return $this->hasOne(Company::className(), ['id'=> 'company_id']);
}
Can anyone help me or guide me how could I filter data properly using with() in a query?
Thanks.
You can't swap joinWith() and with() methods when you need to filter by the column from the related table. That's because these methods does completely different things.
Methods like joinWith() and join() actually modifies the query to add the "JOIN" part to the SQL query. The with in joinWith allows you to specify the joined table by the relation definition in the model. The eager loading in joinWith is only side effect and you can even turn that off by passing false as second parameter.
When you do:
Employee::find()->joinWith(['company'])->all();
The query that is run looks like:
SELECT * FROM employee LEFT JOIN company ON (...)
On the other side the method with() doesn't modify the query itself. It only forces the eager loading of related models. In reality the second query is used for preloading the related records.
When you do:
Employee::find()->with(['company'])->all();
It actually runs queries like these:
SELECT * FROM employee;
SELECT * FROM company WHERE id IN (...company ids selected in first query...);
So when you try to do:
$query = Employee::find()
->with(['company'])
->andFilterWhere(['like', 'company.name', $this->company_id])
->all();
The generated query is
SELECT * FROM employee WHERE company.name LIKE ...
How can I transform this sql to codeigniter query? I have screenshot of an EER diagram to help you understand better. So there are three tables to join. I guess, one is "pjesma", then "izvodjac", and "izvodi_pjesmu". This sql works when I run it and gives me results I want. I am also using pagination so I need limit and offset somehow included.
SELECT pjesma_id, naslov, naziv
FROM pjesma p, izvodjac i, izvodi_pjesmu ip
WHERE p.pjesma_id = ip.pjesma_pjesma_id
AND i.izvodjac_id = ip.izvodjac_izvodjac_id
in model:
public function paginacija_pjesme($limit, $offset) {
$this->db->select('pjesma_id', 'naslov', 'naziv');
$this->db->from('pjesma p');
$this->db->join('izvodi_pjesmu ip', 'p.pjesma_id=ip.pjesma_pjesma_id');
$this->db->join('izvodjac i', 'i.izvodjac_id=ip.izvodjac_izvodjac_id');
$this->db->limit($limit, $offset);
$query = $this->db->get();
return $query->result();
}
EDIT:
So in select I used wrong syntax, this line:
$this->db->select('pjesma_id', 'naslov', 'naziv');
should be like this:
$this->db->select('pjesma_id, naslov, naziv');
A few things, as you are dealing with objects within the query builder class, you can method-chain and make things a little less cluttered.
You are maybe getting an ambiguous column error, joining and not prefixing your select. Hard to tell without the error being posted you are facing.
Always best to (just in case) set defaults to your method arguments as well, in case you pass a blank variable (or don't need to for whatever reason).
When you are using multiple tables in the FROM statement you are actually doing a CROSS JOIN, and your new code is giving INNER JOIN,trying the join syntax direct to your SQL server might help as you are not actually doing the same thing between your first written query, and the codeigniter one..
Try the below with LEFT join just to see... If there are NULL joins using INNER they will be omitted
public function paginacija_pjesme($limit = 10, $offset = 0) {
$query = $this->db->select('p.pjesma_id, p.naslov, i.naziv')
->from('pjesma p')
->join('izvodi_pjesmu ip', 'p.pjesma_id = ip.pjesma_pjesma_id', 'left')
->join('izvodjac i', 'i.izvodjac_id = ip.izvodjac_izvodjac_id', 'left')
->limit($limit, $offset)
->get();
return $query->result();
}
Are you getting any errors? what do you see if you print_r($query->result()) ?
try also spitting out $this->db->last_query() - this will output the SQL codeigniter has build. Then running this directly to your database, and see the results from there. You might even see the issue without needing to run it.
This is an example of what your current codeigniter code is generating (as you can see, different to your original query):
SELECT pjesma_id, naslov, naziv
FROM pjesma p
INNER JOIN izvodi_pjesmu ip ON p.pjesma_id = ip.pjesma_pjesma_id
INNER JOIN izvodjac i ON i.izvodjac_id = ip.izvodjac_izvodjac_id
# LIMIT 10,0
I'm building a Node.js app that needs to query a Redshift database (based on postgres 8.0.2) using CTEs. Unfortunately, the SQL query builders I've looked at thus far (node-sql, knex.js and sequelize) don't seem to support common table expressions (CTEs).
I had great success forming common table expressions in Ruby using Jeremy Evans' Sequel gem, which has a with method that takes two arguments to define the tables' aliased name and the dataset reference. I'd like something similar in Node.
Have I missed any obvious contenders for Node.js SQL query builders? From what I can tell these are the four most obvious:
node-sql
nodesql (no postgres support?)
knex.js
sequelize
knex.js now supports WITH clauses:
knex.with('with_alias', (qb) => {
qb.select('*').from('books').where('author', 'Test')
}).select('*').from('with_alias')
Outputs:
with "with_alias" as (select * from "books" where "author" = 'Test') select * from "with_alias"
I was able to use common table expressions (CTEs) with knex.js and it was pretty easy.
Assuming you're using socket.io along with knex.js,
knex-example.js:
function knexExample (io, knex) {
io.on('connection', function (socket) {
var this_cte = knex('this_table').select('this_column');
var that_cte = knex('that_table').select('that_column');
knex.raw('with t1 as (' + this_cte +
'), t2 as (' + that_cte + ')' +
knex.select(['this', 'that'])
.from(['t1', 't2'])
)
.then(function (rows) {
socket.emit('this_that:update', rows);
});
})
}
module.exports = knexExample;
From this issue and this issue I understand that you can use CTEs with Sequelize.
You need to use the raw queries and maybe precise the type, to be sure that Sequelize understands it's a Select query. See first link.
Sample code would be:
sequelize.query(
query, //raw SQL
tableName,
{raw: true, type: Sequelize.QueryTypes.SELECT}
).success(function (rows) {
// ...
})
See here for mode details.
xql.js supports WITH clause from version 1.4.12. A small example:
const xql = require("xql");
const SELECT = xql.SELECT;
return SELECT()
.WITH("with_alias", SELECT().FROM("books").WHERE("author", "=", "Test"))
.FROM("with_alias");
The WITH clause is supported for SELECT, INSERT, UPDATE, DELETE, and compound queries as well.
I have used too much time (days) on this and I really hope someone can help me out.
I found a good article on describing my problem in a generic way so let's stick to it.
I am trying to build this query but NHibernate fails to build the correct sql and returns a sql query exception.
Column vSagsAendring.Id is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. It could not execute the following query:
select
viewsagsae0_.Id as Id155_,
viewsagsae0_.SagId as SagId155_,
viewsagsae0_.JournalNr as JournalNr155_,
viewsagsae0_.LbfNr as LbfNr155_,
viewsagsae0_.OrgNr as OrgNr155_,
viewsagsae0_.OrgNavn as OrgNavn155_,
viewsagsae0_.AfdNavn as AfdNavn155_,
viewsagsae0_.SagsType as SagsType155_,
viewsagsae0_.Status as Status155_,
viewsagsae0_.SagsbehandlerInit as Sagsbeh10_155_,
viewsagsae0_.Dato as Dato155_,
viewsagsae0_.JournalAktionType as Journal12_155_,
viewsagsae0_.Beskrivelse as Beskriv13_155_,
viewsagsae0_.Ekstern as Ekstern155_
from vSagsAendring viewsagsae0_
group by viewsagsae0_.SagId
var query = from p in _session.Query<ViewSagsAendring>()
group p by p.SagId
into grp
select grp.OrderByDescending(g => g.Dato).First();
This is another version also took from the article:
var query = from p in _session.Query<ViewSagsAendring>()
group p by p.SagId
into grp
let maxDato = grp.Max(g => g.Dato)
from p in grp
where p.Dato == maxDato
select p;
It's have been a long journey, but now it's over. I hope that I can help someone else in the same situation by answering my own question.
var aendring = from sagsAendring in _session.Query<ViewSagsAendring>()
where sagsAendring.Dato ==
(
from innersagsAendring in _session.Query<ViewSagsAendring>()
where innersagsAendring.SagId == sagsAendring.SagId
select innersagsAendring.Dato
).Max()
select sagsAendring;
var result = aendring.ToList();
And because you can chain linq statements you can build a linq filter like this
if(Filters.VisInterneAendringer == false)
query = query.Where(x => x.Ekstern == true);
if (Filters.VisKunNyesteAendringer)
{
query = query.Where(sagsAendring => sagsAendring.Dato ==
(
from innerSagsAendring in Session.Query<ViewSagsAendring>() where innerSagsAendring.SagId == sagsAendring.SagId
select innerSagsAendring.Dato
).Max());
}
return query;
Your queries seem legit for LINQ in EntityFramework.
I'm not sure about hibernate, you might try to use QueryOver API instead of Query
http://nhibernate.info/blog/2009/12/17/queryover-in-nh-3-0.html
I wanted to ask is it possible to specify a where clause after a join in order to match data to a users input. Say for example I have the following code:
$this->db->select('user.*,role.*')
$this->db->from('user');
$this->db->where('user.username', $username);
$this->db->where('user.password', $password);
$this->db->join('role','role.id = user.role_id')
$result = $this->db->get();
and I want to query the matching data from the table 'role':
$this->db->select('user.*,role.*')
$this->db->from('user');
$this->db->where('user.username', $username);
$this->db->where('user.password', $password);
$this->db->join('role','role.id = user.role_id')
$this->db->where('role.speaker', $speaker); //want to know if this is correct
$result = $this->db->get();
Is this possible? Can I compare results (using WHERE) after a JOIN? Will that produce results matching to the WHERE clause?
Thanks!
CodeIgniter doesn't actually build the query until ->get() is called. You can call the methods in whatever order you want.