Select distinct record from mapping table with condition - mysql

In my MySQL database I have these tables:
I want to select count of users who only own birds and no other pet.
So far I've came up with this:
SELECT COUNT(DISTINCT(user_id)) FROM users_pets_map WHERE pet_id IN (SELECT id FROM pets WHERE animal = 'bird')
but it doesn't satisfy the requirement of not owning other animals.

You can do aggregation :
select m.user_id, count(*)
from user_pets_map m inner join
pets p
on p.id = m.pet_id
group by m.user_id
having sum( p.animal <> 'bird' ) = 0;
In other way, you can also do :
select m.user_id, count(*)
from user_pets_map m inner join
pets p
on p.id = m.pet_id
group by m.user_id
having min(p.animal) = max(p.animal) and min(p.animal) = 'bird';
EDIT : If you want only Users count then you can do :
select count(distinct m.user_id)
from user_pets_map m
where not exists (select 1 from user_pets_map m1 where m1.user_id = m.user_id and m1.pet_id <> 3);

You can modify your query as below:
SELECT COUNT(DISTINCT(user_id)) FROM users_pets_map WHERE pet_id IN (SELECT id
FROM pets WHERE animal = 'bird') AND user_id NOT IN (SELECT user_id FROM
users_pets_map WHERE pet_id IN (SELECT id FROM pets WHERE animal <> 'bird'))
The last sub-query will fetch the pet_id who are not birds, the query outside it will fetch users who have animal other than birds. Finally combined your current query it will fetch you the users who does not have any other animals as well as have bird. Although the above query is not the best possible solution in terms of time complexity, but it's one of many solutions as well as easier to understand.

You can use GROUP BY AND HAVING
SELECT COUNT(DISTINCT(user_id)) FROM users_pets_map
WHERE pet_id IN (SELECT id FROM pets WHERE animal = 'bird')
GROUP BY pet_id HAVING COUNT(distinct pet_id)=1

Related

SQL Server - count the number of living grandparents

I'm homelearning databases and currently trying to solve the following problem:
Select id and name of people who are grandchildren. Also select for
each person the number of their still living grandparents.
My table is looking the following way:
id, name, date_of_birth, date_of_death (is NULL when the person is
alive), gender, father_id, mother_id
I was able to solve the part with selecting the id and name of people who are grandchildren the following way:
SELECT b.name, b.id
FROM persons a
JOIN persons b ON c.father_id = a.id or a.mother_id = a.id
JOIN persons c on p.father_id = c.id
WHERE b.father_id = a.id or b.mother_id = a.id;
However, I am unable to solve the part with the number of still living grandparents for each person.
Could you please help me?
Thanks
This is a question of joining the tables. I think you are all confused by the table aliases on the self-joins. So give them meaningful aliases:
p for the person
pp for the person's parents
gp for the person's grandparents
Then, once you have the JOINs correct, the rest is mostly aggregation:
SELECT p.name, p.id,
SUM(CASE WHEN gp.date_of_death IS NULL THEN 1 ELSE 0 END)
FROM persons p JOIN -- persons
persons pp -- parents
ON pp.id IN (p.father_id, p.mother_id) JOIN
persons gp
ON gp.id IN (pp.father_id, pp.mother_id)
GROUP BY p.name, p.id;
Note the conditional sum, which includes the date of death.
You need to join 3 copies of the table, group by person and count conditionally the number of living grandparents:
SELECT p.id, p.name,
count(case when p2.date_of_death is null then 1 end) living_grandparents
FROM persons p
JOIN persons p1 ON p1.id IN (p.father_id, p.mother_id)
JOIN persons p2 on p2.id IN (p1.father_id, p1.mother_id)
GROUP BY p.id, p.name
For MySql the condition could be simplified to:
sum(p2.date_of_death is null) living_grandparents
Check out the SQL Fiddle for the solution :)
http://sqlfiddle.com/#!9/441ad0/16
select
(
select
count(*)
from
people
where
(
id = p.fatherId
or id = p.motherId
)
and (
fatherId in (
select
id
from
people
where
dod is null
)
or motherId in (
select
id
from
people
where
dod is null
)
)
) as mycount,
p.name,
p.id,
p.fatherId,
p.motherId
FROM
people p
WHERE
p.fatherId in (
SELECT
id
from
people
where
id = p.fatherId
and fatherId in (
SELECT
id
from
people
where
1 = 1
)
)
OR p.motherId in (
SELECT
id
from
people
where
id = p.motherId
and motherId in (
SELECT
id
from
people
where
1 = 1
)
)

How to fix query results not showing up?

I have a MySQL query which I want to execute to see who is the employee with the best skill X in a company I work for. To do this I randomly pick a company from my cv_profile (skill_cv_test) and find all users who work there for the same employer. And then I randomly choose a skill I have.
The result should either be zero or a list.
But when testing with PHPMyAdmin I get results where I don't see any row, but the status says there is at least one row.
Here's an example of the message I get: https://imgur.com/bVMH716
I have been trying different structures, even "walling" the query with another query, different joins.
SELECT
DISTINCT(sv.usr_id),
u.first_name AS fn,
u.last_name AS ln,
c.name AS company,
s.name AS skill
FROM
(
SELECT
MAX(last_change) as date,
id,
usr_id,
skill_id
FROM skill_valuations
GROUP BY usr_id, skill_id
ORDER BY date
) sv
LEFT JOIN skill_valuations skv ON skv.last_change = sv.date
INNER JOIN
(
SELECT
DISTINCT(skct.comp_id),
skct.usr_id AS usr_id,
skct.category
FROM skill_cv_test skct
WHERE skct.end_date IS NULL AND skct.comp_id IN (SELECT comp_id FROM (SELECT comp_id FROM skill_cv_test WHERE usr_id = 1 ORDER BY RAND() LIMIT 1) x)
) uqv ON uqv.usr_id = sv.usr_id
INNER JOIN
(
SELECT skill_id
FROM usr_skills
WHERE usr_id = $uid
ORDER BY RAND()
LIMIT 1
) usq ON usq.skill_id = sv.skill_id
LEFT JOIN companies c ON c.id = uqv.comp_id
LEFT JOIN skills s ON s.id = sv.skill_id
LEFT JOIN users u ON u.id = sv.usr_id
As mentioned before, I expect either no results or a result of at least one row.

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!

Mysql count and return just one row of data

I need to count the amount of users that have have answered all of those 3 profile_options (so they have at least 3 records in the profile_answers table).
SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3
The problem is that this query is return a table with rows for each user and how many they answered (in this case always 3). What I need is to return just one row that has the total number of users (so the sum of all rows of this example)
I know how to do it with another subquery but the problem is that I am running into "Mysql::Error: Too high level of nesting for select"
Is there a way to do this without the extra subquery?
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) sum_sub
Please give this query a shoot
SELECT COUNT(DISTINCT(u.id)) AS users_count
FROM users AS u
INNER JOIN (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a ON a.user_id = u.id
If you have lots of data in your tables, you will get a better/faster performance by using temporary tables like so
CREATE TEMPORARY TABLE a (KEY(user_id)) ENGINE = MEMORY
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3;
Then your final query will look like this
SELECT COUNT(DISTINCT(u.id)) as users_count
FROM a
INNER JOIN on a.user_id = u.id
Unless there is a need to join the users table you can go with this
SELECT COUNT(*) AS users_count
FROM (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a
Should you need another solution, please consider providing us you EXPLAIN EXTENDED for the query and the table definitions along with a better problem description.
I hope this helps
You can give the queries a name using the AS clause. See the updated query below.
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) as users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) as sum_sub
You should not group by on a field not present in select statement.
select id, count(*) from users group by id is fine
select count(id) from users group by id is NOT
Regarding your query I think the link to user table is not necessary. Just using foreign key should be fine.
Try this one:
select count(*) from
(SELECT users_id count(*) as cnt
FROM profile_answers
INNER JOIN users ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
group by users_id
having count(*) >3)

Insert value into subquery

I'm trying to insert the value '1' into column 'isTransfer' of every result of an subquery, but it's not working. This is the query where I select the rows:
select r.*
from players r
inner join (
select name, rating, max(id) id
from players group by name, rating
having count(distinct club) > 1
)
q on r.name = q.name and r.rating = q.rating and r.id = q.id
This is what I'm trying to do:
INSERT INTO 'isTransfer' VALUES '1' WHERE
(select r.*
from players r
inner join (
select name, rating, max(id) id
from players group by name, rating
having count(distinct club) > 1
)
q on r.name = q.name and r.rating = q.rating and r.id = q.id)
For this task, you need to do an UPDATE query. Also, you cannot use the WHERE clause like that, you will get an error. Instead, change the where clause to look where the primary key is returned by the subquery. It would look something like this:
UPDATE myTable
SET isTransfer = 1
WHERE primaryKey IN [mySubquery];
You need to make sure that the only column in your SELECT of the subquery is the primary key, otherwise you will get an invalid operand count error.
In regards to your query in the comments, the JOIN is not necessary. Instead, just get the distinct id values from the subquery like this:
SELECT DISTINCT id
FROM(
SELECT name, rating, MAX(id) AS id
FROM players
GROUP BY name, rating
HAVING COUNT(DISTINCT club) > 1) q
Then, but that query as your IN operand.
Assuming the id is unique in the players table:
update players r inner join
(select name, rating, max(id) as id
from players p
group by name, rating
having count(distinct club) > 1
) nr
on r.id = nr.id
set isTransfer = 1;