sql count base table with subtable condition - sql

user_table
| uid |
----------
| 1 |
| 2 |
| 3 |
| 4 |
user_role_table
| uid | role |
-----------------------------
| 1 | Main1Role |
| 1 | Main2Role |
| 1 | Sub1Role |
| 1 | Sub2Role |
| 2 | Main1Role |
| 2 | Sub1Role |
| 3 | Main1Role |
| 3 | Main2Role |
| 4 | Sub1Role |
| 4 | Sub2Role |
if the user has a main role he should not be counted for subrole.
uid 1 is counted in Main
uid 2 is counted in Main
uid 3 is counter in Main
uid 4 is counted in sub
it is like sum of users with higher priority given to main user
Expected Output
MainRoleCount: 3
SubRoleCount: 1

I am not sure about your expected output.
I understood: You want to get the number of uids of every role. But if one uid is in both, a main and a sub role, the count has to ignore the uid for the total of the sub roles.
So, in your example the counts are as follows:
Main1Role is for uids 1,2,3: Count = 3
Main2Role is for uids 1,3: Count = 2
Sub1Role is for uids 1,2,4, but 1 and 2 have a main role, so it is only for 4: Count = 1
Sub2Role is for 1,4, but 1 has a main role, so it is only for 4: Count = 1
Assuming this is what you want:
demo:db<>fiddle
SELECT
role,
SUM (
CASE WHEN role IN ('Main1Role', 'Main2Role') THEN 1
ELSE CASE WHEN ARRAY['Main1Role', 'Main2Role'] && array_agg THEN 0
ELSE 1 END
END
)
FROM (
SELECT
*,
array_agg(role) OVER (PARTITION BY uid)
FROM
user_role_table
) s
GROUP BY role
For added expected output. Same idea, but subquerying the role types:
demo:db<>fiddle

You can try this simple query to get your desired output-
SELECT
CASE
WHEN role_name = 'M' THEN 'MainRoleCount'
WHEN role_name = 'S' THEN 'SubRoleCount'
END role_name,
COUNT(*)
FROM
(
SELECT uid,MIN(LEFT(role,1)) role_name
FROM your_table
GROUP BY uid
)A
GROUP BY role_name
Output will be-
role_name Count
Main1Role 3
Sub1Role 1

Related

SQL query for finding records where count < 2

I have a table called Customer:
|device_id|user_id|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 2 |
| 7 | 3 |
Now I want to return only the entries which have only 1 device per user. In this case only
|device_id|user_id|
| 7 | 3 |
Should be returned because user_id 3 is the only one with only 1 device (user_id 1 has 4, user_id 2 has 2)
How would I do that with a query?
One method is not exists:
select t.*
from t
where not exists (select 1
from t t2
where t2.user_id = t.user_id and t2.device_id <> t.device_id
);
You can also use aggregation:
select device_id, max(user_id) as user_id
from t
group by device_id
having count(*) = 1;
We can use group by to group the data on the basis of user_id followed by aggregate funciton to get the count of device:
SELECT device_id,user_id FROM customer where user_id IN
(
SELECT user_id from
(
SELECT user_id,count(*) FROM customer GROUP BY user_id HAVING count(*)<2
)
);

PSQL select all rows with a non-unique column

The query is supposed to query the item table and:
filter out active=0 items
select id and groupId where there's at least one more item with that groupId
Example:
| id | groupId | active |
| --- | ------- | ------ |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 2 | 0 |
| 4 | 3 | 1 |
| 5 | 3 | 1 |
| 6 | 4 | 1 |
Desired Output:
| id | groupId |
| --- | ------- |
| 4 | 3 |
| 5 | 3 |
Explanation
groupID 1: invalid because has only 1 member
groupID 2: invalid because has two members, but one is inactive
groupID 3: valid
groupID 4: invalid because has only 1 member
What I tried
SELECT id, groupId
FROM items
WHERE id IN (
SELECT id
FROM items
WHERE active=1
GROUP BY groupId
HAVING COUNT(*) > 1
);
But I get the id must appear in the GROUP BY clause or be used in an aggregate function error.
I understand I can mess around with the sql_mode to get rid of that error, but I would rather avoid that.
Go for window functions:
select i.*
from (select i.*, count(*) over (partition by groupid) as cnt
from items i
where active = 1
) i
where cnt > 1
Window functions is the way to go.
But if you want to fix your query then this should do it:
select a.id, a.groupId from items a
where active = 1 and groupid in(
select groupId from item
where active = 1
group by groupId
having count(distinct id) > 1
)
because we are counting which groupid has more than 1 id for the same groupid

TSQL - Picking up first match from a group of rows

I have a simple scenario wherein, a table stores data about which card(s) a users uses and if those cards are registered (exist) in the system. I've applied ROW_NUMBER to group them too
SELECT User, CardId, CardExists, ROW_NUMBER() OVER (PARTITION BY User) AS RowNum From dbo.CardsInfo
User | CardID | CardExists | RowNum
-------------------------------------
A | 1 | 0 | 1
A | 2 | 1 | 2
A | 3 | 1 | 3
---------------------------------
B | 4 | 0 | 1
B | 5 | 0 | 2
B | 6 | 0 | 3
B | 7 | 0 | 4
---------------------------------
C | 8 | 1 | 1
C | 9 | 0 | 2
C | 10 | 1 | 3
Now in the above, I need to filter out User cards based on the two rules below
If in the cards registered with a user, multiple cards exist in the system, then take first one. So, for user A, CardID 2 will be returned and for User C it'll return CardID = 8
Othwerwise, if no card is existing (registered) for the user in the system, then just take the first one. So, for user B, it should return CardID = 4
Thus, final returned set should be -
User | CardID | CardExists | RowNum
-------------------------------------
A | 2 | 1 | 2
---------------------------------
B | 4 | 0 | 1
---------------------------------
C | 8 | 1 | 1
How can I do this filteration in SQL?
Thanks
You can use:
SELECT ci.*
FROM (SELECT User, CardId, CardExists,
ROW_NUMBER() OVER (PARTITION BY User ORDER BY CardExists DESC, CardId) AS RowNum
FROM dbo.CardsInfo ci
) ci
WHERE seqnum = 1;
You can also do this with aggregation:
select user,
max(cardexists) as cardexists,
coalesce(min(case when cardexists = 1 then cardid end),
min(card(cardid)
) as cardid
from cardsinfo
group by user;
Or, if you have a separate users table:
select ci.*
from users u cross apply
(select top (1) ci.*
from cardinfo ci
where ci.user = u.user
order by ci.cardexists desc, cardid asc
) ci

SQL count referrals for each user

My query:
SELECT COUNT(referrer) as refs, SUM(amount) as total, contracts.id, userid, fine
FROM contracts
JOIN users ON contracts.userid = users.id
WHERE active = 1
GROUP BY userid
my users table :
id | username | referrer (int)
1 | test | 2
2 | drekorig |
3 | maximili | 2
my contracts table:
id ! userid | amount | fine | active
1 | 1 | 50 | 23/10/2018 | 1
2 ! 2 | 120 | 24/10/2018 | 1
3 | 2 | 150 | 24/10/2018 | 1
How do I get the count of referrals for each User? My query actually gets the number of contracts instead...
Expected result:
refs | total | id | userid | fine
0 | 0 | 1 | 1 | 23/10/2018
2 | 270 | 2 | 2 | 24/10/2018
http://sqlfiddle.com/#!9/0a464d/5
SELECT r.count as refs,
SUM(amount) as total,
MAX(c.id),
u.id,
MAX(fine)
FROM users u
LEFT JOIN
(SELECT referrer, COUNT(*) `count`
FROM users
GROUP BY referrer
) r
ON u.id = r.referrer
JOIN contracts c
ON c.userid = u.id
WHERE active = 1
GROUP BY u.id

Counts of unique values in Postgres GROUP BY

I have a table with schema:
uid | day | type
In pandas, it looks like this:
d=pd.DataFrame(columns=['uid','day','type'])
d.loc[0]=[1,1,'C']
d.loc[1]=[1,1,'T']
d.loc[2]=[1,1,'C']
d.loc[3]=[2,1,'T']
d.loc[4]=[1,2,'T']
I want to:
GROUP BY uid and day.
Get the count of unique type values per group.
Return the top 3 type values per group.
In pandas, it's possible to get counts of unique values per group:
d.groupby(['uid','day']).type.value_counts()
The output (then I would filter to get the top 3 per group).
uid day
1 1 C 2
T 1
2 T 1
2 1 T 1
How would this query be done in postgres?
I'm not sure to completely understand your question, but as I can't leave a comment I'll just give it a try.
Let's say we have the table t containing these data :
uid | day | type
-----+-----+------
1 | 1 | C
1 | 1 | T
1 | 1 | C
2 | 1 | T
1 | 2 | T
Then this query will return what you want :
SELECT uid, day, type, count(type)
FROM t
GROUP BY uid, day, type;
uid | day | type | type_count
-----+-----+------+------------
1 | 1 | C | 2
1 | 2 | T | 1
1 | 1 | T | 1
2 | 1 | T | 1
Then you can make an ORDER BY DESC on the column type_count with a LIMIT 3 and you get your top 3.
I hope it's what you're looking for.