My "users" table is like this
id name
1 UserA
2 UserB
3 UserC
4 UserD
And my "chats" table is like this
id text sentBy sentTo created
1 Hi UserB 1 2 2019-01-11
2 Hi 2 1 2019-01-12
3 Hi UserB 3 2 2019-01-13
4 Hello UserC 2 3 2019-01-14
5 Hello 3 2 2019-01-15
6 What u do 2 1 2019-01-16
7 Nothing 1 2 2019-01-17
8 Okay 2 1 2019-01-18
8 Hi UserA 3 1 2019-01-19
I want to show user list who is involved in a conversation with logged in user based on the last msg.
Like if UserA logged in the system then the list should be like
userId userName text created
3 UserC Hi UserA 2019-01-19
2 UserB Okay 2019-01-19
I tried by using a join query and group by but not succeed.
I am using PostgreSQL with koa js.
You may use DISTINCT ON to filter out all apart from the most recent record for each involved in the conversation.
SELECT DISTINCT ON( involved) involved AS userid,
u.NAME AS username,
text,
created
FROM (SELECT c.*,
CASE sentby
WHEN 1 THEN sentto
ELSE sentby
END AS involved
FROM chats c
WHERE c.sentby = 1
OR c.sentto = 1) s
JOIN users u
ON s.involved = u.id
ORDER BY involved,
created DESC
Demo
If you want a generic case, you may convert this to a SQL type Postgres function and pass userid as an argument and use it in place of 1.
To get a complete list of users involved in the last conversation of the $current_user, with the last message (sent or received) and its date (created):
WITH u1 AS (SELECT id FROM users WHERE name = $current_user)
SELECT DISTINCT ON (userId) *
FROM (
SELECT u.id AS userId, u.name AS userName, c.text, c.created -- received
FROM u1
JOIN chats c ON c.sentBy = u1.id
JOIN users u ON u.id = c.sentTo
UNION ALL
SELECT u.id, u.name, c.text, c.created -- sent
FROM u1
JOIN chats c ON c.sentTo = u1.id
JOIN users u ON u.id = c.sentBy
) sub
ORDER BY userId, created DESC;
I separated into two UNIONed SELECTs, to make the most of two separate indexes on chats - one with leading sentTo, one with leading sentBy. Just updated a closely related answer yesterday:
Finding all rows with unique combination of two columns
About DISTINCT ON:
Select first row in each GROUP BY group?
Might be optimized in various ways, depending on undisclosed information.
Related
I have 3 tables. User Accounts, IncomingSentences and AnnotatedSentences. Annotators annotate the incoming sentences and tag an intent to it. Then, admin reviews those taggings and makes the corrections on the tagged intent.
DB-Fiddle Playground link: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=00a770173fa0568cce2c482643de1d79
Assuming myself as the admin, I want to pull the error report per annotator.
My tables are as follows:
User Accounts table:
userId
userEmail
userRole
1
user1#gmail.com
editor
2
user2#gmail.com
editor
3
user3#gmail.com
editor
4
user4#gmail.com
admin
5
user5#gmail.com
admin
Incoming Sentences Table
sentenceId
sentence
createdAt
1
sentence1
2021-01-01
2
sentence2
2021-01-01
3
sentence3
2021-01-02
4
sentence4
2021-01-02
5
sentence5
2021-01-03
6
sentence6
2021-01-03
7
sentence7
2021-02-01
8
sentence8
2021-02-01
9
sentence9
2021-02-02
10
sentence10
2021-02-02
11
sentence11
2021-02-03
12
sentence12
2021-02-03
Annotated Sentences Table
id
annotatorId
sentenceId
annotatedIntent
1
1
1
intent1
2
4
1
intent2
3
2
2
intent4
4
3
4
intent4
5
1
5
intent2
6
3
3
intent3
7
5
3
intent2
8
1
6
intent4
9
4
6
intent1
10
1
7
intent1
11
4
7
intent3
12
3
9
intent3
13
2
10
intent3
14
5
10
intent1
Expected Output:
I want an output as a table which provides the info about total-sentences-annotated-per-each editor and the total-sentences-corrected-by-admin on top of editor annotated sentences. I don't want to view the admin-tagged-count in the same table. If it comes also, total-admin-corrected should return 0.
|userEmail |totalTagged|totalAdminCorrected|
|---------------|------------|---------------------|
|user1#gmail.com| 4 | 3 |
|user2#gmail.com| 2 | 1 |
|user3#gmail.com| 3 | 1 |
Query I wrote: I've tried my best. You can see that in the DB-Fiddle
My query is not resulting in the expected output. Requesting your help to achieve this.
My proposal...
SELECT UserEmail, SUM(EDICount), SUM(ADMCount)
FROM (SELECT UserAccounts.UserEmail, AnnotatedSentences.SentenceID, COUNT(*) AS EDICount
FROM AnnotatedSentences
LEFT JOIN UserAccounts ON UserAccounts.UserID=AnnotatedSentences.AnnotatorID
WHERE UserRole='editor'
GROUP BY UserAccounts.UserEmail, AnnotatedSentences.SentenceID) AS EDI
LEFT JOIN (SELECT AnnotatedSentences.SentenceID, COUNT(*) AS ADMCount
FROM AnnotatedSentences
LEFT JOIN UserAccounts ON UserAccounts.UserID=AnnotatedSentences.AnnotatorID
WHERE UserRole='admin'
GROUP BY AnnotatedSentences.SentenceID) AS ADM ON EDI.SentenceID=ADM.SentenceID
GROUP BY UserEmail
Because sentence_id might be reviewed by different users (role), you can try to use subquery (INNER JOIN between user_accounts & annotated_sentences) with window function + condition aggregate function, getting count by your logic.
if you don't want to see admin count information you can use where filter rows.
SELECT user_email,
count(Total_Tagged) Total_Tagged,
SUM(totalAdmin) totalAdmin
FROM (
SELECT ist.sentence_id,
user_email,
user_role,
count(CASE WHEN a.user_role = 'editor' THEN 1 END) over(partition by ist.sentence_id) + count(CASE WHEN a.user_role = 'admin' THEN 1 END) over(partition by ist.sentence_id) Total_Tagged,
count(CASE WHEN a.user_role = 'admin' THEN 1 END) over(partition by ist.sentence_id) totalAdmin
FROM user_accounts a
INNER JOIN annotated_sentences ats ON
a.user_id = ats.annotator_id
INNER JOIN incoming_sentences ist
ON ist.sentence_id = ats.sentence_id
) t1
WHERE user_role = 'editor'
GROUP BY user_email
ORDER BY user_email
sqlfiddle
Okay, i really rushed this so there might still be an error in the Code, but try something like this:
SELECT
a.user_email,
count(ist) Total_Tagged,
sum(innerTable.edits)
FROM
incoming_sentences ist
JOIN annotated_sentences ats ON
ist.sentence_id = ats.sentence_id
JOIN user_accounts a ON
a.user_id = ats.annotator_id
LEFT JOIN ( SELECT ics.sentence_id, count(anno.id) AS edits FROM annotated_sentences anno
LEFT JOIN user_accounts ua ON
ua.user_id = anno.annotator_id
LEFT JOIN incoming_sentences AS ics ON
ics.sentence_id = anno.sentence_id
WHERE user_role LIKE 'admin'
GROUP BY ics.sentence_id ) AS innerTable
ON innerTable.sentence_id = ist.sentence_id
GROUP BY a.user_email
The inner select should count how many admin-edits there are per post, the outer one then sums up that number for every post a user edited.
If it is guaranteed that one sentence can only be annotated once and only be reviewed once, then you can simply group by sentence and get the editor and admin. Then you group by editor and count.
select
editor,
count(*) as total_tagged,
count(admin) as total_admin_corrected
from
(
select
max(ua.user_email) filter (where ua.user_role = 'editor') as editor,
max(ua.user_email) filter (where ua.user_role = 'admin') as admin
from annotated_sentences ans
join user_accounts ua on ua.user_id = ans.annotator_id
group by ans.sentence_id
) with_editor_and_admin
group by editor
order by editor;
Demo: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=e409ec49af25ac8329a99b02161832fb
I have a dataset of users who have liked each other. The data looks like
User HasLiked
User 1 User 3
User 1 User 4
User 1 User 5
User 2 User 3
User 3 User 1
User 4 User 1
User 4 User 5
User 5 User 3
User 5 User 4
I can get count of liked users for any given user using
select user, count(*) as liked_count
from users
group by user;
which gives me
User liked_count
User 1 3
User 2 1
User 3 1
User 4 2
User 5 2
However I can't figure out how to count the number of times a user is liked back by someone they have liked. e.g what I want is:
User liked_count liked_back_count
User 1 2 2
User 2 1 0
User 3 1 1
User 4 2 2
User 5 2 1
Any ideas how to do this using sql? FYI, The number of distinct users is around 100K.
You can use a left join to determine if the "inverse" record appears. And then aggregate:
select u.user, count(*) as liked_count,
count(*) filter (where u2.user is not null) as liked_back_count
from users u left join
users u2
on u2.user = u.hasliked and
u2.hasliked = u.user
group by u.user;
I've a user table in my application.
Id Name ParentId
1 User1 0
2 User2 1
3 User3 1
4 User4 2
5 User5 2
The users have a relationship like grandparent->parent->child. to fetch the users related to a grand parent I'm using the following query
select * from user where ParentId=1 or ParentId in(select id from user where ParentId=1)
also these user have multiple roles in my role table and for fetching the count of rules a user having, I'm using select count(*) from group where userId=1.
Id Role UserId
1 Role 1 1
2 Role 2 1
3 Role 3 2
4 Role 4 2
5 Role 5 3
I need to fetch these both data in a single query .I'm not good with SQL and I know my first query is not perfect . How can I achieve this.
Your query is just need to be joined with your roles table.
SELECT u.id, count(r.id) no_of_roles
FROM user u left join roles r
on u.id = r.userid
WHERE u.ParentId=1
OR u.ParentId IN
(SELECT id FROM t WHERE ParentId=1
)
group by u.id;
I have two tables, one is "Activity" table
UserId ActionID companyId
2971 1 334
2971 4 334
3343 2 124
4416 3 231
4416 3 231
The other is the "ActionType" table, which contains columns of:
ActionID ActionTypeName
1 Download
2 Buy
3 View
4 Click
How should I construct my SQL to get each user's each distinct action,the name and their counts?
My desired result is something like:
UserId ActionId ActionTypeName ActionCount
2971 1 Download 1
2971 4 Click 1
3343 2 Buy 1
4416 3 View 2
Really struggling with SQL.
Thanks for help
You can use something like this:
SELECT a.UserId, a.ActionId, b.ActionTypeName, COUNT(*) AS ActionCount
FROM Activity a
INNER JOIN ActionType b
ON a.ActionId=b.ActionId
GROUP BY a.UserId, a.ActionId, b.ActionTypeName
Use group by clause to have your count like below:
Select u.userid, a.actionid,a.actiontypename , count(actionid)
from activity u
inner join actiontype a
on a.actionid = b.actionid
group by u.userid, a.actionid,a.actiontypename
I have a problem with a nested query (the problem beeing i have no idea how to do it)
I'm using PostgreSQL
I have 2 tables:
users
id name
1 x
2 y
3 z
call
id user_id time data
1 1 00:10 stat1
2 1 00:15 stat2
3 3 00:10 stat2
4 3 00:30 stat1
5 3 00:45 stat2
6 3 00:50 stat3
What i need to get is a select of users, but only if their last call was stat2. The problem i'm having is i don't know how to get only the latest data checked.
My query:
SELECT users.*, call.* FROM users, call WHERE users.id=call.user_id AND call.id IN (SELECT id FROM call WHERE call.data='stat2') ORDER BY users.id
What i get with that is:
users
id name id user_id time data
1 x 2 1 00:15 stat2
3 z 3 3 00:10 stat2
3 z 5 3 00:45 stat2
What i need to get would be:
users
id name id user_id time data
1 x 1 1 00:15 stat2
The 3 shouldn't display as it's last value is stat 3 (and obviously shouldn't double since i have 2 stats.
i can do it with multiple queries, but i'd like to do it in 1.
Thanks
So you want to get the last call id for each Users.id. You do that with a group by. Then once you get the last call id, you check if the status of that last call id is stat2.
The query looks like:
SELECT *
FROM USERS U
INNER JOIN
(SELECT user_id, MAX(id) AS last_id
FROM Calls c
GROUP BY user_id
) t0
ON t0.user_id = u.id
INNER JOIN Calls c ON c.id = t0.last_id
WHERE c.type = 'stat2'
Example fiddle: http://www.sqlfiddle.com/#!1/7016e/5
Try using ALL statement:
SELECT users.*, call.* FROM users, call WHERE users.id=call.user_id AND call.id IN (SELECT id FROM call WHERE call.data='stat2')
AND call.time >= all (select a.time from call a where a.user_id = users.id) ORDER BY users.id
That means that the time in selected row is >= time for all of the calls for this user.