Mysql, search in multiple tables - mysql

I'm working on an async game, but I'm totally new to MySQL and PHP. I have two tables, tb_users and tb_matches. The first one stores all users and their status (for example, a value of 0 in the 'status' column means the player is in stand-by, while a value of 1 means the player is waiting for an opponent to play a match).
The tb_matches table stores all the matches, both active and inactive (completed) ones. There are two columns in this table with player IDs.
What I am trying to do is to perform a search for an available opponent, which must fulfill these requirements:
1) it must have a status of 1 in the tb_users table
2) it must not already have an active match with the user searching for a new opponent
I've tried with:
SELECT *
FROM tb_users
JOIN tb_matches
WHERE tb_users.status = "1"
AND tb_matches.player1 != '".$username."'
AND tb_matches.player2 != '".$username."'
..but it's not giving me any result. Also, it should take into account that in the tb_matches table, there could be no matches at all.
Any help? Also, is there a better way to accomplish this task?

Consider the following alternate table structure:
tb_users
---------
id
name
seeking
tb_user_matches
---------------
match_id
player_id
tb_matches
----------
id
started
ended
status
Adding a third (many-to-many) table to store which players are involved in which match allows simpler queries, see below.
Change "status" in tb_users to "seeking" so a boolean data type makes sense.
Don't store player names in tb_matches, store their ID. (Allows name changes without breaking links (Normalization).)
Consider adding datetime fields to tb_matches to store the match start and end date; it might be useful in the future.
The "status" column in tb_matches can indicate three match statuses: waiting for more players, in progress, or completed (see below).
With this structure, you can use the following query to find players that are:
Searching for a match (seeking = 1)
Not already in a match that is waiting for players
SELECT u.id, u.name
FROM tb_users u
LEFT JOIN tb_user_matches um
JOIN tb_matches m
ON um.match_id = m.id AND m.status = 0
ON u.id = um.player_id
WHERE u.seeking = 1
AND um.match_id IS NULL;
I am proposing tb_matches.status could be "0" for inactive (waiting for players), "1" for active, and "2" for completed.
This does not necessarily check if the player is already engaged in an active match. Perhaps your game allows players to participate in multiple simultaneous matches? If not, you can alter the query to exclude matches that are active as well (change m.status = 0 to m.status != 2).
Edit:
To explain the query in English (as best I can):
Select user id and name from the users table. Include matches that the user is involved where the match status is "0" (waiting). (The LEFT JOIN makes it so if the user is not involved in any matches with status "0", the match_id will be empty (null).)
Now, from this set of data, only show users that are seeking (u.seeking = 1), and aren't in a waiting match (um.match_id IS NULL).

Try this (note that you will have to substitute 'username' for the actual user name)
SELECT *
FROM tb_users
WHERE Username != 'username'
AND status = 1
AND NOT EXISTS (SELECT *
FROM tb_matches
WHERE (player1 = 'username' OR player2 = 'username')
AND Status = 'inactive')

SELECT *
FROM
`tb_users`
WHERE
`tb_users`.`status` = "1"
AND
`tb_users`.`name` NOT IN (
SELECT `player1` FROM `tb_matches` WHERE `player2` = 'username'
UNION
SELECT `player2` FROM `tb_matches` WHERE `player1` = 'username'
UNION
SELECT 'username'
)

Related

What's better checking for record with active status or taking the status field and then checking

I am having a bit confusion as to use which method. My requirement is like this:
Suppose there is a table named user_master. And it has the follwing fields:
user_id user_name pwd status.
The status field is 'A' when Active and 'I' when inactive
What I want is to check if a user is active on the basis of the userid. So whats better
A) SELECT <any_one_field> FROM user_master WHERE user_id = 'some_user_id' AND status = 'A';
and then check for more than 0 rows
OR
B) SELECT status FROM user_master WHERE user_id = 'some_user_id';
and then compare the status field value for 'A' Active or 'I' Inactive.
Whats better according to below,
In A, there is only SQL and in B, there is SQL + the Programming language stuff.
Thanks in Advance!
For point A, it already filtered base on userid and status criteria, in sql execution time it more faster but when you don't know the status from user you will choose option B, it will return userid and status that you want to process in next business flow.
It gives you records those who are active
SELECT * FROM user_master WHERE status ='A';
This query returns if your user_id is active
SELECT <any_one_field> FROM user_master WHERE user_id = 'some_user_id' AND status = 'A';
This query returns the particluar user_id status
SELECT status FROM user_master WHERE user_id = 'some_user_id';

regular expression to extract json values in mysql field

I have a "users" table with an "assignments" field that has a list of course IDs and when then are assigned and whether they are required or optional in one json-like string (missing the top-level braces)
"BUS1077":{"startDate":"2013-09-16","hasPrerequisite":"","list":"required"},
"CMP1042":{"startDate":"2013-09-16","hasPrerequisite":"","list":"optional"},
"CMP1108":{"startDate":"2013-09-16","hasPrerequisite":"","list":"required"}
I have a another table, called "progress" that lists the course ids, like BUS1078, and whether they are completed or not.
I need a query to select the users who have completed all their required courses.
somthing like:
SELECT userid FROM users
where (count([ids from users.assignments where list:"required"] as courseid)
=count([extracted ids] joined using( courseid) where "complete"=1))
so there are just two tables
users (userid,assignments)
progress (id,userid,courseid,complete)
in the end I want to have selected the userids where each REQUIRED course is complete
(note, the database itself is much more complex, but this represents the gist of the problem)
As of MySQL 5.1 you can do this with built-in functions of common_schema you can use for this purpose. I haven't used it myself but I've found a nice blog about how you can parse JSON stored data and do something usefull with it.
The blog: http://mechanics.flite.com/blog/2013/04/08/json-parsing-in-mysql-using-common-schema/
I'm not familiar with the RegEx implementation in MySQL, but this basic approach should work:
SELECT userid FROM users WHERE NOT EXISTS(
SELECT NULL FROM assignments WHERE NOT EXISTS(
SELECT NULL FROM progress WHERE
progress.userid = users.userid
AND REGEXMATCH(
assignments.assignment,
'(^|,)"' + progress.courseid + '":.*?"list":"required"\}') >= 0
)
)
)
This should find all users where there is not a required assignment that the user hasn't completed.
Given the course IDs and the word "required" are unlikely to appear out of context, the regular expression itself could likely be much more naive, such as:
'"' + progress.courseid + '"[^}]+"required"'
I don't know about MySQL's current limitations when it comes to correlated subqueries, but the same thing could be accomplished with joins. Using EXISTS should be preferred over COUNT, since counting requires aggregation across the entire dataset rather than allowing a short-cut on the first non-match found.
if your courseid is always 7 characters long and the list in assignments field can have up to maximum of 10 courses
you can use this sqlFiddle
SELECT U.userId
FROM users U
WHERE NOT EXISTS
(SELECT 1 FROM
(SELECT users.userid,courseName,
(Assignments REGEXP CONCAT('"',courseName,'"[^}]+(:"required"})'))as Required,
Assignments,
courseid,complete
FROM
(SELECT userid,courseName FROM
(SELECT userid,SUBSTRING_INDEX(SUBSTRING_INDEX(assignments,'":{"startDate',course.num),'"',-1) as courseName
FROM users,(SELECT 1 as num
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10)course
)T WHERE LENGTH(courseName)=7
)Courses
INNER JOIN users ON users.userid = Courses.userid
LEFT JOIN progress ON users.userid = progress.userid
AND Courses.courseName = progress.courseId
AND progress.complete = 1
)AllCourses
WHERE AllCourses.userId = U.userId
AND AllCourses.Required = 1
AND Complete IS NULL
)
What the query does is it grabs the courseName(s) from assignment fields and see if it's required and sets required flag, then LEFT JOIN with progress and we have the Required column and Complete is NULL when the course doesn't exist in progress or when complete is not 1.
We then select user id WHERE there does not EXISTS (a record in their Courses where Required = 1 AND Complete IS NULL)
In the fiddle, I have user 2 having only completed an optional course. So userId 2 is not returned.
You can just run the inner select for AllCourses subquery and see the data of all the courses for all users and whether they completed a course that is required or not.

MySQL - tell if column _all_ has same value

I'm trying to write a query like
if (select count(*) from Users where fkId=5000 and status='r') =
(select count(*) from Users where fkId=5000) then ..
in just one query.
What this means is, if all the rows that have fkId=5000 also have status=r, then do something.
There can be any number of rows with fkId=5000, and any fraction of those rows could have status=r, status=k, status=l, status=a etc. I'm interested in the case where ALL the rows that have fkId=5000 also have status=r (and not any other status).
The way I'm doing it now is
how many rows with id=5000 and status = 'r'?
how many rows with id=5000?
are those numbers equal? then ..
I'm trying to figure out how to rewrite this query using only 1 query, instead of 2. Keyword ALL didn't seem to be able to write such a query (<> ALL is equivalent to NOT IN). I tried a couple of GROUP BY formulations but could not get the correct result to appear.
The most efficient way to do this is:
if not exists (select 1
from users
where fkid = 5000 and (status <> 'r' or status is null)
)
This will stop the query at the first non-matching row.
I suggest you to check for any rows with status not equal to 'r'
SELECT count(*)>0 FROM Users WHERE fkId = 5000 AND status != 'r'
In the following case, if the number 1 is "true" (which it is) then you'll get Yes back, and if not you'll get No back:
SELECT IF(1, 'Yes', 'No') AS yesorno
(Go ahead -- try it!)
In your case however, the following would be more appropriate:
SELECT IF (
(SELECT COUNT(*) FROM Users WHERE fkId=5000 AND status IN('r') AND status NOT IN('1', 'a', 'k')) = (SELECT COUNT(*) FROM Users WHERE fkId=5000),
'They are equal.',
'They are not equal.'
)
AS are_they_equal
By adding AS, you can manipulate the name of the "column" that's returned to you.
Hope that helps... Also, see this page if you'd like more info.
:)
EASY!
Simply join back to the same table. Here is the complete code for testing:
CREATE TABLE Users(id int NOT NULL AUTO_INCREMENT, fkID int NOT NULL, status char(1), PRIMARY KEY (id));
INSERT Users (fkID, status) VALUES (5000, 'r');
INSERT Users (fkID, status) VALUES (5000, 'r');
INSERT Users (fkID, status) VALUES (5000, 'r');
-- The next query produces "0" to indicate no miss-matches
SELECT COUNT(*) FROM Users u1 LEFT JOIN Users u2 ON u1.id=u2.id AND u2.status='r' WHERE u1.fkID=5000 AND u2.id IS NULL;
-- now change one record to create a miss-match
UPDATE Users SET status='l' WHERE id=3 ;
-- The next query produces "1" to indicate 1 miss-match
SELECT COUNT(*) FROM Users u1 LEFT JOIN Users u2 ON u1.id=u2.id AND u2.status='r' WHERE u1.fkID=5000 AND u2.id IS NULL;
DROP TABLE Users;
So all you need to test for in the result is that it's 0 (zero) meaning everything has fkID=5000 also has status='r'
If you properly index your table then joining back to the same table is not an issue and certainly beats having to do a 2nd query.
Besides the NOT EXISTS version - which should be the most efficient as it does no counting at all and exits as soon as it finds a value that doesn't match the conditions, there is one more way, that will work if status is not nullable and will be efficient if there is an index on (fkId, status):
IF EXISTS
( SELECT 1
FROM Users
WHERE fkId = 5000
HAVING MIN(status) = 'r'
AND MAX(status) = 'r'
)
There is one difference though. The above will show false if there are no rows at all with fkId=5000, while the NOT EXISTS version will show true - which is probably what you want anyway.

Query different table when column exist

Database: MySql 5.5.3
I have a type column in table topic, if the type is 'E' which means there is an entry exist in 'Event' Table and it should query "TOPIC_HAS_EVENT" table to get few more colums from the EVENT Table. In the same way if Type is 'P' which means there is an entry exist in 'Poll Table' and it should query "TOPIC_HAS_POLL" table to get few columns from POLL Table. If the type is null then it should query as usual to get all the rows and their comments from the comment table.
The end results should have all the topics rows of all types ('E','P', NULL) and their specific columns from other table. If that can accomplish easily then i need to add few more columns as mentioned below
More complex
If type='E' have some rows, it should get some more rows such as user information from EVENT_HAS_USER table.
Relationship
Each Topic has Many Comments
Each Topic has One Event
Each Topic has One Poll
EVENT ManyTOMany User
Query so far created. I still need to add 'type' column. Please help me with it.
SELECT DISTINCT
T.TOPIC_GUID, COUNT(*) TOTAL_COMMENTS
FROM
CIRCLE C, CIRCLE_HAS_USER CHU, CIRCLE_HAS_TOPIC CHT, TOPIC T
LEFT JOIN TOPIC_COMMENT TC ON T.TOPIC_GUID = TC.TOPIC_GUID
WHERE
CHT.CIRCLE_GUID = C.CIRCLE_GUID
AND T.TOPIC_GUID < 400000 -- ?
AND CHT.TOPIC_GUID = T.TOPIC_GUID
AND CHU.CIRCLE_GUID = C.CIRCLE_GUID
AND CHU.USER_GUID = 1
AND CHU.STATUS = 'A'
GROUP BY T.TOPIC_GUID
ORDER BY T.LAST_UPDATED_TIMESTAMP DESC
LIMIT 10
Try this:
select (case when type = 'type1' then (select field from table1) else (select field from table2) end) as a from table;

Right way to do this query

I have a table
form (
int id )
webformsystemflags ( int id, int formid int sysflagid )
sysflag ( int id, name char(10) )
form table is a table which has all the forms
webform is a table which has the forms which have flags applied to it. It has a foreign key formid which is id to the form table and sysflagid which is foreign key to the sys flag table
sys flag is the table which contains the flags. Lets say I have flags defined as 1,2,3
I can have forms which don't have all the flags applied to it, some may have 1, some may have 2 or some may have 3 applied to it or some may have none.
How can I find all the forms which have either flag 1 or flag 2 or flag 3 applied to it ?
This is a common trick to find EXCLUSION. The value I have below of "FlagYouAreExpectingTo_NOT_Exist" is explicitly the one you expect NOT to be there. Here's how it works.
Get every form and LEFT JOIN to the Web System Flags table WITH FINDING the matching form, and flag setting you DO NOT want. If it finds a valid entry for the form and flag, the "formid" in the (wsf) table will exist. So, we want all that DON'T exist, hence the closing WHERE wsf.formid is null.
It will be NULL for those where it is NOT already flagged.
select
f.ID
from
forms f
left join webformsystemflags wsf
on f.id = wsf.formid
AND wsf.sysflagid = FlagYouAreExpectingTo__NOT__Exist
where
wsf.formid is null
You could use a subquery:
SELECT * FROM `form` WHERE `id` IN (SELECT `formid` FROM `webformsystemflags`)
Careful with subqueries on huge databases though. You could do the same thing with joins but this is an easy solution that will get you going.
Or for all results that DO NOT have a certain flag:
SELECT * FROM `form` WHERE `id` IN (SELECT `formid` FROM `webformsystemflags` WHERE `sysflagid` != 1 OR `sysflagid` != 2)
or a join method:
SELECT f.*, r.`sysflagid` FROM `form` f LEFT JOIN `webformsystemflags` r ON r.`formid` = f.`id` WHERE r.`sysflagid` != null
will get you the forms and the related flags. However, it will not get ALL flags in one row if the form has multiple flags on it. That one you may need to do a concat on the flags, but this answer is already growing unnecessarily complex.
*LAST EDIT *
Ok nutsandbolts - You need to update your question cause the two of us have overshot ourselves in a number of different queries and it isn't really helping to come back saying it doesnt give the right results. The right results can easily be reached by simply examining the queries we have provided and using the general logic behind them to compose the query that is right for you.
So my last suggestion - you say you want a query that will return a form IF it has a certain flag applied to it AND that is does NOT have other flags applied to it.
Here it is supposing you wanted all forms with a flag of 1 AND NOT 2 or 3 or none:
SELECT f.*, r.`sysflagid` FROM `form` f LEFT JOIN `webformsystemflags` r ON r.`formid` = f.`id` WHERE r.`sysflagid` =1 AND r.`formid` NOT IN (SELECT `formid` FROM `webformsystemflags` WHERE `sysflagid` = 2 OR `sysflagid` = 3)
Because your webformsystemflags is relational this query will NOT return any forms that do not exist in the webformsystemflags table - so you don't need to consider null.
If this is not what you're looking for I strongly suggest you rewrite your question with absolute and perfect clarity on your needs cause after this one I'm out of this conversation. Much luck to you though. Have fun.
You can use an exists clause to pull records like this:
select a.*
from form a
where exists (select 1
from webformsystemflags
where formid = a.id
and sysflagid IN (1,2,3))
This won't give you the associated flag. If you want that:
select a.*, b.sysflagid
from form a
join (select formid, sysflagid
from webformsystemflags
where sysflagid in (1,2,3)) b
on a.id = b.formid
There are many different ways to solve this.
EDIT: By reading a comment on the other answer it seems the question was unclear. You want the result forms that only have ONE flag? i.e. the form has flag 1 but not 2 or 3?
edit2: if you really just want a true/false query pulling only the true (has a flag):
select a.*, b.sysflagid
from form a
join webformsystemflags b on a.id = b.formid
If you want forms without flags:
select a.*
from form a
left join webformsystemflags b on a.id = b.formid
where b.formid is null
edit3: Based on comment, forms with one flag and not one of the others:
select a.*
from form a
where exists (select 1 from webformsystemflags where formid = a.id and sysflagid = 1)
and (
not exists (select 1 from webformsystemflags where formid = a.id and sysflagid = 2)
or
not exists (select 1 from webformsystemflags where formid = a.id and sysflagid = 3)
)