SELECT DISTINCT ON expressions must match initial ORDER BY expressions in typeorm query - sql

I have a query that I want get rows with unique item_id and sort them according to timestamp.
But I get this error everytime from this code
const boxPreviewActivities = await this.activityLogEntryRepository
.createQueryBuilder('activityLogEntry')
.where("activityLogEntry.activity = 'BOX_PREVIEW'")
.andWhere('activityLogEntry.projectId = :projectId', { projectId })
.andWhere('activityLogEntry.userSub = :userSub', { userSub: user?.sub })
.andWhere((qb) => {
const subQuery = qb
.subQuery()
.select(
"DISTINCT activityLogEntry.originalData ::jsonb -> 'source' ->> 'item_id'"
)
.from(ActivityLogEntryEntity, 'activityLogEntry')
.where("activityLogEntry.activity = 'BOX_DELETE'")
.getQuery();
return `activityLogEntry.originalData ::jsonb -> 'source' ->> 'item_id' NOT IN (${subQuery})`;
})
.distinctOn([
"activityLogEntry.originalData ::jsonb -> 'source' ->> 'item_id' ",
])
.orderBy('activityLogEntry.timestamp', 'DESC')
.limit(limitNumber)
.getMany();
return boxPreviewActivities;
}
** ERROR [ExceptionsHandler] SELECT DISTINCT ON expressions must match initial ORDER BY expressions
QueryFailedError: SELECT DISTINCT ON expressions must match initial ORDER BY expressions**
if I add this two order functions at the and, I dont get error but my query not sorting according to timestamp, instead sort only with item_id. Actually I only want to sort according to timestamp. How can I refactor my query to make it working ?
.orderBy(
"activityLogEntry.originalData ::jsonb -> 'source' ->> 'item_id'",
'DESC'
)
.addOrderBy('activityLogEntry.timestamp', 'DESC')

Related

How to count number of user id in a list without duplicate in sequelize [duplicate]

I am trying to get a distinct count of a particular column using sequelize. My initial attempt is using the 'count' method of my model, however it doesn't look like this is possible.
The DISTINCT feature is needed because I am joining other tables and filtering the rows of the parent based on the related tables.
here's the query I would like:
SELECT COUNT(DISTINCT Product.id) as `count`
FROM `Product`
LEFT OUTER JOIN `Vendor` AS `vendor` ON `vendor`.`id` = `Product`.`vendorId`
WHERE (`vendor`.`isEnabled`=true );
using the following query against my Product model:
Product.count({
include: [{model: models.Vendor, as: 'vendor'}],
where: [{ 'vendor.isEnabled' : true }]
})
Generates the following query:
SELECT COUNT(*) as `count`
FROM `Product`
LEFT OUTER JOIN `Vendor` AS `vendor` ON `vendor`.`id` = `Product`.`vendorId`
WHERE (`vendor`.`isEnabled`=true );
UPDATE: New version
There are now separate distinct and col options. The docs for distinct state:
Apply COUNT(DISTINCT(col)) on primary key or on options.col.
You want something along the lines of:
MyModel.count({
include: ...,
where: ...,
distinct: true,
col: 'Product.id'
})
.then(function(count) {
// count is an integer
});
Original Post
(As mentioned in the comments, things have changed since my original post, so you probably want to ignore this part.)
After looking at Model.count method in lib/model.js, and tracing some code, I found that when using Model.count, you can just add any kind of aggregate function arguments supported by MYSQL to your options object. The following code will give you the amount of different values in MyModel's someColumn:
MyModel.count({distinct: 'someColumn', where: {...}})
.then(function(count) {
// count is an integer
});
That code effectively generates a query of this kind: SELECT COUNT(args) FROM MyModel WHERE ..., where args are all properties in the options object that are not reserved (such as DISTINCT, LIMIT and so on).
The Sequelize documentation on count links to a count method that doesn't let you specify which column to get the count of distinct values:
Model.prototype.count = function(options) {
options = Utils._.clone(options || {});
conformOptions(options, this);
Model.$injectScope(this.$scope, options);
var col = '*';
if (options.include) {
col = this.name + '.' + this.primaryKeyField;
expandIncludeAll.call(this, options);
validateIncludedElements.call(this, options);
}
Utils.mapOptionFieldNames(options, this);
options.plain = options.group ? false : true;
options.dataType = new DataTypes.INTEGER();
options.includeIgnoreAttributes = false;
options.limit = null;
options.offset = null;
options.order = null;
return this.aggregate(col, 'count', options);
};
Basically SELECT COUNT(DISTINCT(*)) or SELECT COUNT(DISTINCT(primaryKey)) if you've got a primary key defined.
To do the Sequelize equivalent of SELECT category, COUNT(DISTINCT(product)) as 'countOfProducts' GROUP BY category, you'd do:
model.findAll({
attributes: [
'category',
[Sequelize.literal('COUNT(DISTINCT(product))'), 'countOfProducts']
],
group: 'category'
})
Looks like this is now supported in Sequelize versions 1.7.0+.
the count and findAndCountAll methods of a model will give you 'real' or 'distinct' count of your parent model.
I was searching for SELECT COUNT(0) query for sequelize, below is the answer for that.
let existingUsers = await Users.count({
where: whereClouser,
attributes: [[sequelize.fn('COUNT', 0), 'count']]
});
This helped me to get distinct count from another table rows,
dataModel.findAll({
attributes: {
include: [[Sequelize.literal("COUNT(DISTINCT(history.data_id))"), "historyModelCount"]]
},
include: [{
model: historyModel, attributes: []
}],
group: ['data.id']
});
Ref 1, Ref 2.
With respect to your question in order to get the distinct counts of products based on the id of product
you just need to pass the key 'distinct' with value 'id' to your count object , Here is the example
To generate this sql query as you asked
SELECT COUNT(DISTINCT(`Product`.`id`)) as `count`
FROM `Product`
LEFT OUTER JOIN `Vendor` AS `vendor` ON `vendor`.`id` = `Product`.`vendorId`
WHERE (`vendor`.`isEnabled`=true );
Add 'distinct' key in your Sequelize query
Product.count({
include: [{model: models.Vendor, as: 'vendor'}],
where: [{ 'vendor.isEnabled' : true }],
distinct: 'id' // since count is applied on Product model and distinct is directly passed to its object so Product.id will be selected
});
This way of using 'distinct' key to filter out distinct counts or rows , I tested in Sequelize Version 6.
Hope this will help you or somebody else!

TypeORM distinctOn with orderBy

I'm trying to get the latest message for a user, either if he is the sender or the receiver.
But I can't use distinctOn with orderBy
const query = this.createQueryBuilder('message')
.orderBy('message.date', 'DESC')
.where('message.senderId = :id OR message.receiverId = :id',{id:user.id})
.distinctOn(['message.senderId','message.receiverId'])
const messages = await query.getRawMany();
SELECT DISTINCT ON ("message"."senderId", "message"."receiverId") "message"."id" AS "message_id", "message"."message" AS "message_message", "message"."date" AS "message_date", "message"."status" AS "message_status", "message"."senderId" AS "message_senderId", "message"."receiverId" AS "message_receiverId"
FROM "message" "message"
WHERE "message"."senderId" = $1
OR "message"."receiverId" = $2
ORDER BY "message"."date" DESC
SELECT DISTINCT ON expressions must match initial ORDER BY expressions
You can also try this way
const messages = this.createQueryBuilder('message')
.distinctOn('DISTINCT ON (message.senderId) as senderId, message.receiverId' as "receiverId")
.where('message.senderId = :id OR message.receiverId = :id',{id:user.id})
.orderBy('message.date', 'DESC').getRawMany()

Can Laravel automatically switch between column = ? and column IS NULL depending on value?

When building a complex SQL query for Laravel, using ? as placeholders for parameters is great. However when the value is null, the SQL syntax needs to be changed from = ? to IS NULL. Plus, since the number of parameters is one less, I need to pass a different array.
To get it to work, I have written it like this, but there must be a better way:
if ($cohortId === null) {
// sql should be: column IS NULL
$sqlCohortString = "IS NULL";
$params = [
Carbon::today()->subDays(90),
// no cohort id here
];
} else {
// sql should be: column = ?
$sqlCohortString = "= ?";
$params = [
Carbon::today()->subDays(90),
$cohortId
];
}
$query = "SELECT items.`name`,
snapshots.`value`,
snapshots.`taken_at`,
FROM snapshots
INNER JOIN (
SELECT MAX(id) AS id, item_id
FROM snapshots
WHERE `taken_at` > ?
AND snapshots.`cohort_id` $sqlCohortString
GROUP BY item_id
) latest
ON latest.`id` = snapshots.`id`
INNER JOIN items
ON items.`id` = snapshots.`item_id`
ORDER by media_items.`slug` ASC
";
$chartData = DB::select($query, $params);
My question is: does Laravel have a way to detect null values and replace ? more intelligently?
PS: The SQL is for a chart, so I need the single highest snapshot value for each item.
You can use ->when to create a conditional where clause:
$data = DB::table('table')
->when($cohortId === null, function ($query) {
return $query->whereNull('cohort_id');
}, function ($query) use ($cohortId) {
// the "use" keyword provides access to "outer" variables
return $query->where('cohort_id', '=', $cohortId);
})
->where('taken_at', '>', $someDate)
->toSql();

Constructor can not be instantiated Slick Scala

I was trying to convert a query from SQL into Scala code with Slick, but I have got a compiler error in filter clause: constructor cannot be instantiated to expected type.
My code in Slick:
val subquery = (for {
pit <- PassInTripTable.table
t <- TripTable.table if pit.tripNoFk === t.tripNo
} yield (pit, t))
.map{ case (pit, t) => ( pit, Case.If(t.townFrom <= t.townTo).Then(t.townFrom ++ t.townTo).Else(t.townFrom ++ t.townTo) )}
.groupBy(_._1.idPsgFk)
.filter{ case ((pit, count), group) => ( group.map(_._2).countDistinct === 1)}
.map(_._1)
val query = PassengerTable.table.filter(_.idPsg in subquery).map(_.name)
db.run(query.result)
The query in SQL itself:
select name from passenger
where id_psg in
(
select id_psg from trip t,pass_in_trip pit
where t.trip_no=pit.trip_no
group by id_psg
having count(distinct case when town_from<=town_to then town_from+town_to else town_to+town_from end)=1
)
I would be very grateful if someone helped me to find an error.
From looking at your code, it looks like the type you are matching on is not supposed to be "((pit, count), group)".
groupBy in Slick only returns a collection of Tuple2s.
http://slick.lightbend.com/doc/3.0.0/queries.html
So, the filter might look something like...
.filter{ case (pit, count) => ( count.map(_._2).countDistinct === 1)}
The problem is that Slick .groupBy requires a .map call with aggregating functions afterwards. You can find detailed information here.
So, try this:
.groupBy(_._1.idPsgFk)
.map{ case (key, group) => (key, group.map(_._2).countDistinct)}
.filter{ case (_, count) => count === 1}
.map(_._1)
P.S.
I've also found "bad smells" in your code. You get pairs as a result of for-comrehension, but it looks like standard join would be more appropriate here (and more efficient), something like:
PassInTripTable.table.join(TripTable.table).on(_.tripNoFk === _.tripNo)
.map{ case (pit, t) => ...}
And why would you use such condition:
Case.If(t.townFrom <= t.townTo).Then(t.townFrom ++ t.townTo).Else(t.townFrom ++ t.townTo)? Its branches are the same, so equals to t.townFrom ++ t.townTo.

How to query from JSON arrays from two different tables?

I have a table name doc_definition with a json column definition which has a format like:
[{
"id":"0",
"name:"Ques1"
},{
"id":"1",
"name:"Ques2"
},{
"id":"2",
"name:"Ques3"
}]
and another table doc which has another json column def_val with format as:
{
"0":{
"value":"Ans1"
},
"1":{
"value":"Ans2"
},
"2":{
"value":"Ans3"
}
}
I want to create a Postgres SQL query that gives the value of a corresponding question by matching the id field inside both json.
SO far I've come up with this:
SELECT json_array_elements(def.definition) ->> 'name' AS json_test
FROM document_definitions AS def
INNER JOIN documents AS doc
ON doc.document_definition_id = def.id
WHERE doc.id = 892 AND json_array_elements(def.definition) ->> 'name' = 'Ques2'
But this throws exception:
ERROR: argument of AND must not return a set
When i put the json_array_elements(def.definition) ->> 'name' = 'Ques2' in SELECT clause, it returns boolean value.
I dont know whats wrong then. Plz Help?
How about that query :
SELECT json_array_elements(def.definition) ->> 'name' AS json_test
FROM document_definitions AS def
INNER JOIN documents AS doc
ON doc.document_definition_id = def.id
WHERE doc.id = 892 AND (json_array_elements(def.definition) ::json->> 'name') = 'Ques2'
After many trial and errors, I've come up with this.
Plz let me know if I can improve this in any way.
SELECT definition_json.definition_value -> definition_json.def_value_id ->> 'value' AS definition_value
FROM (
SELECT json_array_elements(def.definition) ->> 'name' = 'Ques2' AS is_definition_present,
json_array_elements(def.definition) ->> 'id' AS def_value_id,
doc.definition_value, doc.id AS document_id, def.id AS definition_id
FROM document_definitions AS def
INNER JOIN documents AS doc
ON doc.document_definition_id = def.id
) AS definition_json
WHERE definition_json.is_definition_present = 't' AND definition_json.document_id = 892