Limit join to one row using knex - greatest-n-per-group

I am trying to fetch data from two tables using a join. The problem is forum_posts will contain several items with the same thread_id. I would only like to get the first one, either by id or by creating date.
function getByGroup(groupId) {
return knex('forum_threads')
.select('forum_threads.id', 'forum_threads.updated_at', 'forum_posts.content')
.where('forum_threads.group_id', '=', groupId)
.leftJoin('forum_posts', function() {
this.on('forum_posts.thread_id', '=', 'forum_threads.id');
})
.orderBy('updated_at', 'desc')
.then(function(threads) {
return threads;
});
}
I would like to add a limit(1) or a min to the join but not entirely sure how to do it.

You need to add a filter like the following to your left join criteria:
.andOn('forum_posts.created_at', '=', knex.raw("(select min(created_at) from forum_posts where forum_posts.thread_id = forum_threads.id)"))
This says to include the forum post record (as a left join match) if it has the minimum updated_at value for that id.
The full code. The below code isn't tested, although I did test the above snippet in a piece of my code.
function getByGroup(groupId) {
return knex('forum_threads')
.select('forum_threads.id', 'forum_threads.updated_at', 'forum_posts.content')
.where('forum_threads.group_id', '=', groupId)
.leftJoin('forum_posts', function() {
this.on('forum_posts.thread_id', '=', 'forum_threads.id')
/* The new line here */
.andOn('forum_posts.created_at', '=', knex.raw("(select min(created_at) from forum_posts where forum_posts.thread_id = forum_threads.id)"))
})
.orderBy('updated_at', 'desc')
.then(function(threads) {
return threads;
});
}
Cheers!
PS: You didn't ask, but something I find very helpful when debugging Knex is the .on() query reporting clauses:
// ...
.orderBy('updated_at', 'desc')
/* your code above */
.on('query', function(data) {
// outputs the SQL query you generated & runtime data bindings.
console.log(data);
})
.on('query-error', function(error, obj) {
// outputs the Knex failed query, data, etc.
console.log("Error:", error);
console.log("Object: ", obj);
})
/* your code below */
.then(function(threads) {
return threads;
});

Related

Why is double underscore needed here for accessing table field?

I came across a GitHub issue about sorting rows with TypeORM. I found this comment did work for my problem.
Quote:
async sortWithRelations(entityRepository) {
// Assuming repository classname is RepoX
let repoOptions = {
relations: ['relationA', 'relationB'],
where: qb => {
// Filter if required
qb.where('RepoX__relationA.fieldY = :val', {val: 'searchedValue'});
// Then sort
qb.orderBy({
'RepoX__relationA.fieldYYY': 'DESC',
'RepoX__relationB.fieldZZZ': 'DESC'
// '{repositoryClassName}__{relationName}.fieldName
// [+ __{childRelations} for every child relations]
});
}
};
However, I have no idea why RepositoryClassName__ accompanied with double underscore is needed to access the table column?
'RelationName.FieldName': 'DESC' will result in error instead.

Objection.js alias to relationMapping

I have a working relation mapping with Objection / Knex / Sql server that is causing a problem when the results are paged.
components: {
relation: BaseModel.HasManyRelation,
modelClass: Component,
join: {
from: 'vehicle.id',
to: 'component.vehicleID'
}
}
When I use withGraphFetched to get related components for every vehicle, the query fails if I include the 'vehicle.id' in the original select.
static getFieldList() {
return [
'id',
'mark',
'model'
].
}
static getPagedList(page, pagelength) {
return this.query()
.select(this.getFieldList())
.withGraphFetched('components')
.page(page, pagelength)
}
Now, when paging is done, Objection / Knex runs a second query after the primary one to fetch the total number of rows. Objection adds 'vehicle.id' from the relation mapping to the query, thus causing the query to fail because the column 'id' is now fetched twice for the subquery.
exec sp_executesql #statement=N'select count(*) as [count] from (select [id], [mark], [model], [id] from [vehicle]) as [temp]'
My question is, how can this be avoided? Can I use some alias in the relation mapping? I tried 'vehicle.id as vehicleFK' in the relation mapping but that caused the withGraphFetched to not run at all.
there might be two ways to try to solve your issue
get rid of component's id column
const componentsSelectList = ()=>{
// select columns you need from components without the id column
return [
'column1', // columns you need
'column2'
]
}
static getPagedList(page, pagelength) {
return this.query()
.select(this.getFieldList())
.withGraphFetched('components',{minimize: true})
.modifyGraph('components',builder => builder.select(componentsSelectList()))
.page(page, pagelength)
}
use ref function from objection to reference the id column from which table
const {ref} = require('objection')
...
static getFieldList() {
return [
ref('vehicle.id'),
'mark',
'model'
].
}
static getPagedList(page, pagelength) {
return this.query()
.select(this.getFieldList())
.withGraphFetched('components')
.page(page, pagelength)
}
...

node-postgres LEFT JOIN query

I am attempting to effect a join query in node-postgres (pg) with the following function but am getting a syntax error. The problem is the join query, everything else works fine. What is the correct way to format a join query in pg?
exports.bigBook = function(req, res) {
var bookNumber = req.params.id;
pool.connect(function(err, client, done) {
if (err) { return console.error('error fetching client from pool', err);}
client.query('SELECT * FROM book WHERE id = $1 LEFT JOIN author
ON (book.author = author.auth_id)'), [bookNumber], function (err, results) {
client.release();
res.send(results.rows);
};
})
}
The LEFT JOIN is part of the FROM clause, so you'll have to move the WHERE clause to the end of the query.

Update another table row in Azure Mobile Services Javascript

I have two tables in Azure Mobile Services (javascript backend): TableA and TableB. I want to update a specific row in TableB everytime a read query runs in TableA. Both tables have the same column "Date" used to identify which row to update.
I'm trying to accomplish that through the following code (added to the read script in TableA), which is not working to update TableB (it works to update TableA, if I change table name in var myTable to TableA)
function read(query, user, request) {
request.execute({ success: function(results)
{
request.respond();
//retrieve TableB reference
var myTable = tables.getTable('TableB');
myTable.where({
//retrieve TableB specific row by date
Date: results[0].Date
}).read({
success: function(results){
//reference row to be updated in TableB
var tableRef = results[0];
tableRef.Views = tableRef.Views + 1;
//update row in TableB
myTable.update(tableRef);
}});}});}
Please, how can I fix it?
Update, the following code works:
function read(query, user, request) {
request.execute( { success: function(results) {
request.respond();
var countTable = tables.getTable('TableB');
countTable.where({date: results[0].Date}).read({
success: updateCount
});
function updateCount(results) {
if (results.length > 0) {
// tracking record was found. update and continue normal execution.
var trackingRecord = results[0];
trackingRecord.views = trackingRecord.views + 1;
countTable.update(trackingRecord);
} else {
console.log('error updating count');
}
}
}
});};

Laravel - SELECT only certain columns within a `::with` controller function

The following code I have is working perfectly fine, however, it returns more data than what is necessary from each table:
public function getIndex()
{
$alerts = Criteria::with('bedrooms', 'properties')
->where('user_id', '=', Auth::id())
->get();
$this->layout->content = View::make('users.alert.index',
array('alerts' => $alerts));
}
What I'd like to do is, for example, select only the bedroom column out of the bedroom table. As it stands now, it returns all columns.
I have tried:
public function getIndex()
{
$alerts = Criteria::with('bedrooms' => function($q){
$q->select('bedroom');
}, 'properties')
->where('user_id', '=', Auth::id())
->get();
$this->layout->content = View::make('users.alert.index',
array('alerts' => $alerts));
}
But I am presented with the following error:
syntax error, unexpected '=>' (T_DOUBLE_ARROW)
Any help as to how I can achieve this will be hugely appreciated.
Update
public function getIndex()
{
$alerts = Criteria::with(
['coordinate' => function($w){
$w->select('name', 'id');
}],
['bedrooms' => function($q){
$q->select('bedroom', 'criteria_id');
}]
, 'properties')
->where('user_id', Auth::id())
->get();
$this->layout->content = View::make('users.alert.index',
array('alerts' => $alerts));
}
The correct select query works for which ever is queried first. If I swap the queries around, the bedroom function works correctly, but the rest aren't eager loaded nor does the select query work.
Just pass an array there:
// note [ and ]
$alerts = Criteria::with(['bedrooms' => function($q){
$q->select('bedroom', 'PK / FK');
}, 'properties'])
Also mind that you need to select the keys of that relation (primary key/foreign key of the relation).
The answer to your update question is that you need to eager load other values in the same array some thing like this.
public function getIndex()
{
$alerts = Criteria::with(
['coordinate' => function($w)
{
$w->select('name', 'id');
},
'bedrooms' => function($q)
{
$q->select('bedroom', 'criteria_id');
}
, 'properties'])
->where('user_id', Auth::id())
->get();
$this->layout->content = View::make('users.alert.index',
array('alerts' => $alerts));
}