I'm trying to write a query using knex to SUM the votes for each question but am not getting the correct sum. I can write the subquery in SQL but can't seem to piece it all together. I am a student and not sure if I'm doing something wrong with Knex or if my underlying logic is wrong. Thanks in advance for any help!
My knex query looks like this
return knex
.from('question')
.select(
'question.id AS question_id',
knex.raw(
`count(DISTINCT vote) AS number_of_votes`, //this returns the number_of_votes for each question_id as expected
),
knex.raw(
`sum(vote.vote) AS sum_of_votes`, //something wrong here... E.g., question_id 1 has 3 down votes so the sum should be -3, however I am getting -9
),
)
.leftJoin('user', 'question.user_id', 'user.id')
.leftJoin('vote', 'question.id', 'vote.question_id')
.groupBy('question.id', 'user.id');
There are 3 tables that look like:
user
id
user_name
question
id
title
body
user_id (FK references user.id)
vote
question_id (FK references question.id)
user_id (FK references user.id)
vote (-1 or 1)
PRIMARY KEY (question_id, user_id)
I did manage to write the query as a stand-alone SQL query and verified that it works as expected. This is what I am trying to accomplish in the above knex query:
SELECT question.id, sum(vote.vote) AS sum_of_votes FROM question LEFT JOIN vote ON question.id = vote.question_id GROUP BY question.id;
So, broadly your SQL query is correct (after fixing a couple of typos) although as #felixmosh points out it has no user information in it: might be tricky to figure out who voted for what! But perhaps you don't need that for your purposes.
Your posted solution will do the trick, but is perhaps not the most efficient query for the job as it involves a subquery and several joins. Here's the SQL it generates:
SELECT "question"."id" AS "question_id",
count(DISTINCT vote) AS number_of_votes,
(
SELECT sum(vote) FROM vote
WHERE question_id = question.id
GROUP BY question_id
) AS sum_of_votes
FROM "question"
LEFT JOIN "user" ON "question"."user_id" = "user"."id"
LEFT JOIN "vote" ON "question"."id" = "vote"."question_id"
GROUP BY "question"."id", "user"."id";
We can take a simpler approach to get the same information. How about this?
SELECT question_id,
count(vote) AS number_of_votes,
sum(vote) AS sum_of_votes
FROM vote
GROUP BY question_id;
This gets all the information you were looking for, without joining any tables or using subqueries. It also avoids DISTINCT, which could lead to incorrectly counting the number of votes. The Knex to generate such a query looks like this:
knex("vote")
.select("question_id")
.count("vote AS number_of_votes")
.sum("vote AS sum_of_votes")
.groupBy("question_id")
You only really need to join tables here if you were looking for further information from those tables (such as the user's name or the question's title).
After hours of trying to figure this out I finally got it. Here is solution:
return knex
.from('question')
.select(
'question.id AS question_id',
knex.raw(
`count(DISTINCT vote) AS number_of_votes`,
),
knex.raw(
`SELECT sum(vote) from vote WHERE question_id = question.id GROUP BY question_id) AS sum_of_votes`
)
.leftJoin('user', 'question.user_id', 'user.id')
.leftJoin('vote', 'question.id', 'vote.question_id')
.groupBy('question.id', 'user.id');
Related
Im trying to get all pending friend requests from a table with schema:
id
user_id
friend_id
A pending request would just be a single row such as:
(Meaning user 1 sent a request to user 2)
id
user_id
friend_id
1
1
2
and when accepted it becomes two rows, and I am able to join two of this table to find all accepted (this is working just fine).
An accepted request for reference:
id
user_id
friend_id
1
1
2
2
2
1
My accepted query looks like this (Im using bookshelf js and knex.js):
const friends = new Friends();
return friends.query((qb) => {
qb.select('friends.user_id', 'friends.friend_id');
qb.join('friends as friendsTwo', 'friends.user_id', 'friendsTwo.friend_id');
qb.where('friends.user_id', '=', id);
}).fetchAll();
How can I modify this to only get the one way relationships?
My first thought was leftJoin and I couldnt seem to get it to work, so if anyone knows an answer or has seen a good answer please lead me to it, thanks :).
I think you can completely go away from idea of join in this project. I had done something similar in my previous projects and I think it will be best to add a third field isMutual meaning if they are mutual or not. The field is self-explanatory and think you got the idea. After that your table must look like
No accepted friendship
id
user_id
friend_id
isMutual
1
1
2
false
Accepted friendship
id
user_id
friend_id
isMutual
1
2
1
true
2
1
2
true
I believe doing this will actually benefit your system as your query will be faster and try making an compound index for (user_id, friend_id). This solution is more towards shifting the load.
Conclusion
Using this, you can achieve faster queries, but all the hard work done in during READ OPERATION in previous schema will shift to WRITE OPERATION.
But I think this will reduce writing speed by more.
And yeah, make sure you use transaction doing updates for isMutual field.
qb.leftJoin('friends as friendsTwo', 'friends.user_id', 'friendsTwo.friend_id');
qb.whereNull('friendsTwo.user_id');
This will LEFT JOIN, which keeps all rows (pending or accepted), but then filter to keep only those with no matching record in friendsTwo; thus returning only the pending links.
I have created a chat system where, for sending a message, I previosly check that the sender and receiver is in the room, using a query. In a first instance, I thought to use a field "members" which was an array containing two strings (the users ids). But, the problem I had was that I would have to use two array-contains in that query
chatsRef
.where("members", "array-contains", fromId)
.where("members", "array-contains", toId)
...
Something which is not valid, as the docs says:
you can include at most one array-contains or array-contains-any
clause in a compound query.
So, instead of using an array, I have used a map where the key is the user's id, and the value a true boolean.
members = {
"832384798238912789": true,
"90392p2010219021ud": true,
}
Then I query like:
chatsRef
.where(`members.${fromId}`, "==", true)
.where(`members${toId}`, "==", true)
...
Now, the problem I am having is that I need to get all the chats of an specific user, in order of the last message date.
For this, I am querying like this:
const query = firebase
.collection("chats")
.where(`members.${firebase.getCurrentUser().uid}`, "==", true)
.orderBy("lastMessage.date")
.startAfter(startAfter);
Something which throws me a link to create a compound index which looks like this:
members.832384798238912789 Asc lastMessage.date Asc
How can I generalize this compound index for all users? I mean, something like:
members.key Asc lastMessage.date Asc
Is this possible? If not, any workaround to solve this problem?
What I have thought to do (looking for something better, if possible):
Add an extra field (apart from the "members" map), "membersArray", which will allow me to query like
.where("membersArray", "array-contains", userId)
.orderBy("lastMessage.date")
Solves the problem but messes up the database modeling
Add the chat id to a subcollection for each user (unnecesary writes), so I only have to query that collection with a simple index, ignoring the
.where(`members.${firebase.getCurrentUser().uid}`, "==", true)
I am facing a little bit of a mental block in terms of how to do some relational queries with firestore while adhering to the best practices. I am creating a feed feature where you can see a feed of posts from your friends. Essentially my data structure is as follows:
Friends (collection)
-friend_doc
...data
friends_uid: [uid1, uid2]
Posts (collection)
-post_doc
...data
posted_by: uid2
Basically I am making a query to get all of the friends where the friends_uid contains my uid (uid1 in this case). And then once I mapped all of the friends uid's to an array, I want to make a firestore query to get posts where the posted_by field is equal to any of the uid's in that array of friends uid's. I haven't been able to make something that does anything like that yet.
I know that it seems most convenient to loop through the string array of friends uid's and make a query for each one like:
listOfUids.forEach(async (item) => {
const postQuerySnapshot = await firestore()
.collection('posts')
.where('uid', '==', item)
.get();
results.push(postQuerySnapshot.docs);
});
but this is extremely problematic for paging and limiting data as I could possibly receive tons of posts. I may just be too deep into this code and missing an obvious solution or maybe my data structure is somewhat flawed. Any insight would be greatly appreciated.
TLDR - how can I make a firestore query that gets all docs that have a value that exists in an array of strings?
You can use an "in" query for this:
firestore()
.collection('posts')
.where('uid', 'in', [uid1, uid2, ...])
But you are limited to 10 elements in that array. So you are probably going to have to stick to what you have now. You will not be able to use Firestore's pagination API.
Your only real alternatives for this case is to create a new collection that contains all of the data you want to query in one place, as there are no real join operations. Duplicating data like this is common for nosql type databases.
I am using Bookshelf.js and Knex.js to query my database for data, however it seems that the ORM is not returning all rows that match my query. My query is written below.
MyModel.where({id: req.params.id})
.fetchAll({withRelated: ['children.children']})
.then(result => {
res.send(JSON.stringify({myData: result}));
});
This is returning some data that I need but leaving out rows that while containing the same data in each column, have a different unique ID. So, they are different entries that simply contain the same data. I need this function to return all rows and not filter out any rows that have the same data in them.
Now I understand that it would be preferred to avoid duplicate data but I need all of it for my specific use case. Is there a way that Bookshelf.js or Knex.js could return all rows and not filter out what it "thinks" is duplicate data.
Knex / bookshelf does not filter out any data that "it thinks" being duplicate. If you are querying all rows where id = req.params.id knex will return all the rows that has requested id.
Try to run the code with DEBUG=knex:* environment variable to see the query that bookshelf sends to DB. If query seems to be wrong please add it to question. If just results seems to be wrong check DB contents and add that information to the question.
I am trying to do this but this is not working :
tx.executeSql('INSERT INTO MOVIE (id, rate) VALUES(?,?) ON DUPLICATE KEY UPDATE rate=VALUES(rate)',[result.id,score]);
My id is an INT NOT NULL UNIQUE and rate an INT.
I think my syntax is wrong... Do you have a solution ?
Thx :)
Anthony.
As stated in the Web SQL Database:
User agents must implement the SQL dialect supported by Sqlite 3.6.19.
So my guess is that you are going to face the same issues you get with Sqlite. It seems that SQLite UPSERT - ON DUPLICATE KEY UPDATE is not supported by Sqlite, so I suggest just trying one of the solutions provided in the answers. Maybe something like this would work:
db.transaction(function (tx) {
tx.executeSql('INSERT OR IGNORE INTO MOVIE VALUES (?, ?)', [result.id,score]);
tx.executeSql('UPDATE MOVIE SET rate = ? WHERE id = ?', [score,result.id]);
});
See demo
By the way, Web SQL Database is deprecated.