MySQL distinct count type query with RethinkDB - sql

I'm having some problems implementing the following SQL query in rethinkdb, I wanna get the 5 most popular channels in a community, based on user_count.
SELECT
channels.*,
COUNT(distinct channel_users.user_id) as user_count
FROM channel_users
LEFT JOIN channels ON
channels.id = channel_users.channel_id
WHERE channels.community_id = "MY_COMMUNITY_ID" AND channels.type = 'public'
GROUP BY channel_id
ORDER BY user_count DESC
LIMIT 5
This is what I got this far in ReQL, which just gives me a list of the channels, I suspect some more map/reducing is required here?
r.db('my_db')
.table('channel_users')
.filter({ community_id : 'MY_community_id' })
.orderBy(r.desc('created_at'))
.eqJoin('channel_id', r.table('channels'))
.map(function(doc){
return doc.merge(function(){
return {
'left' : null,
'right': {'user_id': doc('left')('user_id')}
}
})
})
.zip()
.run(function(err, channels){
console.log(err, channels);
next();
});
And the table design looks like:
channel_users
id | channel_id | community_id | role | user_id
channels
id | community_id | name | user_id (creator)
Any help appreciated! Thanks

Does this do what you want?
r.table('channels').filter(
{community_id: 'MY_COMMUNITY_ID', type: 'public'}
).merge(function(channel) {
return {user_count: r.table('channel_users').filter({channel_id: channel('id')}).count()};
}).orderBy(r.desc('user_count')).limit(5)
(Note that you can speed this up by using getAll instead of filter inside the merge if you create a secondary index on channel_id.)

Related

Eloquent: get AVG from all rows that have minimum timestamp

I want to get the User ID and it's average score from every minimum timestamp for each category. Here's the table structure
Skill Table
id | user_id | category | score | timestamp**
0....10............a................11........12
1....10............a................10........9
2....10............b................12........10
3....10............c................11........8
4....11............a................8........9
5....11............b................9........10
6....11............c................10........8
7....11............c................15........14
I want to get the result like this:
user_id | AVG(score)
10........11 (average id: 1,2,3)
11........9 (average id: 4,5,6)
For now I use the looping query for every user
foreach ($userIds as $id){
// in some case I need to get from only specified Ids not all of them
foreach ($category as $cat) {
// get the minimum timestamp's score for each category
$rawCategory = Skill::where("user_id", $id)->where("timestamp", "<=", $start)->where("category",$cat->id)->orderBy("timestamp", "desc")->first();
if($rawCategory){
$skillCategory[$cat->cat_name] = $rawCategory->score;
}
}
//get the average score
$average = array_sum($skillCategory) / count($skillCategory);
}
I want to create better Eloquent query to get the data like this with good performance (< 60 sec). Have anyone faced a similar problem and solved it? If so, can you please give me the link. Thanks

Postgres - Querying table with many to many relationship

My schema is composed like this:
channel -> channels_categories -> category
A channel can have many categories, and categories can belong to many channels.
When i query for channels, i would like to get the channels categories with them.
Anyway if i use JOIN i will simply get the channel duplicated for each category.
[
{
"channel_name": "Channel1",
"category_name": "Category1",
"category_id": "1"
},
{
"channel_name": "Channel1",
"category_name": "Category2"
"category_id": "2"
}
]
The ideal result format (JSON) would be something like:
{
channel_name: 'Channel1',
categories: [{/**category**/}, ....]
}
Is there a way i can achieve this result format just with SQL?
This is completely untested but I've looked at the manual for JSON functions and it looks like the following might work:
select jsonb_build_object(
'channel_name', channel.name,
'categories', jsonb_agg(
jsonb_build_object(
'category_id', category.id,
'category_name', category.name
)
)
)
from channel
join channels_categories on channel.id = channel_id
join category on category.id = category_id
group by channel.id
This assumes that channel has a primary key called id.
I just noticed that you really want the result in the JSON format, this answer is about combining and merging attributes into JSON lists.
You can use string_agg and concat to combine the category_name and category_id to lists like this:
select channel_name,
concat('[', string_agg(category_name, ','), ']') as category_names,
concat('[', string_agg(cast(category.id as text), ','), ']') as category_ids
from
channel inner join channel_category on channel.id = channel_category.channel_id
inner join category on channel_category.category_id = category.id
group by channel_name;
Which produces results like that:
| channel_name | category_names | category_ids |
| channel1 | [category1,category2]| [1,2] |
For a complete JSON result melpomene's answer seems to fit your needs way better than this.

Subqueries in Cakephp 3.0

How can I Convert Mysql Query :
SELECT * , (
SELECT COUNT( * )
FROM articles
WHERE `user_id` =1
) AS total_count
FROM articles
WHERE `user_id` =1
GROUP BY user_id
into cakephp subquery?
I have tried this,
$articlescount = $this->Articles->find('all')->where(['user_id' => 1])->count();
print_r($articlescount);
Which returns me only no of count.
I have doubts that the query you are using is the best way to do what you want to achieve since it seems that the query and the subquery are returning the same value
Anyway this is how you can obtain the same exact query you asked
$q = $this->Articles->find();
$q->select([$q->func()->count('*')])
->where(['user_id' => 1]);
$q2 = $this->Users->find()
->select(['total_count' => $q])
->autoFields(true)
->where(['user_id' => 1])
->group(['user_id'])
->all();
$q is the subquery while $q2 is the actual query you want.
By the way i think that you could simply do
$q = $this->Users->find()
->select(['total_count' => $q->func()->count('*')])
->autoFields(true)
->where(['user_id' => 1])
->group(['user_id']);
Ariala's comment almost good but i think this code is more flexible because the suquery is not contains user id fixed condition:
$q = $this->Articles->find();
$q->select([$q->func()->count('*')])
->where(['Articles.user_id = Users.id']);
$q2 = $this->Users->find()
->select(['id', 'first_name', 'total_count' => $q])
->where(['id' => 1])
->all();
But if you would like to list all users with article count you can do it if you leave the where condition from $q2.
It will results like this:
id | first_name | total_count
1 | John | 3
2 | Doe | 0

get JOIN table as array of results with PostgreSQL/NodeJS

I'm creating an app where users are able to create questions, and others can upvote/downvote them.
The following is a part of my sql schema:
CREATE TABLE "questions" (
id SERIAL,
content VARCHAR(511) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT pk_question PRIMARY KEY (id)
);
CREATE TABLE "votes" (
id SERIAL,
value INT,
question_id INT NOT NULL,
CONSTRAINT pk_vote PRIMARY KEY (id),
CONSTRAINT fk_question_votes FOREIGN KEY (question_id) REFERENCES questions (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
);
What I would like to have is Postgres giving me each question with an array of votes, like that:
[{ // a question
id: 1,
content: 'huh?',
votes: [{ // a vote
id: 1,
value: 1
}, { // another vote
id: 2,
value: -1
}]
}, { /*another question with votes*/ }]
I looked at aggregate functions (like array_agg()) but it gave me only the values. A JOIN gave me a question joined with a vote, and would force me to do server side operations, which I would prefer not to.
Is there any way to do that? Is my reasoning regarding what I want to obtain wrong?
Thanks for your time.
This is easy to do with pg-promise:
function buildTree(t) {
const v = q => t.any('SELECT id, value FROM votes WHERE question_id = $1', q.id)
.then(votes => {
q.votes = votes;
return q;
});
return t.map('SELECT * FROM questions', undefined, v).then(a => t.batch(a));
}
db.task(buildTree)
.then(data => {
console.log(data); // your data tree
})
.catch(error => {
console.log(error);
});
The same as above, but using ES7 async/await syntax:
await db.task(async t => {
const questions = await t.any('SELECT * FROM questions');
for(const q of questions) {
q.votes = await t.any('SELECT id, value FROM votes WHERE question_id = $1', [q.id]);
}
return questions;
});
// method "task" resolves with the correct data tree
API: map, any, task, batch
Related questions:
Get a parents + children tree with pg-promise
Conditional task with pg-promise
And if you want to use just a single query, then using PostgreSQL 9.4 and later syntax you can do the following:
SELECT json_build_object('id', q.id, 'content', q.content, 'votes',
(SELECT json_agg(json_build_object('id', v.id, 'value', v.value))
FROM votes v WHERE q.id = v.question_id))
FROM questions q
And then your pg-promise example would be:
const query =
`SELECT json_build_object('id', q.id, 'content', q.content, 'votes',
(SELECT json_agg(json_build_object('id', v.id, 'value', v.value))
FROM votes v WHERE q.id = v.question_id)) json
FROM questions q`;
const data = await db.map(query, [], a => a.json);
And you definitely will want to keep such complex queries in external SQL files. See Query Files.
Conclusion
The choice between the two approaches presented above should be based on the performance requirements of your application:
The single-query approach is faster, but is somewhat difficult to read or extend, being fairly verbose
The multi-query approach is easier to understand and to extend, but it is not great for performance, due to dynamic number of queries executed.
UPDATE-1
The following related answer offers more options, by concatenating child queries, which will give a much improved performance: Combine nested loop queries to parent result pg-promise.
UPDATE-2
Another example added, using ES7 async/await approach.
Please think simple way, May be I am right, I use knex js
let allpost = knex
.select([
'questions.id',
'question.content',
knex.raw('json_agg(v.*) as votes')
])
.from('questions')
.leftJoin('votes as v', 'questions.id', 'v.question_id')
.groupBy('questions.id');
sql-toolkit does exactly this. It's a node library built for pg-promise which allows you to write regular native SQL and receive back properly structured (nested) pure business objects, without either having to split up the query or rewrite it with json_build_object.
For example:
class Article extends BaseDAO {
getBySlug(slug) {
const query = `
SELECT
${Article.getSQLSelectClause()},
${Person.getSQLSelectClause()},
${ArticleTag.getSQLSelectClause()},
${Tag.getSQLSelectClause()}
FROM article
JOIN person
ON article.author_id = person.id
LEFT JOIN article_tags
ON article.id = article_tags.article_id
LEFT JOIN tag
ON article_tags.tag_id = tag.id
WHERE article.slug = $(slug);
`;
return this.one(query, { slug });
// OUTPUT: Article {person: Person, tags: Tags[Tag, Tag, Tag]}
}
The select clause uses the business object "getSQLSelectClause" methods to save tedium in typing the columns, as well as ensure no collisions of names (nothing magical going on, and could just be written out instead).
The this.one is a call into sql-toolkits base DAO class. It is responsible for structuring the flat result records into a nice nested structure.
(Also notice that it is "one" which matches our mental model for the SQL. The DAO methods for one, oneOrNone, many, and any ensure their count against the number of generated top level business objects - not the number of rows the sql expression returns!)
Check out the repository for details on how to set it up on top of pg-promise. It's strictly an enhancement, and does not seek to abstract out pg-promise (you still set up pg-promise and can use it directly). (Disclamer, I am the author of sql-toolkit.)

Is there a way to combine where and where.not into one condition in Rails?

I have an Event model, that has user_id inside it. I want to select all objects of this model, with specified user_id but not including specific events. So I can do it with a query like that:
Event.where(user_id: user.id).where.not(id: id)
But can I combine these 2 where functions into one?
I know that if I need to find, for example, events with specified ids and user_ids, I can do it this way:
Event.where(user_id: user_id).where(id: id)
and I can compact it using one where call instead of two:
Event.where(user_id: user_id, id: id)
but can I do the same thing if I am using where and where.not?
You can gather
Event.where(user_id: 1) + Event.where.not(id: 2)
or deny a parameter
Event.where(user_id: 1).where.not(id: 2)
You can write as per below to add where and where.not :
Event.where(
"user_id = ? AND id != ?",
user.id,
id
)
so if user_id = 1 and id = 2
than this will return records with user_id 1 and without id 2 :)
try this,you can create two scopes and calling then in chain
scope :with_user, ->(user) {user_id: user.id}
scope :excluded_event, ->(event_ids) { where.not(id: event_ids) }
Event.with_user(user).excluded_event(event_ids)