Objection.js alias to relationMapping - objection.js

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

Related

Prisma: Finding items where two fields have the same value

I would like to find items in a Prisma db where the values for two columns are the same. The use case is to compare the 'created_at' and 'updated_at' fields to find items that have never been updated after their initial creation. In raw SQL I would do something like:
select updated_at,
cast(sign(sum(case when updated_at = created_at then
1
else
0
end)) as int) as never_modified
from tab
group by updated_at
Is it possible to achieve this in Prisma?
You would need to use Raw Queries to compare time values from the same table.
Here's an example of how you could achieve this, assuming a PostgreSQL database for the following query.
import { PrismaClient } from '#prisma/client'
const prisma = new PrismaClient()
async function initiateDatesComparisonRawQuery() {
const response =
await prisma.$queryRaw`SELECT * FROM "public"."Project" WHERE "created_at" = "updated_at";`;
console.log(response);
}
await initiateDatesComparisonRawQuery();
you can use the preview feature fieldReference of prisma.
schema.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["fieldReference"]
}
your code
prisma.project.findMany({
where: { created_at: prisma.project.fields.updated_at }
})

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.

Loopback reset the sort order to ASC

I am using loopback 3. This is the API call that I am doing to fetch the record in descending order.
const orderDetail = await Order.app.models.OrderDetail.findOne(
{
where: { orderid: orderId },
order: 'index DESC',
}
);
But loopback is discarding my sort order and using INDEX default. Below is the sql that I get setting DEBUG strings.
SELECT "id","index","timestamp", ............. FROM "public"."orderdetail" ORDER BY "index" ASC LIMIT 1 +4ms
There is no scope defined in model definition.
I remembered that I have had override the find method of model where it had set the defult order. I had done this earlier to prevent default sort (order by id) which was causing database performance issue.
const builtInFindMethod = OrderDetail.find;
OrderDetail.find = function findOrderDetails(filter, ...args) {
const processedFilter = { ...filter, order: 'index ASC' };
return builtInFindMethod.apply(this, [processedFilter, ...args]);
};
Just need to change the order object, try the below given code :
const orderDetail = await Order.app.models.OrderDetail.findOne(
{
where: { orderid: orderId },
order: ['index DESC'],
});

Sequelize migration add "IF NOT EXISTS" to addIndex and addColumn

Is there a way to force Sequelize.js to add IF NOT EXISTS to the Postgres SQL created by the queryInterface.addColumn and queryInterface.addIndex methods?
According to the Postgres Docs this is supported for Alter Table Add Column as well as Create Index
I have looked through the Sequelize.js docs without any luck, and I have tried to go through the code to figure out how the SQL is generated, but I have not had any luck yet.
A bit of background, or "Why"
I am trying to create a migration strategy for an existing postgres instance, and I have currently created a Sequelize migration set which migrates from "nothing" to the current schema. Now I would like to simply get this up and running on my production server where all of the data already exists such that the next time I create a migration, I can run it.
All of this works well for every queryInterface.createTable because the IF NOT EXISTS is automatically added.
I had a similar issue, except in my case I was only interested in addColumn IF NOT EXIST.
You can achieve this with a two step solution, using queryInterface.describeTable.
Given the table name the function will return the table definition which contains all the existing columns. If the column you need to add does not exist then call the queryInterface.addColumn function.
const tableName = 'your_table_name';
queryInterface.describeTable(tableName)
.then(tableDefinition => {
if (tableDefinition.yourColumnName) {
return Promise.resolve();
}
return queryInterface.addColumn(
tableName,
'your_column_name',
{ type: Sequelize.STRING } // or a different column
);
});
addColumn function comes from queryGenerator method called addColumnQuery, which accepts three parameters - table, key and dataType. With use of them it creates a query, which looks like that
let query = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedKey} ${definition};`;
So, as you can see, there is no option to add the IF NOT EXISTS clause to the query string. The same concerns the addIndex method unfortunately. However, you can use plain query in order to perform some atypical operations
queryInterface.sequelize.query(...);
The statement if (!tableDefinition.yourColumnName) won't be able to check if column exists.
Correct way is
return queryInterface.describeTable(tableName).then(tableDefinition => {
if (!tableDefinition[columnName]){
return queryInterface.addColumn(tableName, columnName, {
type: Sequelize.JSON
});
} else {
return Promise.resolve(true);
}
});
A small working example:
module.exports = {
/**
* #description Up.
* #param {QueryInterface} queryInterface
* #return Promise<void>
*/
up: async (queryInterface) => {
const tableDefinition = await queryInterface.describeTable('group');
const promises = [];
return queryInterface.sequelize.transaction((transaction) => {
if (!tableDefinition.column1) {
promises.push(queryInterface.addColumn(
'group',
'column1',
{
type: queryInterface.sequelize.Sequelize.STRING,
allowNull: true,
},
{transaction},
));
}
if (!tableDefinition.oauth2_token_expire_at) {
promises.push(queryInterface.addColumn(
'group',
'column2',
{
type: queryInterface.sequelize.Sequelize.DATE,
allowNull: true,
},
{transaction},
));
}
return Promise.all(promises);
});
},
/**
* #description Down.
* #param {QueryInterface} queryInterface
* #return Promise<void>
*/
down: (queryInterface) => {
...
},
};

eloquent refer to a column of a related a model

I have three tables:
categories
id, title
products
id, name
categories_products
id, category_id, product_id
I have also setup the according models and relationships (both have belongsToMany of the other)
Now I want to get all products belonging to a category
Category::where('title','Electronics')->first()->products()->limit(10)->get(['products.name']);
which works fine, but I also want to include the category title for each product as well:
Category::where('title','Electronics')->first()->products()->limit(10)->get(['products.name','category.title']);
However it returns: Column not found category.title
I thought that the relation would take care of it.
EDIT: Models -->
Category:
class Category extends Model
{
protected $fillable = array('title');
public function products()
{
return $this->belongsToMany('Product', 'categories_products', 'category_id', 'product_id');
}
}
class Product extends Model
{
protected $fillable = array('name');
public function categories()
{
return $this->belongsToMany('Category', 'categories_products', 'product_id', 'category_id');
}
}
The reason you're getting the error is because get() works just like select() and because you're running the category query and then running the product query after there is no categories table to reference for the select.
Look into Eager Loading. It will help with a lot of these kinds of issues. Your query can be written as:
Product::select('id', 'name')
->with(['categories' => function($query) {
return $query->select('id', 'title');
}])
->whereHas('categories', function($query) {
return $query->where('title', 'Electronics');
})
->limit(10)
->get();
Because we are lazy loading you NEED the id column on each model so Laravel knows where to attach the relationships after the queries are run.
The with() method above will eager load the categories relationship and the whereHas() method puts a relationship constraint on the current query.
UPDATE
Similar query from Category model:
$category = Category::where('title','Electronics')
->with(['products' => function($query) {
return $query->select('id', 'name')->limit(10);
}])
->first(['id', 'title']);
Then access the products with:
$category->products