How to implement subqueries in cube.js - sql

I'm struggling to see how I would represent the following type of postgres SQL query in a cube.js schema:
SELECT
CASE
WHEN COUNT(tpp.net_total_amount) > 0 THEN
SUM(tpp.net_total_amount) / COUNT(tpp.net_total_amount)
ELSE
NULL
END AS average_spend_per_customer
FROM
(
SELECT
SUM(ts.total_amount) AS net_total_amount
FROM
postgres.transactions AS ts
WHERE
ts.transaction_date >= '2020-11-01' AND
ts.transaction_date < '2020-12-01'
GROUP BY
ts.customer_id,
ts.event_id
) AS tpp
;
I had the feeling that pre-aggregations might be what I'm after, but that doesn't seem to be the case after looking into them. I can get a list of total amount spent per customer per event with the following schema:
cube(`TransactionTotalAmountByCustomerAndEvent`, {
sql: `SELECT * FROM postgres.transactions`,
joins: {
},
measures: {
sum: {
sql: `SUM(total_amount)`,
type: `number`
}
},
dimensions: {
eventId: {
sql: `event_id`,
type: `string`
},
customerId: {
sql: `customer_id`,
type: `string`
},
transactionDate: {
sql: `transaction_date`,
type: `time`
}
},
preAggregations: {
customerAndEvent: {
type: `rollup`,
measureReferences: [sum],
dimensionReferences: [customerId, eventId]
}
}
});
But that is really just giving me the output of the inner SELECT statement grouped by customer and event. How do I query the cube to get the average customer spend per event figure I'm after?

You might find it easier to model the dataset as two different cubes, Customers and Transactions. You'll then need to set up a join between the cubes and then create a special dimension with the subQuery property set to true. I've included an example below to help you understand:
cube('Transactions', {
sql: `SELECT * FROM postgres.transactions`,
measures: {
spend: {
sql: `total_amount`,
type: `number`,
},
},
dimensions: {
eventId: {
sql: `event_id`,
type: `string`
},
customerId: {
sql: `customer_id`,
type: `string`
},
transactionDate: {
sql: `transaction_date`,
type: `time`
},
},
})
cube('Customers', {
sql: `SELECT customer_id FROM postgres.transactions`,
joins: {
Transactions: {
relationship: `hasMany`,
sql: `${Customers}.id = ${Transactions}.customerId`
}
},
measures: {
averageSpend: {
sql: `${spendAmount}`,
type: `avg`,
},
},
dimensions: {
id: {
sql: `customer_id`,
type: `string`
},
spendAmount: {
sql: `${Transactions.spend}`,
type: `number`,
subQuery: true
},
}
})
You can find more information on the Subquery page on the documentation

Related

Apply where clause on associated table leads to sequelize bad field error

I have 3 tables sale,company and saleItem with the following relations:
Sale.belongsTo(Company);
Company.hasMany(Sale);
Sale.hasMany(SaleItem, { as: "items" });
SaleItem.belongsTo(Sale);
I want to apply a filter on the company's name I saw that to do that we have to use $ at the start and end but it isnt working. Any ideas where I am going wrong?
When I try to execute the below code I get the error:
SqlError: (conn=201, no: 1054, SQLState: 42S22) Unknown column 'company.name' in 'where clause'
sql: SELECT `sale`.*, `company`.`id` AS `company.id`, `company`.`name` AS `company.name`, `items`.`id` AS `items.id`, `items`.`quantity` AS `items.quantity`, `items`.`price` AS `items.price`, `items`.`description` AS `items.description`, `items`.`margin` AS `items.margin`, `items`.`gst` AS `items.gst`, `items`.`createdAt` AS `items.createdAt`, `items`.`updatedAt` AS `items.updatedAt`, `items`.`saleId` AS `items.saleId`, `items`.`itemId` AS `items.itemId` FROM (SELECT `sale`.`id`, `sale`.`date`, `sale`.`type`, `sale`.`description`, `sale`.`poNumber`, `sale`.`poDate`, `sale`.`paymentType`, `sale`.`gst`, `sale`.`discount`, `sale`.`freight`, `sale`.`status`, `sale`.`saleStatus`, `sale`.`referenceNumber`, `sale`.`ftn`, `sale`.`quotationNumber`, `sale`.`showGST`, `sale`.`invoiceDate`, `sale`.`hasWithholdingTax`, `sale`.`serialNumber`, `sale`.`currency`, `sale`.`createdAt`, `sale`.`updatedAt`, `sale`.`companyId`, `sale`.`customerId` FROM `sale` AS `sale` WHERE `company`.`name` LIKE '%%' AND `sale`.`customerId` = 1 AND `sale`.`status` = 'ACTIVE' ORDER BY `id` DESC LIMIT 0, 15) AS `sale` LEFT OUTER JOIN `company` AS `company` ON `sale`.`companyId` = `company`.`id` LEFT OUTER JOIN `saleItem` AS `items` ON `sale`.`id` = `items`.`saleId` ORDER BY `id` DESC;
It works if I dont include the SaleItem table in query
Here is the code
await Sale.findAndCountAll({
include: [
{
model: Company,
attributes: ["name"],
as: "company",
},
{ model: SaleItem, as: "items" },
],
distinct: true,
where: {
"$company.name$": { [Op.like]: `%${search}%` },
customerId:1,
status: "ACTIVE",
},
})
If you see the generated SQL, company.name WHERE clause is incorrectly added to a subquery, so you can either turn off the subquery or you can add your where option within the include.
Option 1:
await Sale.findAndCountAll({
...,
subQuery: false
})
Option 2:
await Sale.findAndCountAll({
include: [
{
model: Company,
attributes: ["name"],
as: "company",
where: {
name: { [Op.like]: `%${search}%` }
}
},
{ model: SaleItem, as: "items" },
],
distinct: true,
where: {
customerId:1,
status: "ACTIVE",
},
})

I need to transform an SQL arguments into Sequelize

I need to transform an SQL arguments into Sequelize
SELECT `WeekHours`.`id_weekhours` AS `idWeekhours`, `WeekHours`.`hour`, `WeekHours`.`week_day` AS `weekDay`
FROM `week_hours` AS `WeekHours`
LEFT OUTER JOIN `user_schedule` AS `UserSchedule`
ON `WeekHours`.`id_weekhours` = `UserSchedule`.`id_weekhours` AND `UserSchedule`.`date` = '2021-05-03'
WHERE `UserSchedule`.`id_weekhours` IS NULL AND `WeekHours`.`week_day` = 'Monday';
I created this in sequelize:
await WeekHours.findAll({
attributes: ['hour', 'weekDay'],
where: {
idWeekhours: {
[Op.eq]: null
},
weekDay,
},
include: [{
attributes: [],
model: UserSchedule,
where: {
date,
},
required: false,
}],
})
but i get that:
SELECT `WeekHours`.`id_weekhours` AS `idWeekhours`, `WeekHours`.`hour`, `WeekHours`.`week_day` AS `weekDay`
FROM `week_hours` AS `WeekHours`
LEFT OUTER JOIN `user_schedule` AS `UserSchedule` ON `WeekHours`.`id_weekhours` = `UserSchedule`.`id_schedule` AND `UserSchedule`.`date` = '2021-05-03'
WHERE `WeekHours`.`id_weekhours` IS NULL AND `WeekHours`.`week_day` = 'Monday';
the issues that i have is:
wrong:
`WHERE `WeekHours`.`id_weekhours
correct:
`WHERE `UserSchedule`.`id_weekhours
wrong:
`ON `WeekHours`.`id_weekhours` = `UserSchedule`.`id_schedule
correct:
`ON `WeekHours`.`id_weekhours` = `UserSchedule`.`id_weekhours
const usedHours = await WeekHours.findAll({
attributes: ['hora'],
where: {
'$UserSchedule.id_weekhours$': {
[Op.eq]: null
},
diaSemana,
},
include: [{
attributes: [],
model: UserSchedule,
on: {
id: Sequelize.where(Sequelize.col("WeekHours.id_weekhours"), "=",Sequelize.col("UserSchedule.id_weekhours"))
},
where: {
data,
},
required: false,
}],
})

How to convert sql query with exist into mongodb query

I have two documents on mongodb, these are percentages and items. I'm good at SQL, I can write PLSql query as follows but i can not convert to mongodb query. Because my mongodb level of knowledge is at the beginning. Actually I know I have to use $gt for the and condition. But I don't know how I can say not exists or union keyword for mongodb. How can I write mongodb query? which keywords should i search for?
select p.*, "to_top" as list
from percentages p
where p.percentage > 5
and p.updatetime > sysdate - 1/24
and not exists (select 1
from items i
where i.id = p.p_id
and i.seller = p.seller)
order by p.percentage desc
union
select p2.*, "to_bottom" as list
from percentages p2
where p2.percentage > 5
and p2.updatetime > sysdate - 1/24
and exists (select 1
from items i2
where i2.id = p2.p_id
and i2.seller = p2.seller)
order by p2.percentage desc
There is no UNION for MongoDB. Luckely, each query is performed on the same collection and have very close condition, so we can implement "Mongo way" query.
Explanation
Normally, alsmost all complex SQL queries are done with the MongoDB aggregation framework.
We filter document by percentage / updatetime. Explanation why we need to use $expr
SQL JOIN / Subquery is done with the $lookup operator.
SQL SYSDATE in MongoDB way can be NOW or CLUSTER_TIME variable.
db.percentages.aggregate([
{
$match: {
percentage: { $gt: 5 },
$expr: {
$gt: [
"$updatetime",
{
$subtract: [
ISODate("2020-06-14T13:00:00Z"), //Change to $$NOW or $$CLUSTER_TIME
3600000
]
}
]
}
}
},
{
$lookup: {
from: "items",
let: {
p_id: "$p_id",
seller: "$seller"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [ "$$p_id", "$id"]
},
{
$eq: [ "$$seller", "$seller"]
}
]
}
}
},
{
$limit: 1
}
],
as: "items"
}
},
{
$addFields: {
list: {
$cond: [
{
$eq: [{$size: "$items"}, 0]
},
"$to_top",
"$to_bottom"
]
},
items: "$$REMOVE"
}
},
{
$sort: { percentage: -1 }
}
])
MongoPlayground
Note: The MongoDB aggregation has the $facet operator that allows to perform different queries on the same collection.
SCHEMA:
db.percentages.aggregate([
{$facet:{
q1:[...],
q2:[...],
}},
//We apply "UNION" the result documents for each pipeline into single array
{$project:{
data:{$concatArrays:["$q1","$q2"]}
}},
//Flatten array into single object
{$unwind:"$data"}
//Replace top-level document
{$replaceWith:"$data"}
])
MongoPlayground
why you don't import your mangoDB data into oracle and use sql(that is more easy and powerful than mango.)

Filter an array of dates (datetime) with GROQ (sanity) (React App)

I have a list of movies that could be shown more than once. I decided to provide a user with an option to select multiple dates for a single movie (sanity studio interface).
The schema for movies is as follows:
export default {
name: 'movie',
title: 'Movie',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string'
},
{
name: 'dates',
title: 'Dates',
type: 'array',
of: [
{
type: 'datetime',
options: {
dateFormat: 'YYYY-MM-DD',
timeFormat: 'HH:mm',
timeStep: 15,
calendarTodayLabel: 'Today'
}
}
]
},
{
name: 'poster',
title: 'Poster',
type: 'image',
options: {
hotspot: true
}
},
{
name: 'body',
title: 'Body',
type: 'blockContent'
}
],
preview: {
select: {
title: 'title',
date: 'date',
media: 'poster'
}
}
}
Current query:
const query = groq`*[_type == "movie"]{
title,
dates,
poster,
body
}`
I need to filter the movie that has today's date in the dates array with GROQ
Maybe I'm overcomplicating this and someone will come up with a better way.
The idea is to avoid duplicates in the database (1 movie can be shown 3-6 times). That's the only reason I used an array
The solution for this should be:
const query = '*[_type == "movie" && dates match $today]{title, dates, poster, body}'
const today = new Date().toISOString().split('T')[0]
client.fetch(query, {today}).then(result => {
// movies which are showing today
})
However, there is currently a bug in the string tokenizer which cripples date string matching. In the meantime, I'm afraid your only option is to fetch all movies and filter client side. We're hoping to get this fixed as soon as possible.

Sequelize: on a subset of model A, sum an integer-attribute of an associated model B

I want to do this:
select sum("quantity") as "sum"
from "orderArticles"
inner join "orders"
on "orderArticles"."orderId"="orders"."id"
and "orderArticles"."discountTagId" = 2
and "orders"."paid" is not null;
which results in on my data base:
sum
-----
151
(1 row)
How can I do it?
My Sequelize solution:
The model definitions:
const order = Conn.define('orders', {
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
primaryKey: true
},
// ...
paid: {
type: Sequelize.DATE,
defaultValue: null
},
// ...
},
// ...
})
const orderArticle = Conn.define('orderArticles',
{
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
primaryKey: true
},
// ...
quantity: {
type: Sequelize.INTEGER,
defaultValue: 1
}
},
{
scopes: {
paidOrders: {
include: [
{ model: order, where: { paid: {$ne: null}} }
]
}
},
// ...
})
Associations:
orderArticle.belongsTo(order)
order.hasMany(orderArticle, {onDelete: 'cascade', hooks: true})
I came up with this after hours of research:
db.models.orderArticles
.scope('paidOrders') // select only orders with paid: {$ne: null}
.sum('quantity', { // sum up all resulting quantities
attributes: ['quantity'], // select only the orderArticles.quantity col
where: {discountTagId: 2}, // where orderArticles.discountTagId = 2
group: ['"order"."id"', '"orderArticles"."quantity"'] // don't know why, but Sequelize told me to
})
.then(sum => sum) // return the sum
leads to this sql:
SELECT "orderArticles"."quantity", sum("quantity") AS "sum",
"order"."id" AS "order.id", "order"."taxRate" AS "order.taxRate",
"order"."shippingCosts" AS "order.shippingCosts", "order"."discount"
AS "order.discount", "order"."paid" AS "order.paid",
"order"."dispatched" AS "order.dispatched", "order"."payday" AS
"order.payday", "order"."billNr" AS "order.billNr",
"order"."createdAt" AS "order.createdAt", "order"."updatedAt" AS
"order.updatedAt", "order"."orderCustomerId" AS
"order.orderCustomerId", "order"."billCustomerId" AS
"order.billCustomerId" FROM "orderArticles" AS "orderArticles" INNER
JOIN "orders" AS "order" ON "orderArticles"."orderId" = "order"."id"
AND "order"."paid" IS NOT NULL WHERE "orderArticles"."discountTagId" =
'4' GROUP BY "order"."id", "orderArticles"."quantity";
which has this result on the same data base: 0 rows
If you know what I got wrong please let me know!
Thank you :)
Found the solution:
in the scopes definition on the orderArticle model:
scopes: {
paidOrders: {
include: [{
model: order,
where: { paid: {$ne: null}},
attributes: [] // don't select additional colums!
}]
}
},
//...
and the algorithm:
db.models.orderArticles
.scope('paidOrders')
.sum('quantity', {
attributes: [], // don't select any further cols
where: {discountTagId: 2}
})
Note: In my case it was sufficient to return the promise. I use GraphQL which resolves the result and sends it to the client.