Postgres query - return flattened JSON - sql

I have the following working query:
const getPromos = async (limit = 10, site: string, branch: string) => {
const query = `SELECT
json_build_object(
'id', p.id,
'description', p.description,
'discounted_price', p.discounted_price,
'items', jsonb_agg((i.id, i.price, i.title))
)
FROM promotions p
INNER JOIN promotion_items pi ON p.id = pi.promotion_id
INNER JOIN items i ON pi.item_code = i.item_code WHERE site_id = ${site} and store_id = ${branch}
GROUP BY p.id LIMIT ${limit}`;
return await db.query(query);
};
The issue is simple - each item (in this example - promotion) is returned with an object that wraps it - named json_build_object. I don't want any object to wrap my promotions - just like this:
[{id:1, .... items: [...items here...]}, {id:2, .... items: [...items here...]}]
Any idea?

You can get the desired result directly from the query when you aggregate the resultset using jsonb_agg (like items further down the query).
SELECT
jsonb_agg(jsonb_build_object(
'id', p.id,
'description', p.description,
'discounted_price', p.discounted_price,
'items', jsonb_agg((i.id, i.price, i.title))
))
--- the rest of your query

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!

Laravel sum nested relationship column

I tried sum nested related column but get empty result.
Code:
$data = CropType::with(['categories' => function($cq) {
return $cq->with(['varieties' => function($vq) {
return $vq->with(['products' => function($pq) {
return $pq->sum('total');
}]);
}]);
}])->get();
As you can see, I have 4 tables that are interconnected with foreign keys
Table: crop_types
id
title
Table: categories
id
title
crop_type_id
Table: varieties
id
title
category_id
Table: products
id
title
total
variety_id
But when I run my query it's run as like this instead summing total products:
"select * from crop_types"
How I can correctly sum total products in each crop type?
Try this query to get total products in each crop type:
SQL
SELECT crop_types.id,
crop_types.title AS title,
sum(products.total) AS total
FROM `products`
INNER JOIN `varieties`
ON `varieties`.`id` = `products`.`variety_id`
INNER JOIN `categories`
ON `categories`.`id` = `varieties`.`category_id`
INNER JOIN `crop_types`
ON `crop_types`.`id` = `categories`.`crop_type_id`
GROUP BY `crop_types`.`id`
Laravel Eloquent
$result = Product::query()
->selectRaw('crop_types.id, crop_types.title as title, sum(products.total) as total')
->join(
'varieties',
'varieties.id',
'=',
'products.variety_id'
)
->join(
'categories',
'categories.id',
'=',
'varieties.category_id'
)
->join(
'crop_types',
'crop_types.id',
'=',
'categories.crop_type_id'
)
->groupBy('crop_types.id')
->get();

How to write nested where condition with left join using npm Sequqlize

I am trying to implement left join query by clubbing
select product.* from products
left outer join cart as c on c.prodId = product.prodId
left outer join order as order on c.cid = p.pid
where product.secondaryId = 10 and c.cpid = 210;
How can I write, Sequelize code for this.
EDIT:
Here is the actual MYSQL query, what I am implementing to:
select products.* from products
left outer join Cart as cart on cart.CartId = products.CartId
left outer join Orders as order on order.OrderId = cart.PartId
where cart.CartId = 1 and order.CustId = 12;
Association at Sequelize end
Cart.hasMany(models.Products, {as: 'Products', foreignKey: 'CartId'})
Cart.belongsTo(models.Orders, { as: 'Orders', foreignKey: 'PartId'})
Products.belongsTo(models.Cart, {onDelete: "CASCADE", foreignKey: { name: 'CartId', allowNull: false})
Products -> PrimaryKey: ProduceId
Cart -> PrimaryKey: CartId
Orders -> PrimaryKey: OrderId
First of all left outer join cart as c on c.prodId = product.prodId will work as a usual join because of c.cpid = 210 condition. Either remove it or move it to on clause like this:
left outer join cart as c on c.prodId = product.prodId and c.cpid = 210
As of Sequelize query because you didn't show your model definitions and associations I'll try to guess and write like this (so you can get the whole idea):
const products = await db.Products.findAll({
where: {
secondaryId: 10
},
include: [{
model: db.Cart,
required: false, // this is LEFT OUTER JOIN
where: {
cpid: 210
},
include: [{
model: db.Order,
as: 'Orders',
required: false // this is LEFT OUTER JOIN
}]
}]
})
If you indicate your model definitions and associations then I can correct my answer as well (if necessary).

how to do subquery or backlink query in realm react-native?

The problem I'm trying to solve is this. If I have product, I'd like to know what other products I can purchase as a combination that's on-sale. If this were MySQL, I'd set up my tables and queries like so:
t_product: product_id, product_name
t_combination: combination_id, on_sale
t_product_combination: combination_id, product_id
// what other products should I buy along with my product_id == 1 to get a good deal?
SELECT product_id
FROM t_product
WHERE product_id IN (
SELECT product_id
FROM t_combination_product
WHERE combination_id IN (
SELECT combination_id
FROM t_combination
WHERE on_sale = 'yes'
AND combination_id IN (SELECT combination_id FROM t_combination_product WHERE product_id = 1)
)
)
I tried to do this in realm react-native as shown below:
I have two collections: Product and a Combination, set up as follows
class Product {};
Product.schema = {
name: 'Product',
primaryKey:'productId',
properties: {
productId:'int',
product:'string'
}
};
class Combination {};
Combination.schema = {
name: 'Combination',
properties: {
onSale: {type: 'string'},
products: {type: 'list',objectType:'Product'},
}
};
What I want to do is given that I have Product.productId=1, I want to find all other products that belong to Combinations that have Combination.onSale='yes' and Product.productId IS IN (Combination.products ).
This is the query I tried to do in realm with react-native:
let queryFilter = "ANY Combination.onSale = 'yes' AND Combination.products = 1";
let data = realm.objects('Product').objectsWhere(queryFilter);
But when I run this, I get the error
undefined is not a function (evaluating
'realm.objects('Product').objectsWhere(queryFilter)')
If I remove the objectsWhere(queryFilter), then it just returns me the list of all Products that hasn't been filtered.
So it seems I'm using objectsWhere incorrectly? How do I fix this to find a list of all products that are onsale if purchased with product 1?
If you had
class Product {};
Product.schema = {
name: 'Product',
primaryKey:'productId',
properties: {
productId:'int',
product:'string'
combinations: {type: 'linkingObjects', objectType: 'Combination', property: 'products'}
}
};
Backlink queries are not yet supported, so I think what you have to do is something like this:
let queryFilter = 'productId = 1';
let data = realm.objects('Product').filtered(queryFilter);
let combinations = data.combinations.filtered('onSale = true');
let linkedProducts = [];
for(let i = 0; i < combinations.length; i++) {
linkedProducts.push(combinations[i]);
}
// linked products should contain what you need

Doctrine ORM - Self join without any real relation

I'm trying to find related objects to one object by matching the objects tags. I've constructed a mysql query which will return the objects that match the most by counting the matching tags.
I'm new to doctrine (1.2) so I'm wondering if someone could help me to get on the right track modifying my schema and creating a DQL query? The big problem is that the two tagset doesn't relate to each others in my schema.yml I would guess.
Schema.yml:
Object:
columns:
name:
relations:
Tags: { foreignAlias: Objects, class: Tag, refClass: Tagset}
Tagset:
columns:
object_id: {type: integer, primary: true, notnull: true}
tag_id: { type: integer, primary: true, notnull: true }
relations:
Object: { foreignAlias: Tagsets }
Tag: { foreignAlias: Tagsets }
Tag:
columns:
name: { type: string(255), notnull: true }
Object: { foreignAlias: Tags, class: Object, refClass: Tagset}
Here is the mysql query which works using the schema above:
SELECT object.name, COUNT(*) AS tag_count
FROM tagset T1
INNER JOIN tagset T2
ON T1.tag_id = T2.tag_id AND T1.object_id != T2.object_id
INNER JOIN object
ON T2.object_id = object.id
WHERE T1.object_id = 2
GROUP BY T2.object_id
ORDER BY COUNT(*) DESC
You can use subqueries as well. Something like this:
$object_id = 2;
Doctrine::getTable('Tagset')->createQuery('t')
->select('t.tag_id, o.id, o.name, COUNT(t.tag_id) AS tag_count')
->innerJoin('t.Object o WITH o.id != ?', $object_id)
->where('t.tag_id IN (SELECT t.tag_id FROM Tagset t WHERE t.object_id = ?)', $object_id)
->groupBy('t.object_id')
Solution:
$q = new Doctrine_RawSql();
$this->related_objects = $q->
select('{o.name}')->
from('tagset t1 JOIN tagset t2 ON t1.tag_id = t2.tag_id AND t1.object_id != t2.object_id JOIN object o ON t2.object_id = o.id')->
addComponent('o','Object o')->
where('t1.object_id = ?', $this->object->id)->
groupBy('t2.object_id')->
orderBy('COUNT(*) DESC')->
execute();