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.
Related
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
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.
I am trying to select a list of friends from a table, which works fine. However now I am trying to order the results by another table, which is the users online status/recent online activity.
The query I am trying to use is (The error is: Unknown column 'friendID' in 'where clause'):
SELECT
CASE WHEN f.userID=$session
THEN f.userID2 ELSE f.userID
END AS friendID
FROM friends f, usersStatus u
WHERE
(
f.userID=$session OR f.userID2=$session
)
AND friendID=u.userID
AND f.state='1'
ORDER BY u.periodicDate DESC, u.setStatus ASC
LIMIT 5
now the query that just returns the friends of a user is this:
SELECT
CASE WHEN f.userID=$session
THEN f.userID2 ELSE f.userID
END AS friendID
FROM friends f
WHERE
(
f.userID=$session OR f.userID2=$session
)
AND f.state='1'
LIMIT 5
The friendID should match usersStatus.userID, I then want to order them in the way in the example above. I would also like to add that if the friendID is not IN usersStatus table, it should automatically order those rows at the end.
u.periodicDate is just the most recent date set by the user, so this would indicate the last time the user was active, so I want the most recently active users to be first.
u.setStatus is set depending on what my script determines him to be, so it can be 1, 2, or 3. 1 being Online, 2 being Away, and 3 being Offline.
So like I said above, users that don't yet have a row in this table should automatically be given an offline status.
EDIT
I have got a query to work as I want it to, however It's extremely messy and must be hugely wasteful of its resources, as I have to GROUP BY friendID in order for it to return users only once. (because my friends database keeps every row, so if a person unfriends someone, it just creates a new row if they return friends etc.)
SELECT * FROM usersStatus,
(SELECT
CASE WHEN friends.userID=$session
THEN friends.userID2 ELSE friends.userID
END AS friendID
FROM friends, usersStatus
WHERE
(
friends.userID=$session OR friends.userID2=$session
)
AND friends.state='1'
) t
WHERE t.friendID=usersStatus.userID
GROUP BY t.friendID
ORDER BY usersStatus.periodicDate ASC, usersStatus.setStatus ASC
LIMIT 5
Instead of writing
AND friendID=u.userID
use the actual value for the alias:
AND CASE WHEN f.userID=$session
THEN f.userID2 ELSE f.userID
END = u.userID
I just learned you can stack SQL queries instead of running 4 different ones and combining the data. So I'm read tutorials and stuff but still can't figure this certain one out.
SELECT ID,
(SELECT firstname
FROM user
WHERE ID = fundraiser.user_ID) AS firstname,
(SELECT lastname
FROM user
WHERE ID = fundraiser.user_ID) AS lastname,
(SELECT org_fund_id
FROM fundraiser
WHERE ID = fundraiser.ID) AS org_fund_ID,
(SELECT ref_ID
FROM fundraiser
WHERE ID = fundraiser.ID) AS ref_ID
FROM fundraiser
WHERE 1
ORDER BY org_fund_ID ASC
Here's the basic setup for the database/tables being called:
[fundraiser] - (ID, ref_ID, user_ID, org_fund_ID) and
[user] - (firstname, lastname)
Basically, I want to pull all of the fields from "fundraiser" from the database but get the corresponding "user.firstname" and "user.lastname" where "fundraiser.user_ID" = "user.ID".
So it would come out something like this as a row:
fundraiser.ID, fundraiser.user_ID, fundraiser.ref_ID, user.firstname, user.lastname
I've tried like 30 different ways of writing this query and all have failed. The error I get is "#1242 - Subquery returns more than 1 row".
Not sure how I can give you more information so you can visualize what I'm talking about, but I will provide whatever data I can.
Thanks in advance.
to select ALL columns:
SELECT *
FROM fundraiser f
INNER JOIN user u
ON u.ID = f.user_ID
ORDER BY f.ord_fund_id ASC;
to select needed columns:
SELECT
u.firstname,
u.lastname,
f.org_fund_id,
f.ref_ID
FROM fundraiser f
INNER JOIN user u ON u.ID = f.user_ID
ORDER BY f.ord_fund_id ASC;
this should be, what you need. See this Wikipedia page.
I have a query that produces a result like this:
The data is sorted by date DESC, then by time DESC, but also the common 'markers_name' elements are chunked together. (They're only chunked together for each date).
To do this I get the list of MAX(time) values for every combination of (markers_id, date) in conditions, then join that list to the row set I am getting from the present query, and use the MAX(time) values for sorting:
SELECT
m.name AS markers_name,
c.time AS conditions_time,
c.date AS conditions_date,
s.name AS station_name
FROM markers m
INNER JOIN conditions c ON c.markers_id = m.id
INNER JOIN station s ON c.station_id = s.id
INNER JOIN (
SELECT
markers_id,
date,
MAX(time) AS time
FROM conditions
GROUP BY
markers_id,
date
) mx ON c.markers_id = mx.markers_id AND c.date = mx.date
ORDER BY
c.date DESC,
mx.time DESC,
m.name DESC,
c.time DESC
Now I need to restrict user access to some of the rows.
The conditions table has a 'private' column. If 'private' is set to 1 only some people are allowed to see the query row. The people that can see it include the person that created the conditions report and that person's contacts. The conditions table has a 'user_id', which contains the id of the person that created the conditions report. The contacts are obtained from a 'contacts' table which has two fields: 'user_id' and 'friend_id'. I have the user_id of the person requesting the information.
To restate another way, I need to do something like this:
Do the query above, but also check to see if c.private is set to one.
If c.private is set to one use c.user_id to get that user's 'friend_id's from the contacts table.
If the user_id of the person requesting the information matches c.user_id or any of c.user_id's friends return the query row. If not, don't return that row or return something that indicates that the row is private (I could have a markers_name with the name "Private" in the database, for example.
If anyone has any ideas I would love to hear them. I struggled with this all day yesterday. Thank you.
Possibly just adding a filter like this would be enough:
…
WHERE c.Private <> 1
OR c.user_id = #user_id
OR c.user_id IN (
SELECT friend_id
FROM contacts WHERE user_id = #user_id
)
where #user_id is the ID of the person requesting the info.
This would prohibit the private rows from appearing in the output where inappropriate.
If, however, you'd rather like them to be there but be marked as Private (for being dealt with later in the client or something like that), you could use that filter as a column in your SELECT clause:
SELECT
NOT (
c.Private <> 1
OR c.user_id = #user_id
OR c.user_id IN (
SELECT friend_id
FROM contacts WHERE user_id = #user_id
)
) AS IsPrivate,
…
FROM …
I don't have similar tables to play around with, so I'm writing this for you to try out, alright? Here is the WHERE clause for you to add to your query:
WHERE
-- all non-1 valued `private` columns are public, right? remove if not
c.private <> 1
-- all private but #user_id is entitled to view
OR (
c.private = 1
AND (
(c.user_id = #user_id) -- requestor is creator
OR (
EXISTS (
SELECT ct.friend_id
FROM contacts ct
WHERE ct.user_id = c.user_id
AND friend_id = #user_id
) -- requestor is a friend of the creator
)
)
)
Again, I wasn't able to test this.. I'll leave that up to you for now.. so please let me know how it goes. :)