Grouping multiple MySql queries - mysql

I have a simple query as listed below
SELECT id, name, email FROM users WHERE group_id = 1
This works great until, I then start adding LIKE queries, chained with OR statements to the end.
SELECT id, name, email FROM users
WHERE group_id = 1
AND id LIKE $searchterm
OR name LIKE $searchterm
OR email LIKE $searchterm
Suddenly my WHERE clause is no longer upheld and results with a 'group_id' of 2 or 3 are retrieved.
Is there a way I can group WHERE clauses so that they are always upheld or am I missing something obvious?

Dealing with the query first - you need to use brackets for the WHERE clause to be interpreted correctly:
SELECT id, name, email
FROM users
WHERE group_id = 1
AND ( id LIKE $searchterm
OR name LIKE $searchterm
OR email LIKE $searchterm)
I'd be looking at using Full Text Search (FTS) instead, so you could use:
SELECT id, name, email
FROM users
WHERE group_id = 1
AND MATCH(id, name, email) AGAINST ($searchterm)
Mind that the USERS table needs to be MyISAM...

I assume you want
email FROM users WHERE group_id = 1 AND (id LIKE $searchterm OR name LIKE $searchterm OR email LIKE $searchterm)
Here is the mysql operator precedence table

Related

Mysql - Search by tokenized string

I have a simple request: search by a user name.
Nevertheless, imagine I have this data on database:
1 - John Amazing Doe
2 - John Stupid Doe
3 - John Anthony
When I search by "John Doe", is there an easy way to search by results that have "john" or "doe"? Even better, if we could rank by who has more matches?
Thanks
Prepare the data prior to building the query
This is something that would be best handled a bit programmatically prior to sending to MySQL.
Turn the name into a list, break on space:
"John Doe" --> "John|Doe"
SELECT username FROM users WHERE username RLIKE 'John|Doe'
Be careful about sql injection here.
php Example
$db = new PDO([conn info]);
$searchArg = 'John Doe';
$nameRegex = str_replace(' ','|', strtolower($searchArg));
$userSearch = $db->prepare('SELECT username FROM users WHERE LOWER(username) REGEXP :nameRegex');
$userSearch->bindValue(':nameRegex', $nameRegex, PDO::PARAM::STR);
$userSearch->execute();
foreach ( $userSearch->fetchAll(PDO::FETCH_ASSOC) as $user ) {
echo $user['username'] . '\n<br>';
}
A full text index and MATCH() is your best option. That said, here is another way:
You can search for names that have 'John' or 'Doe' using LIKE:
SELECT user_name
FROM users
WHERE user_name LIKE 'John%'
OR user_name LIKE '%Doe'
Use leading and trailing % only if names will not always start with 'John' or end with 'Doe', otherwise use one. Double % will not make use of any indexes you have on user_name
You can then rank these using UNION and sorting column:
SELECT user_name, 1 as Sort_Col
FROM users
WHERE user_name LIKE 'John%'
AND user_name LIKE '%Doe'
UNION
SELECT user_name, 2 as Sort_Col
FROM users
WHERE (user_name LIKE 'John%'
OR user_name LIKE '%Doe')
AND user_name NOT IN (
SELECT user_name
FROM users
WHERE user_name LIKE 'John%'
AND user_name LIKE '%Doe')
ORDER BY Sort_Col ASC
You'll have to use an annoying NOT IN for the second query to be sure that you don't return rows that have both 'John' and 'Doe'. Normally the UNION would suffice, but since we added Sort_Col it will not. Just another reason to add a full text index if at all possible.

Nesting an "SQL SELECT STATEMENT" as/inside the REGEX operator

Say I have a table called People with columns PersonID and Name and I can select a Person's Name like:
SELECT Name FROM People WHERE PersonID = 1
which for this example will return 'John'.
I also have another table called ForumPosts with the fields ForumPostID and PostContent where PostContent is just TEXT which for the purpose of this example can be something like "My Name is John" or "John likes football"
Now I want to perform a Query which based on a given initial PersonID will return all rows from ForumPosts where the Person's Name matches a word contained in the PostContent field.
A regex which will match single words (or in this case the person's name) is:
[[:<:]]*Person'sNameHere*[[:>:]]
So ideally I want my SQL logic to be something like:
Select * FROM ForumPosts WHERE PostContent
REGEX [[:<:]](SELECT Name FROM People WHERE PersonID = '1') [[:>:]]
However I am not sure if this is even possible or how I would structure the query.
Sounds like you want to create a regex dynamically. Regexes are just strings in MySQL, so you can just use CONCAT to create the string you want.
SELECT *
FROM ForumPosts
WHERE PostContent
REGEXP CONCAT('[[:<:]]',(SELECT Name FROM People WHERE PersonID = '1'),'[[:>:]]')
Even better, you can use a JOIN instead of a subquery
SELECT ForumPosts.*
FROM ForumPosts
JOIN People ON PersonID = 1
WHERE PostContent REGEXP CONCAT('[[:<:]]',People.Name,'[[:>:]]')
DEMO: http://sqlfiddle.com/#!2/40828/1
You can implement this logic using an exists subquery. If I understand your logic correctly:
select fp.*
from ForumPosts fp
where exists (select 1
from people p
where personid = '1' and
fp.PostContent regex concat('[[:<:]]', name, '[[:>:]]')
)

Override column value in WHERE clause?

I was wondering if it was possible to override column value in the Where clause of a SQL query (MySQL in my case).
To be more clear, here is an example :
Suppose a basic query is :
SELECT lastname, firstname FROM contacts WHERE lastname = "Doe";
Is it possible to force lastname and firstname to return value from an other table, just by modifying what is after the WHERE part ? Something like
SELECT lastname, firstname FROM contacts WHERE lastname = (SELECT name FROM companies);
I am currently testing a web application, and I found a SQL Injection flaw where I can change Doe to whatever I want, but I'm limited with only one query (mysql_query restriction of PHP) and addslashes (so no " and ').
possible could be
SELECT lastname, firstname FROM contacts WHERE lastname = "{0}" UNION SELECT {1} --
where {0} non existed value and {1} data from other tables
UPDATE from wiki example
$res = mysql_query("SELECT author FROM news WHERE id=" . $_REQUEST['id'] ." AND author LIKE ('a%')");
become
SELECT author FROM news WHERE id=-1 UNION SELECT password FROM admin/* AND author LIKE ('a%')
The syntax that you used in your SELECT ... WHERE clause is a standard SQL feature called a subquery.
In the context of your example there is a restriction on the subquery to return just single value. Otherwise your query is a valid SQL and you can change subquery to return multiple values (with implicit OR) using IN operator like this:
SELECT lastname, firstname FROM contacts
WHERE lastname IN (
SELECT name FROM companies
);
You can dig deeper into this subject to uncover correlated subquery.

mysql select query - to match one field with the other field

I am trying to find records that has the following scenario.
ID | name | email
1 Robert robert#gmail.com
2 William bill#gmail.com
3 Michael michael#gmail.com
4 Micahel mike#gmail.com
Based on the above table, I want to find the records where the "name" is contained in the "email field", here record 1 and 3 should be the output and not 2 and 4. Is there any way I can do this comparison?
I tried reading about regex but couldn't find anything. If it's comparison of same value, it will be straightforward, but I am not having any clue for this one. I thought of LIKE but looks like this cannot have field names.
The exact syntax will depend on how you want to define the relationship.
Are you looking for the name anywhere in the email address? (This will be slow)
select id,name,email
from your_table
where email like concat('%',name,'%')
Just at the beginning of the email address?
select id,name,email
from your_table
where email like concat(name,'%')
Just before the # sign?
select id,name,email
from your_table
where email like concat(name,'#%')
You can use LIKE, you just have to use it in combination with CONCAT.
SELECT
ID,
name,
email
FROM
yourTable
WHERE
email LIKE CONCAT(name, '%');
The CONCAT will return a string which can be used to match against email via LIKE.
This should work
SELECT * FROM table WHERE email LIKE (CONCAT('%',name,'%'))
select * from your_table where lower(substring_index(email,'#',1))=lower(name)

Grouping fields that partially match in MySQL

I'm trying to return duplicate records in a user table where the fields only partially match, and the matching field contents are arbitrary. I'm not sure if I'm explaining it well, so here is the query I might run to get the duplicate members by some unique field:
SELECT MAX(id)
FROM members
WHERE 1
GROUP BY some_unique_field
HAVING COUNT(some_unique_field) > 1
I want to apply this same idea to an email field, but unfortunately our email field can contain multiple e-mails seperated by a comma. For example, I want a member with his email set to "user#someaddress.com" to be returned as a duplicate of another member that has "user#someaddress.com","someotheruser#someaddress.com" in their field. GROUP BY obviously will not accomplish this as-is.
Something like this might work for you:
SELECT *
FROM members m1
inner join members m2 on m1.id <> m2.id
and (
m1.email = m2.email
or m1.email like '%,' + m2.email
or m1.email like m2.email + ',%'
or m1.email like '%,' + m2.email + ',%'
)
It depends on how consistently your email addresses are formatted when there are more than one. You might need to modify the query slightly if there is always a space after the comma, e.g., or if the quotes are actually part of your data.
This works for me; may not do what you want:
SELECT MAX(ID)
FROM members
WHERE Email like "%someuser%"
GROUP BY Email
HAVING COUNT(Email) > 1