how to using a db function in where sentence via sequelize - node.js

I want to find an average value of this week and group by dayofweek, so I writen this sql, the sql is work well, but how can I achieve the same purpose via sequelize.
select dayofweek(datetime), avg(value) from Temperatures where week(datetime) = week(now()) group by dayofweek(datetime);
And here is my Temperatures table:
| field | type |
|----------|-------------|
| uuid | char(36) |
| value | float |
| datetime | datetime |
I've read the document of sequelize, but it seems that there is no clear explanation of how to using a function after where sentence, the condition is a function but not a field.
I have tried under way, but it's not work
where: {
[Sequelize.Op.eq]: [
sequelize.fn('week', Temperature.sequelize.fn('now')),
sequelize.fn('week', Temperature.sequelize.col('datetime')),
]
},
Thank your for your help!

You can use sequelize.fn for that , something like this :
[sequelize.fn('dayofweek', sequelize.col('datetime')), 'datetime']
For more details DO READ

Finally, I found the following method works well
return Temperature.findAll({
attributes: [
[Temperature.sequelize.fn('dayofweek', Temperature.sequelize.col('datetime')), 'dayofweek'],
[Temperature.sequelize.fn('AVG', Temperature.sequelize.col('value')), 'value'],
],
where: sequelize.where(sequelize.fn('week', Temperature.sequelize.fn('now')), sequelize.fn('week', Temperature.sequelize.col('datetime'))),
group: sequelize.fn('dayofweek', Temperature.sequelize.col('datetime')),
});
and if I need more conditional filters, just add an [Op.and] operator
return Temperature.findAll({
attributes: [
[Temperature.sequelize.fn('dayofweek', Temperature.sequelize.col('datetime')), 'dayofweek'],
[Temperature.sequelize.fn('AVG', Temperature.sequelize.col('value')), 'value'],
],
where: {
[Sequelize.Op.and]: [
sequelize.where(sequelize.fn('week', Temperature.sequelize.fn('now')), sequelize.fn('week', Temperature.sequelize.col('datetime'))),
]
},
group: sequelize.fn('dayofweek', Temperature.sequelize.col('datetime')),
});
here is the sql generated
SELECT dayofweek(`datetime`) AS `dayofweek`, AVG(`value`) AS `value` FROM `Temperatures` AS `Temperature` WHERE (week(now())=week(`datetime`)) GROUP BY dayofweek(`datetime`);

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.

nodejs sequelize include relation sorting

ha ve tow models one is Boat Master and another is boat price I want all boat data with its price and order by descending boat price the query is not working as expected its sorting data inside my relation and also the result is shown is sorted but its not picking up the boat who has price highest among all boats
db.BoatMaster.findAndCountAll({
distinct: true,
limit:request.params.pagesize,
offset:offsetpage,
attributes:[
'id',
'name',
'saleprice','length','cabins','year','berths','createdAt','updatedAt'
],
include:[
"boat_price",
],
order:[
['boat_price','price','DESC']
],
})
and here is the query output
SELECT
"BoatMaster".*,
"boat_price"."id" AS "boat_price.id",
"boat_price"."price" AS "boat_price.price",
"boat_price"."boatId" AS "boat_price.boatId",
"boat_price"."currency" AS "boat_price.currency",
"boat_price"."datefrom" AS "boat_price.datefrom",
"boat_price"."dateto" AS "boat_price.dateto"
FROM
(
SELECT
"BoatMaster"."id",
"BoatMaster"."name",
"BoatMaster"."saleprice",
"BoatMaster"."length",
"BoatMaster"."cabins",
"BoatMaster"."year",
"BoatMaster"."berths",
"BoatMaster"."createdAt",
"BoatMaster"."updatedAt"
FROM
"BoatMasters" AS "BoatMaster"
WHERE
(
SELECT
"boatId"
FROM
"boatprices" AS "boat_price"
WHERE
(
"boat_price"."boatId" = "BoatMaster"."id"
)
LIMIT
1
) IS NOT NULL
LIMIT
10 OFFSET 0
) AS "BoatMaster"
INNER JOIN "boatprices" AS "boat_price" ON "BoatMaster"."id" = "boat_price"."boatId"
ORDER BY
"boat_price"."price" DESC;
can anyone help me out what I am doing wrong here because data is not sorting according to the price?
thank in advanced
try this (if correct):
include:[
boat_price,
],
order:[
[boat_price,'price','DESC']
],
or
include:[
boat_price,
],
order:[
['boat_price.price','DESC']
],

Sequelize with SQL Server returns error aggregate function or GROUP BY clause even I didn't used GROUP BY

I'm built a app's backend with ExpressJs with Sequelize and SQL Server.
I've two tables, called User and Order. A user hasMany order.
I wanna list all user include their order(as count).
Here is my code:
User.findAll({
attributes: ['id'],
include: [
{
model: ProductBuy,
attributes: [
[Sequelize.fn('COUNT', Sequelize.col('Order.member_id')), 'total_active']
],
where: {status: ['Active', 'Expired']}
}
],
})
but it give me an error
Column 'User.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I don't understand this, because I haven't used a group by yet.
But if I add a grouping like
group:['Order.member_id', 'User.id', 'Order.id']
It successfully gets the data, but the order isn't counted, it showed each order one by one in each user like this:
{
"id": 4,
"orders": [
{
"total_active": 1
},
{
"total_active": 1
}
]
},
What I want is to say User A has 4 total_active, user B has 6 total_active like that
How do I achieve this?
Table Structure
User
| id | name |
| 1 | Andrew |
| 2 | Bruce |
Order
| id | user_id | status |
| 1 | 1 | Active |
| 2 | 1 | Expired |
| 3 | 2 | Active |
Expected result
{
"data": [
{
"id": 1,
"Orders": [
{
"total_active": 2
}
]
},
{
"id": 2,
"Orders": [
{
"total_active": 1
}
]
},
]
}
Screenshot result
I think you can takeout the attribute field from ProductBuy and use it like this :
User.findAll({
attributes: [
'id'
[Sequelize.fn('COUNT', Sequelize.col('Order.member_id')), 'total_active'] // <--- HERE
],
include: [
{
model: ProductBuy,
where: {status: ['Active', 'Expired']}
}
],
group:['User.id', 'Order.id']
})
NOTE :
If its counting duplicate records you can also use DISTINCT like
this
[Sequelize.fn('COUNT', Sequelize.fn('DISTINCT',Sequelize.col('Order.member_id'))), 'total_active']
if you use COUNT with another fields you have to use GROUP BY
SELECT user_id, COUNT(*)
FROM [Order]
GROUP BY user_id

Optional query across relation

I have two tables to handle coupon codes in my application.
The first (coupon) stores coupon codes, along with the discount they provide:
| id | code | public | discount_percent |
|----|------------|--------|------------------|
| 1 | EXAMPLE | true | 10 |
| 2 | PRIV_12981 | false | 30 |
Public indicates whether the coupon code can be claimed by anyone.
The second (user_coupon) table stores users entitled to redeem private coupons, and whether their instance of the coupon has been redeemed:
| id | coupon_id | user_id | redeemed |
|----|-----------|---------|----------|
| 1 | 2 | 1 | false |
I need to make the following query (pseudo code):
SELECT * from coupons
WHERE (
code = 'PRIV_12981'
AND (
public = true
OR (
user_coupon.user_id = 1
AND user_coupon.redeemed = false
)
)
)
This query should check whether a coupon exists for the provided code that is either public, or has a corresponding entry in the user_coupon table for the provided user_id and has not already been redeemed.
I'm using Sequelize, and have written the following query:
Coupon.findOne({
where: {
[Sequelize.Op.and]: [
{
code: 'TEST'
},
{
[Sequelize.Op.or]: [
{
public: true
},
{
[Sequelize.Op.and]: [
Sequelize.where(
Sequelize.col('user_coupons.redeemed'), false
),
Sequelize.where(
Sequelize.col('user_coupons.user_id'), 1
)
]
}
]
}
]
},
include: [{
model: UserCoupon,
as: 'user_coupons',
}]
}).then(coupon => {
console.log(coupon)
})
This generates the following SQL:
SELECT "Coupon".*,
"user_coupons"."id" AS "user_coupons.id",
"user_coupons"."coupon_id" AS "user_coupons.coupon_id",
"user_coupons"."user_id" AS "user_coupons.user_id",
"user_coupons"."redeemed" AS "user_coupons.redeemed",
"user_coupons"."created_at" AS "user_coupons.created_at",
"user_coupons"."updated_at" AS "user_coupons.updated_at"
FROM
(SELECT "Coupon"."id",
"Coupon"."code",
"Coupon"."public",
"Coupon"."discount_percent",
"Coupon"."max_discount",
"Coupon"."valid_from",
"Coupon"."invalid_after",
"Coupon"."created_at",
"Coupon"."updated_at"
FROM "public"."coupon" AS "Coupon"
WHERE ("Coupon"."code" = 'TEST'
AND ("Coupon"."public" = TRUE
OR ("user_coupons"."redeemed" = FALSE
AND "user_coupons"."user_id" = 1)))
LIMIT 1) AS "Coupon"
LEFT OUTER JOIN "public"."user_coupon" AS "user_coupons" ON "Coupon"."id" = "user_coupons"."coupon_id";
However, it throws the following error:
missing FROM-clause entry for table "user_coupons"
What have I done wrong with the Sequelize query?
I discovered the error wasn't actually in my query structure, it was due to my use of findOne, which was adding LIMIT 1 to the query, which resulted in the error. Querying with Coupon.findAll fixed this,

Sequelize produces invalid query "model.id AS model.id"

Here is the sequelize call I'm making to count favorites along with my model:
Model.findAll({
group: [ 'model.id', 'favorites.id' ],
attributes: [
'*',
[ Sequelize.fn('count', Sequelize.col('favorites.id')), 'favorites_count' ]
],
include: [
{ attributes: [], model: Favorite },
]
});
Here's the query it produces:
SELECT
model.id,
model.*,
count(favorites.id) AS favorites_count,
favorites.id AS favorites.id # Invalid!
FROM models AS model ...
Here's the error
ERROR: syntax error at or near "."
LINE 1: SELECT model.*, favorites.id AS favorites.id, ...
^
Because favorites.id is not a valid AS alias. Why does Sequelize produce this
invalid alias and how do I prevent it?
You cannot prevent it - sequelize needs to alias the column. The query becomes invalid because you have set quoteIdentifiers to false. The query sequelize wants is "favorites"."id" AS "favorites.id"
Why is sequelize doing such a "stupid" thing you might say. Well lets take an example:
SELECT "model"."id", "favorites"."id" ...
This produces the following result set:
[{ id: 42 }]
Because the two columns have the same name, they override each other so only one of the columns is returned. So we need to alias the favorites column so we get
[{ id: 42, 'favorites.id': 87 }]

Resources