How to do multiple counts / exists in one query? - mysql

I'm struggling to figure out how I can check for two things from one table without making two calls to mysql.
I have a Members table. I'd like to test whether a certain value exists in the MemberID column, and whether a certain value exists in the PhoneNumber column. MemberID and PhoneNumber are both indexed.
But there's something wrong with the syntax I'm trying. For example, each of
SELECT COUNT(1) AS IDExists FROM Members WHERE MemberID = '999999999999' LIMIT 1
and
SELECT COUNT(1) AS PhoneExists FROM Members WHERE PhoneNumber = '5555555555' LIMIT 1
works. Why can't they be combined, somehow, like
SELECT (COUNT(1) AS IDExists FROM Members WHERE MemberID = '999999999999' LIMIT 1), (COUNT(1) AS PhoneExists FROM Members WHERE PhoneNumber = '5555555555' LIMIT 1)
?
Or perhaps, since I only care whether the value occurs zero times, or at all, something like
SELECT EXISTS (SELECT 1 FROM Members WHERE MemberID = '999999999999')
?
But, unfortunately, there's something wrong with that syntax even for the case of one of my inquiries.
Any suggestions on how to accomplish this?

You want a conditional SUM
SQL FIDDLE
SELECT
SUM( CASE
WHEN PhoneNumber = '5555555555' THEN 1
ELSE 0
END) PhoneExists,
SUM( CASE
WHEN MemberID = '999999999999' THEN 1
ELSE 0
END) IDExists
FROM Members

You can combine two working query like this:
select a.IDExists, b.PhoneExists
from (SELECT COUNT(1) AS IDExists FROM Members WHERE MemberID = '999999999999' LIMIT 1) a,
(SELECT COUNT(1) AS PhoneExists FROM Members WHERE PhoneNumber = '5555555555' LIMIT 1) b
Though the query join two results with cartesian product, it is guaranteed that each result has only one row, it would not be a problem.
I'm not sure how big is the table and what kinds of index it has. If the table is big, then perhaps you have indexes on column MemberID and PhoneNumber respectively, then the above query will return the result quickly.

Related

MySQL finding the most similar user

Let's say I have a table that looks like this:
Mark - Green
Mark - Blue
Mark - Red
Adam - Yellow
Andrew - Red
Andrew - Green
And my objective is to compare the user "Mark" with all the other users in the database, to find out which other user he is most similar to. In this case, he would be most similar to Andrew (2/3 matches), and least similar to Adam (0/3) matches. After I've found out which user is most similar to Mark I would like to extract the entires that Andrew got but Mark doesn't.
Would this be possible in MySQL? I appreciate all help, thank you guys!
Edit: OVERWHELMED by all the good help! THANK you SO MUCH guys! I will be sure to check out all of your contributions!
The following query attempts to list all the users with the number of matches to Mark. It basically joins the table with Mark's entries and counts the common entries for all users.
SELECT ours.user, theirs.user, count(*) as `score`
FROM tableName as `theirs`, (SELECT *
FROM tableName
WHERE user = 'Mark') as `ours`
WHERE theirs.user != 'Mark' AND
theirs.color = ours.color
GROUP BY theirs.user
ORDER BY score DESC
The query, however, wouldn't work if there's duplicate data (i.e. one person picks the same color twice). But that shouldn't be a problem as you mention in the comments that it wouldn't occur.
The query can be modified to show the score for all users:
SELECT ours.user as `myUser`, theirs.user as `theirUser`, count(*) as `score`
FROM tableName as `ours`, tableName as `theirs`
WHERE theirs.user != ours.user AND
theirs.color = ours.color
GROUP BY ours.user, theirs.user
ORDER BY score DESC
Let Q be the above query that gives you the most similar user. Once you have that user, you can use it to show the distinct entries between them. Here's what we're trying to do:
SELECT *
FROM tableName as theirs
WHERE user = 'Andrew'
AND NOT EXISTS (SELECT 1
FROM tableName as ours
WHERE ours.user = 'Mark'
AND ours.color = theirs.color)
Replacing the inputs Andrew and Mark from Q:
SELECT similar.myUser, theirs.user, theirs.color
FROM tableName as theirs JOIN (Q) as similar
ON theirs.user = similar.theirUser
WHERE NOT EXISTS (SELECT 1
FROM tableName as ours
WHERE ours.user = similar.myUser
AND ours.color = theirs.color)
Here's the final query up and running. Hope this makes sense.
Use FULLTEXT INDEXES. And your query will be like:
SELECT * FROM user WHERE MATCH (name,color) AGAINST ('Mark blue');
Or simplest way, is using LIKE search
SELECT * FROM user WHERE name LIKE '%Mike%' OR color = 'blue'
You can choose which way more suitable for you
select
name,
sum(case when t2.cnt > t1.cnt then t1.cnt else t2.cnt end) matches
from (
select name, color, count(*) cnt
from table
where name <> 'Mark'
group by name, color
) t1 left join (
select color, count(*) cnt
from table
where name = 'Mark'
group by color
) t2 on t2.color = t1.color
group by name
order by matches desc
The derived table t1 contains the # of colors each user (except Mark) has, t2 contains the same for Mark. The tables are then left joined on the color and the smaller of the 2 counts is taken i.e. if Amy has 2 reds and Mark has 1 red, then 1 is taken as the number of matches. Finally group by name and return the largest sum of matches.
select match.name, count(*) as count
from table
join table as match
on match.name <> table.name
and table.name = 'mark'
and match.color = table.color
group by match.name
order by count(*) desc
Below query returns matching score between name and matching_name and the maximum score it could get, so that you know what % value your matching has.
This code counts duplicate values in color column as only one, so that if you have record Mark - Red twice, it will only count as 1.
select
foo.name, foo.matching_name, count(*) AS matching_score, goo.color_no AS max_score
from
(
select
distinct a.name, a.color, b.name AS matching_name
from
(
select name, color from yourtable
) a
left join yourtable b on a.color = b.color and a.name <> b.name
where b.name is not null
) foo
left join ( select name, count(distinct color) AS color_no from yourtable group by name ) goo
on foo.name = goo.name
group by foo.name, foo.matching_name
Attaching SQLFiddle to preview the output.
This should get you close. The complexity comes from the fact that you allow each user to pick each color multiple times and require that each otherwise-identical pair be matched in the other user you're comparing to. Therefore, we're really interested in knowing how many total color picks a user per color and how that number compares to the compared-to users' count for that same color.
First, we create a derived relation that does the simple math for us (counting up each users' number of picks by color):
CREATE VIEW UserColorCounts (User, Color, TimesSeen)
AS SELECT User, Color, COUNT(*) FROM YourTable GROUP BY User, Color
Second, we need some kind of relation that compares each color-count for the primary user to the color-counts of each secondary user:
CREATE VIEW UserColorMatches (User, OtherUser, Color, TimesSeen, TimesMatched)
AS SELECT P.User, S.User, P.Color, P.TimesSeen, LEAST(P.TimesSeen, S.TimesSeen)
FROM UserColorCounts P LEFT OUTER JOIN UserColorCounts S
ON P.Color = S.Color AND P.User <> S.User
Finally, we total up the color-counts for each primary user and compare against the matching color counts for each secondary user:
SELECT User, OtherUser, SUM(TimesMatched) AS Matched, SUM(TimesSeen) AS OutOf
FROM UserColorMatches WHERE OtherUser IS NOT NULL
GROUP BY User, OtherUser

Can I use a subquery to complete a where clause?

I have 2 tables joined by the membership.ID and memberDues.memberID. The membership table has a column, firstLast, which is unique for each row. If I have a value for firstLast, i should be able to then find the membership.ID and compare it to the memberDues.memberID. I am using the following query but it doesn't work:
SELECT * FROM memberDues, membership
WHERE memberID =
( SELECT ID FROM membership
WHERE firstLast = 'Jim Smith')
ORDER BY payDate DESC
LIMIT 1
The result gets the correct memberDues.memberID and the other data from its row but pulls an unrelated data set from the membership table where even the ID is wrong. What's wrong with the query?
You can join the two tables on their shared ID values and then you can select only those rows which have firstLast = 'Jim Smith'. I think you can achieve more efficiently what you want (avoiding one additional select) using the following query:
SELECT aa.*, bb.*
FROM memberDues AS aa
INNER JOIN membership AS bb
ON aa.memberID = bb.ID
WHERE bb.firstLast = 'Jim Smith'
ORDER BY aa.payDate DESC
LIMIT 1;
You should use IN instead of equal sign:
SELECT * FROM memberDues, membership WHERE memberID IN
(SELECT ID FROM membership WHERE firstLast = 'Jim Smith')
ORDER BY payDate DESC LIMIT 1
What's wrong with the query?
Your query joins 2 tables without specifying a condition to relate them.
For a fast start on your question, I'd try to specify
membership.ID = memberDues.memberID in the where clause.
My personal advice is to use a LEFT JOIN using that condition in the ON clause. But it's more advanced SQL coding, and choices depends on the structure and the needs of this particular application.

MySQL search users, order friends above others

I have a feature to search for users on my website,
I also have a feature for friends on my site.
I have a query to search for the correct users on my site, and
I have a query that determines the users that a user is friends with, they both work as they should,
so what i need assistance with is combining the two, to order friends before other users.
Search query:
SELECT userID, fname, lname,
FROM names
WHERE (
fname LIKE '".$term."%'
OR lname LIKE '".$term."%'
OR CONCAT(fname, ' ', lname) LIKE '".$term."%'
)
AND userID!=$session
ORDER BY fname ASC, lname ASC
LIMIT 10
Friends query:
SELECT CASE WHEN userID=$session
THEN userID2
ELSE userID
END AS friendID
FROM friends
WHERE (
userID=$session
OR userID2=$session
)
AND state='1'
so, a statement that would compare the two should be something like, IF names.userID=friends.friendID
Now I found a users suggestion for a similar issue on stackoverflow that seems like it would be a simple way of doing this, however I'm not entirely sure how to add it with my particular friends query, as it's more of a complex query.
SELECT
posting.id,
IF(a.friend_id = b.criteria, 1, 0) AS is_friend
...
ORDER BY is_friend DESC, posting.id DESC
Use an outer join (left join) to join the user list returned by your first query with the friend list that your second query produces. Now you'll be able to sort on the fact whether the second row set has a match: if it does, the corresponding row should go first.
This is an example of how you could implement the above:
SELECT
n.userID,
n.fname,
n.lname
FROM names n
LEFT JOIN (
SELECT CASE WHEN userID=$session
THEN userID2
ELSE userID
END AS friendID
FROM friends
WHERE (
userID=$session
OR userID2=$session
)
AND state='1'
) f
ON n.userID = f.friendID
WHERE (
n.fname LIKE '".$term."%'
OR n.lname LIKE '".$term."%'
OR CONCAT(n.fname, ' ', n.lname) LIKE '".$term."%'
)
AND n.userID!=$session
ORDER BY (f.friendID IS NULL), n.fname ASC, n.lname ASC
LIMIT 10
The first sorting criterion, (f.friendID IS NULL), evaluates to false if there's a matching f row, i.e. if the user is a friend, and to true otherwise. Consequently, since false sorts before true, the friends will be sorted before non-friends.

Query to count number of followers

I have a MySQL table, in which I will be storing the email_id of a user along with the user's followers' email_ids. Its columns are:
ID, USER_EMAIL, FOLLWING_EMAIL
Now while the user is logged in to his account, I want to calculate:
The number of people he is following
The number of followers he has
Calculating the number of people he's following is simple:
select count(*) from list where user_email = <logged-in email>
But how can I calculate how many people are following him in just one query?
SELECT
COUNT(CASE WHEN user_email = '<email>' THEN 1 ELSE NULL END) AS following_cnt,
COUNT(CASE WHEN following_email = '<email>' THEN 1 ELSE NULL END) AS follower_cnt
FROM
list
WHERE
'<email>' IN (user_email, following_email)
I think you just need to change the field in your WHERE clause:
SELECT count(*) FROM list WHERE following_email = '...'
To count both in one query, use subselects:
SELECT
(SELECT count(*) FROM list WHERE user_email = '...') AS following,
(SELECT count(*) FROM list WHERE following_email = '...') AS followers
is there any optimize to store the data, to achieve this functionality?
Yes. Add an index on USER_EMAIL and another on FOLLOWING_EMAIL.
So is FOLLOWING_EMAIL the email address of a person this user is following? Or an index into another table? If the former, then:
SELECT COUNT(*) from list where FOLLOWING_EMAIL="test#test.com"

MySQL evaluate case with subquery

I am trying to create a custom sort that involves the count of some records in another table. For example, if one record has no records associated with it in the other table, it should appear higher in the sort than if it had one or more records. Here's what I have so far:
SELECT People.*, Organizations.Name AS Organization_Name,
(CASE
WHEN Sent IS NULL AND COUNT(SELECT * FROM Graphics WHERE People.Organization_ID = Graphics.Organization_ID) = 0 THEN 0
ELSE 1
END) AS Status
FROM People
LEFT JOIN Organizations ON Organizations.ID = People.Organization_ID
ORDER BY Status ASC
The subquery within the COUNT is not working. What is the correct way to do something like this?
Update: I moved the case statement into the order by clause and added a join:
SELECT People.*, Organizations.Name AS Organization_Name
FROM People
LEFT JOIN Organizations ON Organizations.ID = People.Organization_ID
LEFT JOIN Graphics ON Graphics.Organization_ID = People.Organization_ID
GROUP BY People.ID
ORDER BY
CASE
WHEN Sent IS NULL AND Graphics.ID IS NULL THEN 0
ELSE 1
END ASC
So if if the People record does not have any graphics, Graphics.ID will be null. This achieves the immediate need.
If what you tried does not work, it can be done by joining against a subquery, and placing the CASE expression into ORDER BY as well:
SELECT
People.*,
orgcount.num
FROM People JOIN (
SELECT Organization_ID, COUNT(*) AS num FROM Graphics GROUP BY Organization_ID
) orgcount ON People.Organization_ID = orgcount.num
ORDER BY
CASE WHEN Sent IS NULL AND orgcount.num = 0 THEN 0 ELSE 1 END,
orgcount.num DESC
You could use an outer join to the Graphics table to get the data needed for your sort.
Since I don't know your schema, I made an assumption that the People table has a primary key column called ID. If the PK column has a different name, you should substitute that in the GROUP BY clause.
Something like this should work for you:
SELECT People.*, (count(Distinct Graphics.Organization_ID) > 0) as Status
FROM People
LEFT OUTER JOIN Graphics ON People.Organization_ID = Graphics.Organization_ID
GROUP BY People.ID
ORDER BY Status ASC
Fairly straight forward with a LEFT JOIN provided you have some kind of primary key in the People table to GROUP on;
SELECT p.*, sent IS NOT NULL or COUNT(g.Organization_ID) Status
FROM People p LEFT JOIN Graphics g ON g.Organization_ID = p.Organization_ID
GROUP BY p.primary_key
ORDER BY Status
Demo here.