MySQL - Get count of users that have a "user-contact" - mysql

Consider the three tables in the following Schema SQL Fiddle.
This mini-model represents a simple example of a database I'm working on .
The Users table has 4 attributes, the user_id (primary key auto_increment), the country_id which is a foreign key referencing the Country table, the user_msisdn which is the phone number of the user, and the username.
The Contacts table has 3 attributes, the contact_id (primary key auto_increment), the user_id which is a foreign key referencing the Users table, and the contact_msisdn which is the user contact's phone numbers (the phone numbers on your phone contacts list).
The relation between the Users table and the Contacts table is many-to-many, a user can have many contacts, and a contact can be found in any user's contacts list.
Requirements:
For each country, get the count of users that have at least one "user-contact", where a "user-contact" is a contact that is a user, and the count of users that don't have any "user-contact".
E.g., For country = 'Sweden' (country_id = 3), the user_id = 3 has two contacts in the Contacts table that are considered "user-contact" with (msisdn = '+220011223344' & '+224433221100'). So the query results that I want: Sweden has 1 user (user_id = 3) that has at least one "user-contact" and zero users that have no "user-contact", and so on for each country.

Try this:
SELECT c.country_id,
COUNT(DISTINCT CASE WHEN u2.user_id IS NOT NULL THEN u.user_id END) as has_contact_that_is_user ,
COUNT(distinct CASE WHEN u2.user_id is null AND u.user_id IS NOT NULL THEN u.user_id END) as has_no_contact_that_is_user
FROM Country c
LEFT JOIN users u
ON(c.country_id = u.country_id)
LEFT JOIN contacts co
ON(co.user_id = u.user_id)
LEFT JOIN Users u2
ON(u2.user_msisdn = co.contact_msisdn)
GROUP BY c.country_id
Fiddle

Related

How to find percentage within a group in sql that has inner join as subquery?

I have 3 tables like this
create table Users (id serial primary key, country varchar(100) not null);
create table tweets(id serial primary key, user_id int, text varchar(100) not null, CONSTRAINT FK_TWEETSUSERS FOREIGN KEY (user_id)
REFERENCES Users(id));
create table Logins(user_id int, client varchar(100), CONSTRAINT FK_LOGIN_USERS FOREIGN KEY (user_id)
REFERENCES Users(id));
insert into Users
values
(1,'Japan'),
(2, 'Moroco'),
(3,'Japan'),
(4,'India'),
(5,'India'),
(6,'Japan'),
(7,'Moroco'),
(8,'China');
insert into tweets
values
(733,1,'I love #food'),
(734,1,'I love food'),
(735,2,'I love #food'),
(736,5,'I love food'),
(737,6,'I love #food'),
(738,3,'I love #food'),
(739,8,'I love #food');
insert into Logins
values
(1,'mobile-ios'),
(2,'mobile-ios'),
(3,'mobile-ios'),
(4,'web'),
(8,'mobile-ios');
I need to find percentage of users from each country whose users have used '#food' in their tweets and the other condition is that user should have logged in using 'mobile' device
I have written the following query so far -
select t.country, count(t.country) as tweet_users
from
(select Mobile_User_Tweets.user_id, U.country from Users as U
inner join
(select distinct user_id from tweets
where text like '%#food%'
and user_id in (select distinct user_id
from Logins
where client like '%mobile-%')) as Mobile_User_Tweets
on U.id = Mobile_User_Tweets.user_id) as t
group by t.country ;
This gives the number of users from a country that have user #food in their tweets
Result below -
country tweet_users
Japan 2
Moroco 1
China 1
I want the following result -
country tweet_users
Japan 66.67 -------------> (2 out of 3 users from Japan)
Moroco 50 -------------> (1 out of 2 users from Moroco)
China 100 -------------> (1 out of 1 user from China)
I tried number of different queries to find the percentage but haven't been able to get the result?
Can some one help me with this?
One way to achieve the results you want is to check in a derived table whether a user has made any tweets about #food; then you can LEFT JOIN that table to Users and Logins to determine the average number of users from each country that have logged in from mobile and tweeted about food:
SELECT u.country,
AVG(COALESCE(t.tfood, 0) AND COALESCE(l.client, '') LIKE '%mobile-%') * 100 AS tweet_users
FROM Users u
LEFT JOIN Logins l ON l.user_id = u.id
LEFT JOIN (
SELECT user_id, MAX(text LIKE '%#food%') AS tfood
FROM tweets
GROUP BY user_id
) t ON t.user_id = u.id
GROUP BY u.country
Output:
country tweet_users
China 100.0000
India 0.0000
Japan 66.6667
Moroco 50.0000
If you don't want countries with no users that meet the criteria, just add HAVING tweet_users > 0 to the end:
SELECT u.country,
AVG(COALESCE(t.tfood, 0) AND COALESCE(l.client, '') LIKE '%mobile-%') * 100 AS tweet_users
FROM Users u
LEFT JOIN Logins l ON l.user_id = u.id
LEFT JOIN (
SELECT user_id, MAX(text LIKE '%#food%') AS tfood
FROM tweets
GROUP BY user_id
) t ON t.user_id = u.id
GROUP BY u.country
HAVING tweet_users > 0
Demo on dbfiddle
Note this code takes advantage of the fact that in a numeric context, MySQL treats boolean expressions as 1 (true) or 0 (false).
Note that if a user might have multiple entries in the Logins table, you need to make a derived table from that too:
SELECT u.country,
AVG(COALESCE(t.tfood, 0) AND COALESCE(l.mclient, 0)) * 100 AS tweet_users
FROM Users u
LEFT JOIN (
SELECT user_id, MAX(client LIKE '%mobile-%') AS mclient
FROM Logins
GROUP BY user_id
) l ON l.user_id = u.id
LEFT JOIN (
SELECT user_id, MAX(text LIKE '%#food%') AS tfood
FROM tweets
GROUP BY user_id
) t ON t.user_id = u.id
GROUP BY u.country
Demo on dbfiddle

How to make many to many relationship table efficient for SELECT queries in MySQL?

A user can have many interests.
An interest can be interested to many users.
My database looks like that:
Users table:
id - primary key,
name,
email,
Interests table:
id - primary key,
title
Users_To_Interests table:
id - primary key,
user_id(id from users table)
interest_id(id from interests table)
How can I improve Users_To_Interests table to be able to pick all users who have the same interest efficiently? user_id and interest_id columns don't have indexes or keys. If I need to add them, please show me how can I make that.
Edition 1: For example,
user1 has interests : interest1, interest2, interest3;
user2 has interests : interest3, interest4;
user3 has interests : interest3, interest5;
user4 has interests : interest4;
If I want to get all users who have interest1, I should receive user1;
If I want to get all users who have interest2, I should receive user1;
If I want to get all users who have interest3, I should receive user1, user2, user3;
The query to get users for interest #3 is very simple (use IN or EXISTS). With an index on users_to_interests(interest_id, user_id) this should be very fast.
select *
from users
where id in (select user_id from users_to_interests where interest_id = 3);
Here is a query which would find all users having interests 1 and 2. It should be clear how to generalize this to any number of interets. The subquery aggregates over users and finds those users who have the interests we want. We then join this back to the Users table to get the full information for each user.
SELECT
t1.*
FROM Users t1
INNER JOIN
(
SELECT ui.user_id
FROM Users_To_Interests ui
INNER JOIN Interests i
ON ui.interest_id = i.id
WHERE i.title IN ('interest2', 'interest3')
GROUP BY ui.user_id
HAVING COUNT(DISTINCT i.id) = 2
) t2
ON t1.id = t2.user_id;

Mysql: count and group unique emails in associated tables by polymorphic join

The relevant part of my schema (Mysql 5.6.24) is thus:
table: training_event_invitees
registered (tinyint)
invitee_id (id)
invitee_type (varchar)
table: users
id (integer)
email (varchar)
school_id (integer)
table: contacts
id (integer)
email (varchar)
school_id (integer)
table: schools
id (integer)
email (varchar)
I want to try to do the following: get all the training_event_invitees that have registered set to 1, get the associated school, user and contact records, and then group them by school_id, and return the school id and the count of unique email addresses from that school.
training_event_invitees has a two-column foreign key, using invitee_id and invitee_type: invitee_type would be either "School", "User" or "Contact", and references the id field from the corresponding table.
So, algorithmically, it's something like
- get all the registered training_event_invitees
- get all of the associated user, contact and school records
- group these by users.school_id, contacts.school_id or schools.id
- count the number of distinct emails in each group
So, it should return an array like
[
[1234, 6],
[3407, 2]
]
where 1234 and 3407 are values of school_id and 6 and 2 are the count of distinct emails.
I can break this down into a few steps, but there must be a one-hit way to do it. Can anyone help?
One method is to combine the two tables using left join, and then doing the aggregation:
select coalesce(u.school_id, c.school_id) as school_id,
count(distinct coalesce(u.email, c.email)) as num_emails
from training_event_invitees tei left join
users u
on u.id = tei.invitee_id and tei.invitee_type = 'user' left join
contacts c
on c.id = tei.invitee_id and tei.invitee_type = 'contact'
where tei.registered = 1
group by coalesce(u.school_id, c.school_id);
EDIT:
To include the school, follow the same logic:
select coalesce(u.school_id, c.school_id, s.id) as school_id,
count(distinct coalesce(u.email, c.email, s.email)) as num_emails
from training_event_invitees tei left join
users u
on u.id = tei.invitee_id and tei.invitee_type = 'user' left join
contacts c
on c.id = tei.invitee_id and tei.invitee_type = 'contact' left join
schools s
on s.id = tei.invitee_id and tei.invitee_type = 'school'
where tei.registered = 1
group by coalesce(u.school_id, c.school_id, s.id);

Selecting primary keys that do not have foreign keys in another table

For simplification, I have two tables related with one to many using a foreign key, for example:
Users table:
id
name
Actions table:
id
user_id
one user may have many actions or not. I need an sql select that returns users ids that don't have a user_id value in the actions table.
Users Table:
id name
1 John
2 Smith
3 Alice
Actions Table:
id user_id
1 3
2 1
So I need an sql query that returns the user id 2 (Smith) because the foreign key values don't include the id 2
I tried the following SQL, but it returns all users ids:
SELECT users.id from users left join actions on actions.user_id is null
select u.id
from users u
left outer join actions a on a.user_id = u.id
where a.user_id is null
Optimized version would be:
SELECT u.id
FROM users u
LEFT JOIN actions a
ON a.user_id = u.id
AND ISNULL(a.user_id)
SELECT u.id
FROM users u
LEFT JOIN actions a
ON a.user_id = u.id
WHERE a.user_id IS NULL

A complex data not sure how to explain it

Hi I have a table called Users and a table called friends, friends table have two data types UserID and FriendID, (foreign key of both data types to primary key of the Users table),
I need to give an ID and find a list of that persons friends'name, I am not sure if I have designed the tables wrongly or I should rewrite the query.
my query is as following, (so far it just shows the details of first matched person)
SELECT Users.Name
FROM Users
WHERE Users.ID = SELECT Friends.UserID
FROM Friends,Users
WHERE Users.ID = (Select Users.ID
From Users
WHERE Users.Username = 'John')
Try this:
SELECT Users.Name FROM Users WHERE Users.ID IN -- Get names that belongt to ID's
(SELECT FriendID FROM Friends WHERE UserID = -- All ID's of the Friends of
(SELECT UserID FROM Users WHERE Name = 'John')) -- Johns ID
I've solved it by changing the first = to IN
is this you want to achieve ??
User
UserID (PK)
Name
Friend
FriendID (PK)
UserID (FK)
select User.Name from User u join Friend f on f.UserID = u.UserID where Name = 'John'