Using raw bulk update query with Sequelize - node.js

I would like to use this type of request with sequelize to make large number of updates in one request (for performance reasons) :
UPDATE employee
SET address = new.address,
name = new.name
from (values :updateStack) AS new(address, name, employeeId)
WHERE employee.id = new.employeeId
Here is the value of updateStack :
[{
address: 'France',
name: 'Chris',
employeeId: 21
}, {
address: 'UK',
name: 'Steve',
employeeId: 42
}]
I'm not sure how sequelize can properly parse the updateStack array.
Any idea ?
This SQL query is working fine :
UPDATE employee
SET address = new.address,
name = new.name
from (values ('France', 'Chris', 21), ('UK', 'Steve', 42)) AS new(address, name, employeeId)
WHERE employee.id = new.employeeId
Thank you and have a good day.

I've discovered how to do it !
sequelize.query(
`
UPDATE employee
SET address = new.address,
name = new.name
from (values ?) AS new(address, name, employeeId)
WHERE employee.id = new.employeeId
`,
{
replacements: [['France', 'Chris', 21], ['UK', 'Steve', 42]],
type: models.models.sequelize.QueryTypes.INSERT
}
)

Related

Using EXISTS with bound parameters in sub-query with Sequelize

Suppose I have a table of something like cars, where inside is a JSONB object specifying possible customizations:
+----+-----------------------------------------+
| id | customizations JSONB |
+----+-----------------------------------------+
| 1 | {"color": "blue", "lights": "led", ...} |
+----+-----------------------------------------+
| 2 | {"color": "red"} |
+----+-----------------------------------------+
| 3 | {} |
+----+-----------------------------------------+
If I want to query for a certain customization based on case-insensitive value or partial value (i.e., ILIKE), I can do something like:
SELECT * FROM "Cars" WHERE EXISTS (
SELECT 1 FROM JSONB_EACH_TEXT("customizations") WHERE "value" ~* 'BLU'
);
This pattern works fine in Postgres, but now I am trying to translate it over to Sequelize as best as I can. The important thing here is that the search term ('BLU' from the example) is passed as a parameter.
const cars = await cars.findAll({
where: // ???? Maybe something like db.fn('EXISTS', ...), but how do I bind the parameter?
});
How can I use an EXISTS query here, but bind a parameter? I know I could use db.literal(), but if I did that, I'd have to escape the search term string myself before interpolating it into the query. (Is there at least a proper method for doing this data escaping in Sequelize?)
Note that the JSONB object in customizations can have many keys, a single key, or even no keys.
Bounty Note: Answers using modern versions of Postgres are fine, but I would also like an answer for PostgreSQL v10.12, as that's all that is available with AWS Aurora Serverless. I'll happily assign a separate bounty to both answers!
If on postgresql 12+, it would be possible to use the json path expression to extract all values & cast to text in order to regex match
The raw query would be
SELECT *
FROM cars
WHERE jsonb_path_query_array(customizations, '$.*')::TEXT ~* 'BLU'
in sequelize:
where: Sequelize.where(
Sequelize.literal("jsonb_path_query_array(customizations, '$.*')::TEXT"),
Op.iRegexp,
'BLU'
)
On older versions, i would probably use a raw query with bound parameters that maps back to the Car object.
How can I use an EXISTS query here, but bind a parameter? I know I could use db.literal(), but if I did that, I'd have to escape the search term string myself before interpolating it into the query. (Is there at least a proper method for doing this data escaping in Sequelize?)
you can bind parameters to raw sql queries using either $1 or $name for positional (array) and named (object) arguments.
// cars.js
const Sequelize = require('sequelize');
const path = 'postgresql://hal:hal#localhost:5432/hal'
const sequelize = new Sequelize(path);
let Car = sequelize.define('cars', {
id: { type: Sequelize.INTEGER, primaryKey: true },
customizations: Sequelize.JSONB
});
let cars = [{ id: 1, customizations: {color: "blue", lights: "led"} },
{ id: 2, customizations: {color: "red"} },
{ id: 3, customizations: {} }]
const search_pat = 'BLU';
const stmt = `
select distinct cars.*
from cars, jsonb_each_text(customizations) kv(kk, vv)
where vv ~* $search_pattern
`;
sequelize
.sync()
.then(() => {
Car.bulkCreate(cars)
})
.then(() => {
sequelize.query(stmt, {
bind: {search_pattern: search_pat},
type: sequelize.QueryTypes.SELECT,
model: Car,
mapToModel: true
}).then(cars => {console.log(cars);})
});
executing this script (node cars.js) produces the following output:
Executing (default): CREATE TABLE IF NOT EXISTS "cars" ("id" INTEGER , "customizations" JSONB, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'cars' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Executing (default): INSERT INTO "cars" ("id","customizations","createdAt","updatedAt") VALUES (1,'{"color":"blue","lights":"led"}','2021-04-14 04:42:50.446 +00:00','2021-04-14 04:42:50.446 +00:00'),(2,'{"color":"red"}','2021-04-14 04:42:50.446 +00:00','2021-04-14 04:42:50.446 +00:00'),(3,'{}','2021-04-14 04:42:50.446 +00:00','2021-04-14 04:42:50.446 +00:00') RETURNING "id","customizations","createdAt","updatedAt";
Executing (default): select distinct cars.*
from cars, jsonb_each_text(customizations) kv(kk, vv)
where vv ~* $1
[
cars {
dataValues: {
id: 1,
customizations: [Object],
createdAt: 2021-04-14T04:42:50.446Z,
updatedAt: 2021-04-14T04:42:50.446Z
},
_previousDataValues: {
id: 1,
customizations: [Object],
createdAt: 2021-04-14T04:42:50.446Z,
updatedAt: 2021-04-14T04:42:50.446Z
},
_changed: Set(0) {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: undefined
},
isNewRecord: false
}
]
note that i've used an alternative (and likely less efficient select statement in the example above)
You can use your original query, which IMO is the most straight-forward query for the target postgresql version (10.x), i.e.
const stmt = `
SELECT *
FROM cars
WHERE EXISTS (
SELECT 1
FROM JSONB_EACH_TEXT(customizations) WHERE "value" ~* $search_pattern
)
`;
If you modify your condition like this:
SELECT * FROM "Cars" WHERE (
SELECT "value" FROM JSONB_EACH_TEXT("customizations")) ~* 'BLU'
then you can use Sequelize.where in a conjunction with Sequelize.literal:
where: Sequelize.where(
Sequelize.literal('(SELECT "value" FROM JSONB_EACH_TEXT("customizations"))'),
Op.iRegexp,
'BLU'
)
Upd.
This solution will work only if a subquery returns 1 record.

How to insert range type data in typeorm

postgresql / typeorm / nestjs
I am reporting an error when inserting data using typeorm,
Mainly because the rangetype is not converted to a string
#Entity()
export class Test {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column('numrange', { array: true })
price: number[];
}
this.entityRepository.save({
price: [100,200]
})
logger: INSERT INTO "test" ( "id", "price", ) VALUES ( DEFAULT, $1 ) RETURNING "id" -- PARAMETERS: [[100,200]]
postgresql: ERROR: syntax error at or near "["
in pg driver it is
pool.query(
`
INSERT INTO test
(title,numbers_range)
VALUES ($1,$2)
`,
["test", JSON.stringify([100, 200])],
);
PS:
[100, 200] include 200
[100, 200) not include 200
OR
pool.query(
`
INSERT INTO test
(title,numbers_range)
VALUES ($1,$2)
`,
["test", "[100,200)"],
);
in typeorm it can be
this.entityRepository.save({
price: JSON.stringify([100,200])
})
OR
this.entityRepository.save({
price: "[100,200)"
})
from doc
https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INDEXING
CREATE TABLE reservation (room int, during tsrange);
INSERT INTO reservation VALUES
(1108, '[2010-01-01 14:30, 2010-01-01 15:30)');

Node js JSON string insert to DB but cant update same JSON string

I encountered a strange mistake. I add a data that I send with Vue axios post to the database, a column of this data that is converted to JSON object with JSON.stringfy. When I try to update this line later, can I share the idea that I am having the following error? I shared SQL string below.
{
id: null,
table_id: 1425,
user_id: 15,
order_time: 1586975000253,
order_status: 1,
order: [
{
product: 8,
amount: 1,
portion: [Object],
order_time: 1586974979254,
status: 0,
desc: ''
},
{
product: 4,
amount: 1,
portion: [Object],
order_time: 1586974979707,
status: 0,
desc: ''
},
{
product: 8,
amount: 1,
portion: [Object],
order_time: 1586974980271,
status: 0,
desc: ''
}
],
corp: 'sssxx'
}
ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'order = '[{\"product\":8,\"amount\":1,\"portion\":{\"id\":1,\"title\":\"Tam Pors' at line 1
UPDATE orders SET order = '[{\\"product\\":8,\\"amount\\":1,\\"portion\\":{\\"id\\":1,\\"title\\":\\"Tam Porsiyon\\",\\"price\\":\\"25\\"},\\"order_time\\":1586974979254,\\"status\\":0,\\"desc\\":\\"\\"},{\\"product\\":4,\\"amount\\":1,\\"portion\\":{\\"id\\":1,\\"title\\":\\"Tam Porsiyon\\",\\"price\\":\\"25\\"},\\"order_time\\":1586974979707,\\"status\\":0,\\"desc\\":\\"\\"},{\\"product\\":8,\\"amount\\":1,\\"portion\\":{\\"id\\":1,\\"title\\":\\"Tam Porsiyon\\",\\"price\\":\\"25\\"},\\"order_time\\":1586974980271,\\"status\\":0,\\"desc\\":\\"\\"}]', order_status = 1 WHERE id = NULL AND table_id = 1425 and corp = 'sssxx'
The problem with your code has nothing to do with using JSON.
The word order is a reserved keyword. See https://mariadb.com/kb/en/reserved-words/ You can't use it as a column name unless you delimit it with back-ticks:
UPDATE orders SET `order` = ...whatever...
The clue in the error message is that it complained about the word order, not about anything in your JSON.
...for the right syntax to use near 'order = ...
Syntax errors show you exactly the point in your SQL syntax where the parser got confused.

Laravel activity log does not update properties column

I am learning to log an activity in Laravel(7.1) using spatie/laravel-activitylog package. But when I update an user, it does not update properties column in activity_log table, I set $logAttributes attribute on model (protected static $logAttributes = ['name', 'email'];)
When I update an user like
>>> $user = User::find(1);
=> App\User {#3104
id: 1,
name: "John",
email: "prath#example.com",
email_verified_at: "2020-03-12 13:35:19",
created_at: "2020-03-12 13:35:19",
updated_at: "2020-03-12 13:41:34",
}
>>> $user->update(['name' => 'James']);
=> true
and it logs that activity but returns with an empty properties column.
{
id: 8,
log_name: "default",
description: "updated",
subject_id: 1,
subject_type: "App\User",
causer_id: null,
causer_type: null,
properties: [ ],
created_at: "2020-03-12T14:58:10.000000Z",
updated_at: "2020-03-12T14:58:10.000000Z"
}
In your model add these field name which you want to update:
protected static $logAttributes = ['xxx', 'yyy', 'zzz'];
protected static $logOnlyDirty = true;
php artisan config:cache
do this for each log activity update, since all log activities are cached
php artisan config:clear

How to aggregate fields from embedded documents in Mongoose

Coverage Model.
var CoverageSchema = new Schema({
module : String,
source: String,
namespaces: [{
name: String,
types: [{
name: String,
functions: [{
name: String,
coveredBlocks: Number,
notCoveredBlocks: Number
}]
}]
}]
});
I need coveredBlocks aggregations on every level:
*Module: {moduleBlocksCovered}, // SUM(blocksCovered) GROUP BY module, source
**Namespaces: [{nsBlocksCovered}] // SUM(blocksCovered) GROUP BY module, source, ns
****Types: [{typeBlocksCovered}] // SUM(blocksCovered) BY module, source, ns, type
How do I get this result with Coverage.aggregate in Mongoose ?
{
module: 'module1',
source: 'source1',
coveredBlocks: 7, // SUM of all functions in module
namespaces:[
name: 'ns1',
nsBlocksCovered: 7, // SUM of all functions in namespace
types:[
{
name: 'type1',
typeBlocksCovered: 7, // SUM(3, 4) of all function in type
functions[
{name: 'func1', blocksCovered: 3},
{name:'func2', blocksCovered: 4}]
}
]
]
}
My ideas is to deconstruct everything using $unwind then reconstruct the document back again using group and projection.
aggregate flow:
//deconstruct functions
unwind(namesapces)
unwind(namespaces.types)
unwind(namespace.types.functions)
//cal typeBlocksCovered
group module&source ,ns,type to sum functions blocksCovered->typeBlocksCovered + push functions back to types
project to transform fields to be easier for next group
// cal nsBlocksCovered
group module&source ,ns to sum typeBlocksCovered -> nsBlocksCovered) + push types back to ns
project to transform fields to be easier for next group
// cal coveredBlocks
group module&source to sum nsBlocksCovered -> coveredBlocks
project to transform fields to match your mongoose docs
My sample query with mongo shell syntax and its seem working , guess is you are using collection name "Coverage"
db.Coverage.aggregate([
{"$unwind":("$namespaces")}
,{"$unwind":("$namespaces.types")}
,{"$unwind":("$namespaces.types.functions")}
,{"$group": {
_id: {module:"$module", source:"$source", nsName: "$namespaces.name", typeName : "$namespaces.types.name"}
, typeBlocksCovered : { $sum : "$namespaces.types.functions.blocksCovered"}
, functions:{ "$push": "$namespaces.types.functions"}}}
,{"$project" :{module:"$_id.module", source:"$_id.source"
,namespaces:{
name:"$_id.nsName"
,types : { name: "$_id.typeName",typeBlocksCovered : "$typeBlocksCovered" ,functions: "$functions"}
}
,_id:0}}
,{"$group": {
_id: {module:"$module", source:"$source", nsName: "$namespaces.name"}
, nsBlocksCovered : { $sum : "$namespaces.types.typeBlocksCovered"}
, types:{ "$push": "$namespaces.types"}}}
,{"$project" :{module:"$_id.module", source:"$_id.source"
,namespaces:{
name:"$_id.nsName"
,nsBlocksCovered:"$nsBlocksCovered"
,types : "$types"
}
,_id:0}}
,{"$group": {
_id: {module:"$module", source:"$source"}
, coveredBlocks : { $sum : "$namespaces.nsBlocksCovered"}
, namespaces:{ "$push": "$namespaces"}}}
,{"$project" :{module:"$_id.module", source:"$_id.source", coveredBlocks : "$coveredBlocks", namespaces: "$namespaces",_id:0}}
])

Resources