PSQL select all rows with a non-unique column - sql

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

Related

Get some values from the table by selecting

I have a table:
| id | Number |Address
| -----| ------------|-----------
| 1 | 0 | NULL
| 1 | 1 | NULL
| 1 | 2 | 50
| 1 | 3 | NULL
| 2 | 0 | 10
| 3 | 1 | 30
| 3 | 2 | 20
| 3 | 3 | 20
| 4 | 0 | 75
| 4 | 1 | 22
| 4 | 2 | 30
| 5 | 0 | NULL
I need to get: the NUMBER of the last ADDRESS change for each ID.
I wrote this select:
select dh.id, dh.number from table dh where dh =
(select max(min(t.history)) from table t where t.id = dh.id group by t.address)
But this select not correctly handling the case when the address first changed, and then changed to the previous value. For example id=1: group by return:
| Number |
| -------- |
| NULL |
| 50 |
I have been thinking about this select for several days, and I will be happy to receive any help.
You can do this using row_number() -- twice:
select t.id, min(number)
from (select t.*,
row_number() over (partition by id order by number desc) as seqnum1,
row_number() over (partition by id, address order by number desc) as seqnum2
from t
) t
where seqnum1 = seqnum2
group by id;
What this does is enumerate the rows by number in descending order:
Once per id.
Once per id and address.
These values are the same only when the value is 1, which is the most recent address in the data. Then aggregation pulls back the earliest row in this group.
I answered my question myself, if anyone needs it, my solution:
select * from table dh1 where dh1.number = (
select max(x.number)
from (
select
dh2.id, dh2.number, dh2.address, lag(dh2.address) over(order by dh2.number asc) as prev
from table dh2 where dh1.id=dh2.id
) x
where NVL(x.address, 0) <> NVL(x.prev, 0)
);

sql count base table with subtable condition

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

Efficient ROW_NUMBER increment when column matches value

I'm trying to find an efficient way to derive the column Expected below from only Id and State. What I want is for the number Expected to increase each time State is 0 (ordered by Id).
+----+-------+----------+
| Id | State | Expected |
+----+-------+----------+
| 1 | 0 | 1 |
| 2 | 1 | 1 |
| 3 | 0 | 2 |
| 4 | 1 | 2 |
| 5 | 4 | 2 |
| 6 | 2 | 2 |
| 7 | 3 | 2 |
| 8 | 0 | 3 |
| 9 | 5 | 3 |
| 10 | 3 | 3 |
| 11 | 1 | 3 |
+----+-------+----------+
I have managed to accomplish this with the following SQL, but the execution time is very poor when the data set is large:
WITH Groups AS
(
SELECT Id, ROW_NUMBER() OVER (ORDER BY Id) AS GroupId FROM tblState WHERE State=0
)
SELECT S.Id, S.[State], S.Expected, G.GroupId FROM tblState S
OUTER APPLY (SELECT TOP 1 GroupId FROM Groups WHERE Groups.Id <= S.Id ORDER BY Id DESC) G
Is there a simpler and more efficient way to produce this result? (In SQL Server 2012 or later)
Just use a cumulative sum:
select s.*,
sum(case when state = 0 then 1 else 0 end) over (order by id) as expected
from tblState s;
Other method uses subquery :
select *,
(select count(*)
from table t1
where t1.id < t.id and state = 0
) as expected
from table t;

2 listagg in one SQL Select in Oracle

I have a table in the form of :
| ID | COURSE | PASS |
---------------------------
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 1 | 3 | 1 |
| 1 | 4 | 0 |
| 1 | 5 | 0 |
and I want row in the form:
| ID | FAILED | PASSED |
---------------------------
| 1 | 4,5 | 1,2,3 |
the only i figured is something like this:
select NVL(passed.id, failed.id), passed.test, failed.test from
(select id, listagg(course, ',') within group (order by course) test from table1 where pass = 1 group by id ) passed
full outer join
(select id, listagg(course, ',') within group (order by course) test from table1 where pass = 0 group by id ) failed
on passed.id = failed.id
is there a way to do it in a single query ?
Try
select id,
listagg(case when pass = 1 then course end, ',') within group (order by course) passed,
listagg(case when pass = 0 then course end, ',') within group (order by course) failed
from table1
group by id
Here is a sqlfiddle demo

Count rows grouped by condition in SQL

We have a table like this:
+----+--------+
| Id | ItemId |
+----+--------+
| 1 | 1100 |
| 1 | 1101 |
| 1 | 1102 |
| 2 | 2001 |
| 2 | 2002 |
| 3 | 1101 |
+----+--------+
We want to count how many items each guy has, and show the guys with 2 items or more. Like this:
+----+-----------+
| Id | ItemCount |
+----+-----------+
| 1 | 3 |
| 2 | 2 |
+----+-----------+
We didn't count the guy with Id = 3 because he's got only 1 item.
How can we do this in SQL?
SELECT id, COUNT(itemId) AS ItemCount
FROM YourTable
GROUP BY id
HAVING COUNT(itemId) > 1
Use this query
SELECT *
FROM (
SELECT COUNT(ItemId ) AS COUNT, Id FROM ITEM
GROUP BY Id
)
my_select
WHERE COUNT>1
SELECT id,
count(1)
FROM YOUR_TABLE
GROUP BY id
HAVING count(1) > 1;
select Id, count(ItemId) as ItemCount
from table_name
group by Id
having ItemCount > 1