I have a table:
Table Teams
Id_team member_1 member_2 member_3
1 Alice Ben
2 Ben
3 Charles Alice Ben
4 Ben Alice
I will need to know in how many different teams Alice is a member (doesn't count if she is the first member, second or third). In my sample, the right answer is 2 (with Ben in Id_team 1 and 4, with Ben and Charles in Id_team = 3). Thank you!
You have to count "alices" in each column separately to ensure distinct oer column
What you appear to checking is "
SELECT
COUNT(DISTINCT CASE WHEN member_1 = 'Alice' THEN member_1 END) +
COUNT(DISTINCT CASE WHEN member_2 = 'Alice' THEN member_2 END) +
COUNT(DISTINCT CASE WHEN member_3 = 'Alice' THEN member_3 END)
FROM tablename
WHERE 'Alice' IN(member_1, member_2, member_3);
Update: fixed COUNT
Okay, so you want to same teams with different positions (e.g. Alice&Ben, Ben&Alice) count as one.
To do this, order the members in ascending order for alice in every position, and count the results (this returns 2 to your example):
SELECT COUNT(*) FROM
(
SELECT
least( member_2, member_3) AS l,
greatest(member_2, member_3) AS g
FROM teams
WHERE
member_1 = 'Alice'
UNION
SELECT
least( member_1, member_3) AS l,
greatest(member_1, member_3) AS g
FROM teams
WHERE
member_2 = 'Alice'
UNION
SELECT
least( member_1, member_2) AS l,
greatest(member_1, member_2) AS g
FROM teams
WHERE
member_3 = 'Alice'
) q
;
Note that this can only be done to the special case of 3 member teams, because least and greatest can select the two other members - for member coun of 4 and greater, a more complex solution is needed.
You can try to concatenate the fields (sorted alphabetically) in order to turn them into a list of strings.
Then run a distinct on this list (so it will list all separate teams)
Then search how many strings contains Alice
From this the hardest is the "concat alphabetically", as I couldn't really find any good function to do it, but a GROUP_CONCAT with a separate SELECTs and UNIONs to convert the fields into rows should do it:
SELECT COUNT(*)
FROM (
SELECT DISTINCT team_as_string
FROM (
SELECT id tid, GROUP_CONCAT(q ORDER BY q ASC SEPARATOR ',') team_as_string
FROM (
SELECT id, member_1 q FROM teams
UNION SELECT id, member_2 q FROM teams
UNION SELECT id, member_3 q FROM teams
/* add more fields if needed */
) c
GROUP BY tid
) b
) a
WHERE team_as_string LIKE '%Alice%'
I haven't checked it for syntax errors, but it should be fine logically. Tested and gives the correct answer (2)
This can be enhanced for more members, if needed.
Of course if the members are in a separate join table, then the whole group_concat part can be simplified.
I will need to know in how many different teams Alice is a member
Try this:
SELECT 'Alice', COUNT(id_team)
FROM tablename
WHERE 'Alice' IN(member_1, member_2, member_3);
The result:
| ALICE | THECOUNT |
--------------------
| Alice | 3 |
Fiddle Demo.
If id_team is not unique, use COUNT(DISTINCT id_team).
Related
I've a data base with two tables.
Table Players Table Wins
ID Name ID Player_won
1 Mick 1 2
2 Frank 2 1
3 Sarah 3 4
4 Eva 4 5
5 Joe 5 1
I need a SQL query which show "The players who have not won any game".
I tried but I don't know even how to begin.
Thank you
You need all the rows from players that don't have corresponding rows in wins. For this you need a left join, filtering for rows that don't join:
select
p.id,
p.name
from Players p
left join Wins w on w.Player_won = p.id
where w.Player_won is null
You can also use not in:
select
id,
name
from Players
where id not in (select Player_won from Wins)
How about the MINUS set operator?
Sample data:
SQL> with players (id, name) as
2 (select 1, 'Mick' from dual union all
3 select 2, 'Ffrank' from dual union all
4 select 3, 'Sarah' from dual union all
5 select 4, 'Eva' from dual union all
6 select 5, 'Joe' from dual
7 ),
8 wins (id, player_won) as
9 (select 1, 2 from dual union all
10 select 2, 1 from dual union all
11 select 3, 4 from dual union all
12 select 4, 5 from dual union all
13 select 5, 1 from dual
14 )
Query begins here:
15 select id from players
16 minus
17 select player_won from wins;
ID
----------
3
SQL>
So, yes ... player 3 didn't win any game so far.
I think you should provide your attempts next time, but here you go:
select p.name
from players p
where not exists (select * from wins w where p.id = w.player_won);
MINUS is not the best option here because of not using indexes and instead performing a full-scan of both tables.
I've a data base with two tables.
You don't show the names or any definition of the tables, leaving me to make an educated guess about their structure.
I tried but I don't know even how to begin.
What exactly did you try? Possibly what you are missing here is the concept of a LEFT OUTER JOIN.
Assuming the tables are named player_table and wins_table, and have column names exactly as you showed, and that the player_won column is intended to express the number of games won by the player of that row's ID, and without knowing whether or not wins_table will have rows for players with zero wins… this should cover it:
select Name
from players_table pt
left join wins_table wt on (pt.ID = wt.ID)
-- Either this player is explicitly specified to have Player_won=0
-- or there is no row for this player ID in the wins table
-- (but excluding the possibility of an explicit NULL value, since its meaning would be unclear)
where Player_won = 0 or wt.ID is null;
As you can see from the variety of answers you've gotten, there are many ways to accomplish this.
One additional way to do this is to use COUNT in a correlated subquery, as in:
SELECT *
FROM PLAYERS p
WHERE 0 = (SELECT COUNT(*)
FROM WINS w
WHERE w.PLAYER_WON = p.ID)
db<>fiddle here
SELECT *
FROM Players p
INNER JOIN Wins w
ON p.ID = w.ID
WHERE w.players_won = 0
I have not done SQL in awhile but I think this might be right if you are looking for players with 0 wins
I have an interesting problem.
I've some teams with a team leader stored in one table and the members of teams are stored in child table. I want to determine the teams that have same members.
TEAMS
TEAM_ID LEADER_ID
1 1
2 1
3 2
4 2
MEMBERS
TEAM_ID MEMBER_ID
1 2
1 3
1 4
2 3
2 4
2 5
3 1
3 3
3 4
4 5
4 6
4 7
I was able to write this query to determine the formations and now I am clueless how to proceed.
SELECT
TEAM_ID,
(
SELECT
CONVERT (VARCHAR, MEMBER_ID) + ', '
FROM
(
SELECT
TEAM_ID,
LEADER_ID AS MEMBER_ID
FROM
TEAMS
UNION ALL
SELECT
TEAM_ID,
MEMBER_ID
FROM
MEMBERS
) FORMATIONS
WHERE
TEAM_ID = MT.TEAM_ID
ORDER BY
MEMBER_ID FOR XML PATH ('')
) AS MEMBERS
FROM
TEAMS MT
As it is clear that team id 1 and 3 are same, how can the lowest ID of the duplicate teams can be obtained.
i.e. the query should return the list of TEAM_IDs that are smallest per duplicate group (and only if they are duplicate)
In this scenario id 1 should be returned.
http://sqlfiddle.com/#!18/c845a/5
There are worse ways to approach this than stuffing the members into a string and comparing them. So, I'll follow the route you have started.
All you need to do is to combine the members from the two tables, and then use that for the logic:
with m as (
select team_id, member_id
from members
union -- on purpose to remove duplicates
select team_id, leader_id
from teams
)
select *
from (select team_id, members, count(*) over (partition by members) as num_teams
from (select t.team_id,
stuff( (select concat(',', m.member_id)
from m
where m.team_id = t.team_id
order by m.member_id
for xml path ('')
), 1, 1, ''
) as members
from teams t
) t
) t
where num_teams > 1
order by members;
Here is your SQL Fiddle.
Note that string comparison works fine for this case, which is an exact match of members. For superset relationships is doesn't work so well.
Try below query, it uses CTE to get grouped teams wth its members listed as comma separated list.
Later it is used with group by to determine lowest ID of teams with same members. To ensure thhat there will be only duplicated teams, I used having clause.
;with cte as (
select team_id,
(select cast(member_id as varchar(5)) + ',' from #members innerMembers
where team_id = m.team_id
and not exists(select 1 from #TEAMS
where leader_id = innerMembers.member_id)
order by member_id
for xml path('')) members
from #members m
group by team_id
)
select min(team_id), members from cte
group by members
having count(*) > 1
Using pure SQL.
The main idea is that two sets A and B being equal is defined by A being a subset of B and B being a subset of A.
And we can check whether B is a subset of A by getting the members of B which are in A, counting them, and checking whether this equals the count in A.
As this is a somewhat complicated step I simply did it by cross applying a subquery filtered to teams A and B. There may be a more elegant way.
WITH MembersAll AS
(
SELECT Team_Id, Member_Id FROM Members
UNION
-- Consider leaders as members.
SELECT Team_Id, Leader_Id AS Member_Id FROM Teams
),
-- Teams and any teams which are a subset of that team:
TeamSubsetTeam AS (
SELECT
ThisTeam.Team_Id,
OtherTeam.Team_Id AS SubsetTeam_Id
FROM Teams AS ThisTeam
CROSS JOIN Teams AS OtherTeam -- Considering all pairs of teams.
CROSS APPLY (
-- Get the members in both teams,
-- left join so that we have all members from a given team
-- and all of the members in the other team that are in the given team
-- then filter on the counts of these being the same.
SELECT
COUNT(MembersThisTeam.Member_Id) AS MemberCountThisTeam,
COUNT(MembersOtherTeamInThisTeam.Member_Id) AS MemberCountOtherTeamInThisTeam
FROM MembersAll AS MembersThisTeam
LEFT JOIN MembersAll AS MembersOtherTeamInThisTeam
ON MembersThisTeam.Member_Id = MembersOtherTeamInThisTeam.Member_Id
AND MembersOtherTeamInThisTeam.Team_Id = OtherTeam.Team_Id
WHERE MembersThisTeam.Team_Id = ThisTeam.Team_Id
) MemberCounts
WHERE MemberCounts.MemberCountThisTeam = MemberCounts.MemberCountOtherTeamInThisTeam
),
-- Teams and any teams which are equivalent to that team (including itself):
TeamEquivalentTeam AS (
-- From set theory, team A is equivalent to team B if
-- team A is a subset of team B and
-- team B is a subset of team A.
SELECT
Team_Id,
SubsetTeam_Id AS EquivalentTeamId
FROM TeamSubsetTeam
WHERE Team_Id IN (
SELECT SubsetTeam_Id FROM TeamSubsetTeam AS SubsetTeamSubsetTeam
WHERE SubsetTeamSubsetTeam.Team_Id = TeamSubsetTeam.SubsetTeam_Id
)
)
-- The specified post-processing step.
-- Doesn't seem particularly useful but you can do whatever you like
-- now you have the information in TeamEquivalentTeam.
SELECT DISTINCT MIN(EquivalentTeamId) AS FirstEquivalentTeam
FROM TeamEquivalentTeam
GROUP BY Team_Id
Returns:
FirstEquivalentTeam
1
2
4
I have a simple query like:
SELECT employee, ITEM_TYPE, COUNT(ITEM_TYPE)
FROM hr_database
So the output may look like
BOB MUGS 4
BOB PENCILS 10
CAT MUGS 2
CAT PAPERCLIPS 7
SAL MUGS 11
But for readability, I want to put a blank row between each user in the output(i.e for readability), like this :
BOB MUGS 4
BOB PENCILS 10
CAT MUGS 2
CAT PAPERCLIPS 7
SAL MUGS 11
Is there a way to do this in Oracle SQL ? So far, I found this link but it doesn't match what I need . I'm thinking to use a WITH in the query?
You can do it in the database, but this type of processing should really be done at the application layer.
But, it is kind of an amusing trick to figure out how to do it in the database, and that is your specific question:
WITH e AS (
SELECT employee, ITEM_TYPE, COUNT(ITEM_TYPE) as cnt
FROM hr_database
GROUP BY employee, ITEM_TYPE
)
SELECT (case when cnt is not null then employee end) as employee,
item_type, cnt
FROM (select employee, item_type, cnt, 1 as x from e union all
select distinct employee, NULL, NULL, 2 as x from e
) e
ORDER BY e.employee, x;
I emphasize, though, that this is really for amusement and perhaps for understanding better how SQL works. In the real world, you do this type of work at the application layer.
A summary of how this works. The union all brings in one additional row for each employee. The x is a priority for sorting -- because you have to sort the result set to get the proper ordering. The case statement is needed to prevent the employee from being in the first column. cnt should never be NULL for the valid rows.
You can try like this with normal union & distinct
select emp,item_type,cnt from
(select distinct ' ' as emp,' ' as item_type ,' ' as cnt, employee
from hr_database
union
select employee as emp,item_type ,to_char(count(item_type)) as cnt, employee
from hr_database
group by employee,item_type)a
order by a.employee
Heres my scenario.
I have a table with 3 rows I want to return within a stored procedure, rows are email, name and id. id must = 3 or 4 and email must only be per user as some have multiple entries.
I have a Select statement as follows
SELECT
DISTINCT email,
name,
id
from table
where
id = 3
or id = 4
Ok fairly simple but there are some users whose have entries that are both 3 and 4 so they appear twice, if they appear twice I want only those with ids of 4 remaining. I'll give another example below as its hard to explain.
Table -
Email Name Id
jimmy#domain.com jimmy 4
brian#domain.com brian 4
kevin#domain.com kevin 3
jimmy#domain.com jimmy 3
So in the above scenario I would want to ignore the jimmy with the id of 3, any way of doing this without hard coding?
Thanks
SELECT
email,
name,
max(id)
from table
where
id in( 3, 4 )
group by email, name
Is this what you want to achieve?
SELECT Email, Name, MAX(Id) FROM Table WHERE Id IN (3, 4) GROUP BY Email;
Sometimes using Having Count(*) > 1 may be useful to find duplicated records.
select * from table group by Email having count(*) > 1
or
select * from table group by Email having count(*) > 1 and id > 3.
The solution provided before with the select MAX(ID) from table sounds good for this case.
This maybe an alternative solution.
What RDMS are you using? This will return only one "Jimmy", using RANK():
SELECT A.email, A.name,A.id
FROM SO_Table A
INNER JOIN(
SELECT
email, name,id,RANK() OVER (Partition BY name ORDER BY ID DESC) AS COUNTER
FROM SO_Table B
) X ON X.ID = A.ID AND X.NAME = A.NAME
WHERE X.COUNTER = 1
Returns:
email name id
------------------------------
jimmy#domain.com jimmy 4
brian#domain.com brian 4
kevin#domain.com kevin 3
I have one table named GUYS(ID,NAME,PHONE) and i need to add a count of how many guys have the same name and at the same time show all of them so i can't group them.
example:
ID NAME PHONE
1 John 335
2 Harry 444
3 James 367
4 John 742
5 John 654
the wanted output should be
ID NAME PHONE COUNT
1 John 335 3
2 Harry 444 1
3 James 367 1
4 John 742 3
5 John 654 3
how could i do that? i only manage to get lot of guys with different counts.
thanks
Update for 8.0+: This answer was written well before MySQL version 8, which introduced window functions with mostly the same syntax as the existing ones in Oracle.
In this new syntax, the solution would be
SELECT
t.name,
t.phone,
COUNT('x') OVER (PARTITION BY t.name) AS namecounter
FROM
Guys t
The answer below still works on newer versions as well, and in this particular case is just as simple, but depending on the circumstances, these window functions are way easier to use.
Older versions: Since MySQL, until version 8, didn't have analytical functions like Oracle, you'd have to resort to a sub-query.
Don't use GROUP BY, use a sub-select to count the number of guys with the same name:
SELECT
t.name,
t.phone,
(SELECT COUNT('x') FROM Guys ct
WHERE ct.name = t.name) as namecounter
FROM
Guys t
You'd think that running a sub-select for every row would be slow, but if you've got proper indexes, MySQL will optimize this query and you'll see that it runs just fine.
In this example, you should have an index on Guys.name. If you have multiple columns in the where clause of the subquery, the query would probably benefit from a single combined index on all of those columns.
Use an aggregate Query:
select g.ID, g.Name, g.Phone, count(*) over ( partition by g.name ) as Count
from
Guys g;
You can still use a GROUP BY for the count, you just need to JOIN it back to your original table to get all the records, like this:
select g.ID, g.Name, g.Phone, gc.Count
from Guys g
inner join (
select Name, count(*) as Count
from Guys
group by Name
) gc on g.Name = gc.Name
In Oracle DB you can use
SELECT ID,NAME,PHONE,(Select COUNT(ID)From GUYS GROUP BY Name)
FROM GUYS ;
DECLARE #tbl table
(ID int,NAME varchar(20), PHONE int)
insert into #tbl
select
1 ,'John', 335
union all
select
2 ,'Harry', 444
union all
select
3 ,'James', 367
union all
select
4 ,'John', 742
union all
select
5 ,'John', 654
SELECT
ID
, Name
, Phone
, count(*) over(partition by Name)
FROM #tbl
ORDER BY ID
select id, name, phone,(select count(name) from users u1 where u1.name=u2.name) count from users u2
try
select column1, count(1) over ()
it should help