MYSQL Checking even/odd within a substring - mysql

I have a query where one condition to check if a room number is odd/even. The issue is that the room number is stored with the building info as well in a string.
Here is the format of the data in the database:
ABC-0101A (Odd)
ABC-0112B (Even)
ZYX-123A1 (Odd)
ZYX-456B1 (Even)
For a room number to be considered even/odd, it is dependent on the last number before the first letter after the dash.

This would be quite a lot easier and more performant if you could separately store the numeric portion in another column. As a long term solution, I would recommend doing that.
But you can use a regular expression here to match the last digit before the first number, with a set of even [02468] and a set of odd [13579] with a CASE statement.
SELECT
CASE
WHEN room REGEXP '-[0-9]*[02468][A-Z]' THEN 'even'
WHEN room REGEXP '-[0-9]*[13579][A-Z]' THEN 'odd'
ELSE 'something else entirely'
END
FROM rooms
The pattern -[0-9]* matches a literal hyphen followed by zero or more digits. Then the significant part is the [02468] or [13579] set to match an even or odd digit that must be present immediately preceding [A-Z] matching the next letter.
Here's a demonstration: http://sqlfiddle.com/#!9/464d1d/3
Exmaple matching an even room:
SELECT 'ABC-0112B' REGEXP '-[0-9]*[02468][A-Z]';
+------------------------------------------+
| 'ABC-0112B' REGEXP '-[0-9]*[02468][A-Z]' |
+------------------------------------------+
| 1 |
+------------------------------------------+
Example matching an odd room:
SELECT 'ABC-0101A' REGEXP '-[0-9]*[13579][A-Z]';
+------------------------------------------+
| 'ABC-0101A' REGEXP '-[0-9]*[13579][A-Z]' |
+------------------------------------------+
| 1 |
+------------------------------------------+
Example matching an odd room that doesn't end with a letter:
SELECT 'ABC-0101A11' REGEXP '-[0-9]*[13579][A-Z]';
+--------------------------------------------+
| 'ABC-0101A11' REGEXP '-[0-9]*[13579][A-Z]' |
+--------------------------------------------+
| 1 |
+--------------------------------------------+

First create a function IsNumeric which returns 1 if number is integer otherwise 0:
CREATE FUNCTION IsNumeric (input varchar(1024)) RETURNS int
RETURN input REGEXP '^(-|\\+){0,1}([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+|[0-9]+)$';
Then use following query to check room number:
select IF (IsNumeric(right(room,1))>0,
IF(right(room,1)%2=0,'Even','ODD'),
IF(left(right(room,2),1)%2=0,'Even','ODD')
) as room_number
from rooms;
Here in above query first check if last digit is integer, If so then check for even/Odd number. If digit is not integer pick the 2nd last digit and apply the same check.

Related

MySQL - REGEX Search word from string and matched word should be either alnum or numeric only

Question: If i pass any word in where clause then query should return only if passing word is alnum or num only using mysql regex.
I have a table where addresses.
address table
--------------------
id | name
--------------------
1 | 123 demo
--------------------
2 | 1st demo
--------------------
3 | 123
--------------------
4 | demo
Example 1
SELECT * FROM address WHERE name regexp '(^|[[:space:]])123([[:space:]]|$)'
Result: Row 1,3 should return. it works for me
Example 2
SELECT * FROM address WHERE name regexp '(^|[[:space:]])1st([[:space:]]|$)'
Result: Row 2 should return. it works for me
Example 3
SELECT * FROM address WHERE name regexp '(^|[[:space:]])demo([[:space:]]|$)'
Result: It should not return any row. but it return 1,2,4 row
Final : So if i pass "demo" in where clause then no result should return.
http://sqlfiddle.com/#!9/acc5c8/2
I want to recommend wordboundary syntax like this: REGEXP '[[:<:]]demo[[:>:]]'
http://sqlfiddle.com/#!9/d63e05/1
Honestly, if this were my project, I'd be doing a ctype_alnum() check on the search value before bothering to make a trip to the database. However, your requirement is:
only if passing word is alnum or num only using mysql regex
To ensure that the needle string in the query contains at least one number, add another check to the WHERE clause.
More specifically...
SELECT * FROM address WHERE address REGEXP '[[:<:]]demo[[:>:]]' AND 'demo' REGEXP '[0-9]'
This will return no rows as desired.

difference between find_in_set and and locate

I have product tables in my database
Product table structure:
product_id | testid
------------------------------------
1 11,12,13
2 2,4
Below is my FIND_IN_SET query:
SELECT product_id FROM product
WHERE FIND_IN_SET(3, testid) > 0;
Output
0
Below is my LOCATE query:
SELECT product_id FROM product
WHERE LOCATE(3, testid) > 0;
output
1
My question
What is difference between FIND_IN_SET and LOCATE and what is the best way to find id in column
To put it in simple technical terms(PHP terminology), find_in_set is like substring function of PHP. It will accept a substring and a string as parameters, and return 1 if the substring is found within the string. It will return 0 if substring is not found.
On the contrary, LOCATE() returns the position of the first occurrence of a string within a string. It accepts, a substring and a string as parameters.
I think in your use case, find_in_set is the one you should go for. Because this is the one. find_in_set will return 1 if 3 is found in a row, where as locate will first occurance of 3 in the string even if it finds 31 or 300 as first element.
Difference between LOCATE() and FIND_IN_SET() Function
When using LOCATE() function for integers, suppose we need 1 to return from LOCATE() if integer 3 is in the set 1,2,3,4,5,.. the following MySQL commands can be written:
mysql> SELECT IF(LOCATE(3,'1,2,3,4,5,6,7,8,9')>0,1,0);
+-----------------------------------------+
| IF(LOCATE(3,'1,2,3,4,5,6,7,8,9')>0,1,0) |
+-----------------------------------------+
| 1 |
+-----------------------------------------+
1 row in set (0.06 sec)
The above command working rightly because the set contains the number 3 , but if we write the following commands, look what happened
mysql> SELECT IF(LOCATE(3,'11,12,13,14,15')>0,1,0);
+--------------------------------------+
| IF(LOCATE(3,'11,12,13,14,15')>0,1,0) |
+--------------------------------------+
| 1 |
+--------------------------------------+
1 row in set (0.02 sec)
Above the 3 is not present as a number three(3) in the given set, though the LOCATE() returns 1.
To avoid this type of situation you can use the FIND_IN_SET() function. Here is the example below:
mysql> SELECT IF(FIND_IN_SET(3,'11,12,13,4,5,6,7,8,9')>0,1,0);
+-------------------------------------------------+
| IF(FIND_IN_SET(3,'11,12,13,4,5,6,7,8,9')>0,1,0) |
+-------------------------------------------------+
| 0 |
+-------------------------------------------------+
1 row in set (0.05 sec)
So, LOCATE() function is very much suitable for string but not as much suitable for integer.
Examples, credits and some more information you can find here
So in your example FIND_IN_SET return 0 because there is no 3 in the given set, but LOCATE() returns 1 it treat the given set as a string but not a comma separated value, and the 3 present in the number 13

MySQL and regexp - return as a SELECT column with a table's column's regexp matching part only

I know there is an easy way to give a WHERE condition matching a regexp in MySQL, but my question is different. The column is like this:
jknewfjnkewnjkfewnjfwe1jnkf2jnw wefwef 1234567.12345678 qwrqwerqwrq
jnewdnkewjk ewnfewf1 wefwefew2 1234568.22314152 qwrqwrqwr qw
whjefjwefwe1 wefwefwef2 qweqwrqrw 1234369.21213131 qwdqwdqwd
I would like to get a SELECT column SUBSTRING phrase, which returns for me:
selectcol1 selectcol2
1234567 12345678
1234568 22314152
1234369 21213131
All I know: first matching number is 7 digits, and 2nd matching number is 8 digits always, and the parts before and after surely won't match the exactly 7 digit pattern.
Is there any way to get these SELECT columns?
Short answer: No. Long answer...
REGEXP '[[:<:]][0-9]{7}[[:>:]].*[[:<:]][0-9]{8}[[:>:]]'
will match the first 'word' of 7 digits followed (eventually) by a 'word' with 8 digits.
Demonstration (1=true):
mysql> SELECT 'jnewdnkewjk ewnfewf1 wefwefew2 1234568.22314152 qwrqwrqwr qw'
-> REGEXP '[[:<:]][0-9]{7}[[:>:]].*[[:<:]][0-9]{8}[[:>:]]' AS test;
+------+
| test |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
Keep in mind that SQL is not a good language for extracting those fields. In fact, I would say that you should use PHP (or other language) to extract the fields after MySQL locates and fetches the rows.
That is, there is no LOCATE() with regexp.

Mysql regex quirks

Why does this match (it should match (44[0-9]) zero or more times)
mysql> SELECT "tampampam" REGEXP "(44[0-9])*$";
+----------------------------------+
| "tampampam" REGEXP "(44[0-9])*$" |
+----------------------------------+
| 1 |
+----------------------------------+
1 row in set (0.00 sec)
And this does not (it should match 44 followed by ([0-9]) zero or more times
mysql> SELECT "44tampampam" REGEXP "44([0-9])*$";
+------------------------------------+
| "44tampampam" REGEXP "44([0-9])*$" |
+------------------------------------+
| 0 |
+------------------------------------+
1 row in set (0.00 sec)
Well, it is a very strange regex expression.
As for the first case, (44[0-9])*$ means "match a string starting with 44 and then a number from 0 to 9, any number of times up to the end of string". Since "any number" is possible, the string "tampampam" is matched.
As for the second case, 44([0-9])*$ means "match 44, then any number from 0 to 9 (with heavy backtracking), zero or more times, up to the end of string". But after 44 there is "tampampam". No match is due. Remove $, and you'll have a match.
You must use start anchor also to make sure it doesn't match unwanted text:
SELECT "tampampam" REGEXP "^(44[0-9])*$";
+-----------------------------------+
| "tampampam" REGEXP "^(44[0-9])*$" |
+-----------------------------------+
| 0 |
+-----------------------------------+
The first query matches because matching something zero or more times, means that not matching it (ie. matching zero times), is also a match.
The second query does not match, because you have anchored the regular expression to the end of the string, because of the dollar-sign ($). As the end of the string is not the string 44 optionally followed by digits, it does not match.
I see no reason to use *$ in your case. Keep it simple:
SELECT "tampampam" REGEXP "44[0-9]";
=> 0
SELECT "t441ampampam" REGEXP "44[0-9]";
=> 1
SELECT "t441ampampam" REGEXP "^44[0-9]";
=> 0
SELECT "441tampampam" REGEXP "^44[0-9]";
=> 1
So if you need 44 to be the first characters in the string use '^44[0-9]'.
If you don't care that is as simple as '44[0-9]'.

mySQL SELECT IN from string

Here is my table X:
id vals
---------------------
1 4|6|8|
Now table Y:
id name
--------------------
1 a
4 b
6 c
8 d
Now I want the following:
select * from Y where id IN (replace(select vals from X where id = '1'),'|',',')
But this does not seem to work. Any ideas why?
You may use FIND_IN_SET instead of just IN, normal IN keyword couldn't search between comma seperated values within one field.
For example
mysql> select FIND_IN_SET(4, replace('4|6|8|','|',','));
+-------------------------------------------+
| FIND_IN_SET(4, replace('4|6|8|','|',',')) |
+-------------------------------------------+
| 1 |
+-------------------------------------------+
1 row in set (0.00 sec)
Replace gives you a string back - but it's a string value, not a string as in part of your query.
What you can do is instead of using IN, use a REGEXP to match within your original string, for example:
vals REGEXP '[[:<:]]4[[:>:]]'
would be true only if there is a "4" in the original string that isn't part of a larger number (thus if you have 3|44|100 it wouldn't match on "4" but would match on "44").
The [[:<:]] and [[:>:]] are "left side of word" and "right side of word" respectively.
To generate that string, you can do something like...
CONCAT('[[:<:]]', CAST(id AS CHAR), '[[:>:]]')