Sum results of multiple grouped documents - couchbase

I have setup a schema where I associate two different entities: Users and Brands. This association is stored in the document UserBrand. A user may have multiple brands, and in each UserBrand document, the information there contained is different.
Here is an example of three documents:
{
type: "UserBrand",
userId: "x",
brandId: 1,
value: 100
}
{
type: "UserBrand",
userId: "x",
brandId: 2,
value: 50
}
{
type: "UserBrand",
userId: "y",
brandId: 1,
value: 150
}
As you see, user 'x' follows multiple brands. How can I get the sum of the values grouped by user, while fetching the user information as well?
So far I am able to join both document types (User and UserBrand) through this query:
SELECT ub.*, u.name FROM bucket as ub JOIN bucket as u ON KEYS "User_" || ub.userId WHERE ub.type="UserBrand" AND u.type="User" AND (ub.brand=1 OR ub.brand=2)
but I cannot seem to group the records and sum the values for a single user. Is it possible to do so on a single query?
If possible, the end result should be something like:
{
type: "UserBrand",
name: "name1',
userId: "x",
brandId: 1,
value: 150
}
{
type: "UserBrand",
name: "name2',
userId: "y",
brandId: 1,
value: 150
}

Why are you using 'join' instead of simply 'group by' and aggregation? If I wanted to sum the values for each distinct userId, I would write a query as follows:
select userId, sum(value) from bucket group by userId;
Note that when using 'group by', every field in the select list must be either part of the 'group by' list (userId above) or an aggregation expression (like sum(value) above). But what if you want to include the field 'name' in the result? If you know that 'name' always has the same value for a given id, you could use an aggregation function like 'max' or 'min' to bring up the value (since max of a set of identical values will be any of the values):
select userId, max(name), sum(value) from bucket group by userId;
For a field like 'brandId', which seems to have different values, you'd want to use an aggregation function like 'array_agg', which will take all the different values of 'brandId' and put them in an array. E.g.,
select userId, max(name), sum(value), array_agg(distinct brandId) from bucket group by userId;

Related

Left outer Join in Cube JS

I have a requirement to join two tables in cube js. First table has list of customers. Second table has list orders with customer ids with purchase date. I have to display the total orders placed by the customer with date filter. If the there is an order available in the second table I am able to get the customer row with the order count. If the date range is changed and if no orders available it should show zero against customer, instead no row is returned.
Any work around or help on this is much appreciated.
you could specify the left outer join in the SQL property of the cube:
cube(`Users`, {
sql: `SELECT * from orders LEFT OUTER JOIN customers,
})
In your dimensions specify a new dimension that takes 1 if order id is not null and takes 0 else, as follows:
hasOrder: {
type: `number`,
case: {
when: [
{ sql: `${CUBE}.order_id is null`, label: { sql: Number("0") } },
{ sql: `${CUBE}.order_id is not null`, label: { sql: Number("1") } }
],
}
},
then define a measure that sums the number of Orders, it should return O for customers with no orders:
measures: {
ordersCompletedCount: {
type: `sum`,
sql: `${CUBE.hasOrder}`
},
},

Objection JS - MySQL insert and update choices from User

I'm facing an issue with how to set up my MySQL selected relationship table as you can see below.
&
Our users have a profile page, where they can answer to 3 questions from a dropdown menu with multiple choices.
My question is how to add their 3 choices in my MySQL table selected in a single query and where I can update their choices if needed. I can't use ON DUPLICATE KEY since my user_id key is only an index because each user will appear 3 times in selected since there are 3 questions per user.
On their profile page, when our users hit the Submit button I would like our selected table to insert and/or update the choice_id for that user.
I'm using objection JS to set up my models. Here are my User and Selected models
USER MODEL
class User extends Model {
static get tableName() {
return 'users';
}
static get relationMappings() {
return {
choice: {
relation: Model.HasManyRelation,
modelClass: Selected,
join: {
from: 'users.id',
to: 'selected.user_id',
},
},
user: {
relation: Model.ManyToManyRelation,
modelClass: Choices,
join: {
from: 'choices.id',
through: {
from: 'selected.user_id',
to: 'selected.choice_id',
},
to: 'users.id',
},
},
};
}
SELECTED MODEL
class Selected extends Model {
static get tableName() {
return 'selected';
}
static get jsonSchema() {
return {
type: 'object',
properties: {
id: {
type: 'integer',
},
choice_id: {
type: 'integer',
},
user_id: {
type: 'integer',
},
},
};
}
}
Any help to check out if the relationship table is correctly set up or help with the MySQL query would be much appreciated.
Thanks!
EDIT This is where I'm at. Missing the update function for existing answers.
insert into selected(choice_id, user_id)
select choices.Id, users.Id from choices join users
on choices.Id in (1, 6, 10) and users.Id = 91
WHERE NOT EXISTS (
SELECT user_id FROM selected WHERE user_id = 91
) LIMIT 3;
This is the kind of insert you want to execute:
insert into selected(Choice_id, User_id)
select User.Id, Choices.Id
from User
join Choices
on User.Id = 12 and Choices.Id in (4, 8, 15);
Instead of the hard-coded values above, you can use your actual values on the server-side. If you are using PHP, then check what you have in $_POST and $_SESSION and build your query, but make sure you do not allow SQL injection to occur.
EDIT
Sorry for the late reply. You can do the insertion with the criteria of non-existence, like this:
insert into selected(Choice_id, User_id)
select User.Id, Choices.Id
from User
join Choices
on User.Id = 12 and Choices.Id in (4, 8, 15)
where not exists (
select 1
from selected
where Choice_id = Choices.Id and User_id = User.id
);
However, you intend to perform an update if the user chooses a different answer of a question. You can do it like this
update selected
join Choices
on selected.Choice_Id = Choices.Id and Selected.User_id = 12
join Questions
on Choices.Question_id = Questions.Id and Questions.Id = 4
set Choices = 5;
Assuming that you perform updates like above before you perform the insert-select, you should get the result you prefer. Alternatively you could create a trigger before insert which would check whether the pair already exists and if so, it would update instead of insert.
In the end, I'm going to forget the update ability of this, since I believe my selected table wasn't built the 'right' way.
I should have only one row per user_id which lists the 3 choices (choice_id1, choice_id2 and choice_id3). Like that I could use ON DUPLICATE KEY UPDATE for the user_id value.
My workaround is to delete all rows for an user before inserting new values.
START TRANSACTION ;
DELETE FROM
selected
WHERE
user_id = 91;
INSERT INTO selected(choice_id, user_id)
select choices.Id, users.Id from choices join users
on choices.Id in (3, 5, 11) and users.Id = 91
COMMIT ;

Querying element inside a collection on a json field - Postgres

I have the following json structure on my Postgres. The table is named "customers" and the field that contains the json is named "data"
{
customerId: 1,
something: "..."
list: [{ nestedId: 1, attribute: "a" }, { nestedId: 2, attribute: "b" }]
}
I'm trying to query all customers that have an element inside the field "list" with nestedId = 1.
I accomplished that poorly trough the query:
SELECT data FROM customers a, jsonb_array_elements(data->'list') e WHERE (e->'nestedId')::int = 1
I said poorly because since I'm using jsonb_array_elements on the FROM clausule, it is not used as filter, resulting in a seq scan.
I tried something like:
SELECT data FROM customers where data->'list' #> '{"nestedId": 1, attribute: "a"}'::jsonb
But it does not return anything. I imagine because the "list" field is seen as an array and not as each type of my records.
Any ideas how to perform that query filtering nestedId on the WHERE condition?
Try this query:
SELECT data FROM customers where data->'list' #> '[{"nestedId": 1}]';
This query will work in Postgres 9.4+.

Couchbase N1QL ORDER by true and field that doesn't exist

I am trying to order by a field that is present sometimes and other times its not. Example data:
[
{ Name: Jeff, RegisteredUser: true },
{ Name: Jane },
{ Name: Jill, RegisteredUser: true },
]
My query is like so:
SELECT a.*
FROM table a
WHERE AND a.DocType = "User"
ORDER BY Lower(a.RegisteredUser) ASC
However when I use the query above it doesn't order by properly at all (basically does nothing).
It does order. Please note LOWER on MISSING is MISSING and placed at first,
LOWER on non-string value is NULL (because LOWER can be done only on string) which is placed after MISSING. In duplicated values can be placed any order with in duplicates, to avoid that provide more ORDER BY expressions. Checkout https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/orderby.html
You can remove LOWER because no lower is required on boolean field.
SELECT a.*
FROM table a
WHERE AND a.DocType = "User"
ORDER BY a.RegisteredUser ASC;

Sequelize findAll where a column value is the same as at least one other item in table

I currently have the below query which is getting everything that has id = a certain value, but I need it to get only items that have a their column (called Uid) value appear more than once in the table.
Basically I need to filter out items with a unique Uid column value from the result of the below query
models.table.findAll({
where:{
id: req.params.id
}
})
So I believe the SQL query you're looking for would be
SELECT id, uid, count(*) FROM table
WHERE id = :id
GROUP BY id, uid
HAVING count(uid) > 1;
And in Sequelize it would be:
models.table.findAll({
where: { id: req.params.id },
group: ['id', 'uid'],
having: [sequelize.literal('count(?) > ?)', 'uid', 1]
})
Not entirely sure if that is the right syntax for Sequelize. Also look at sequelize.fn() and sequelize.col().