MySQL Reg ex, need to match phrases in any order - mysql

I am using a MySQL Database to search through a list of categories. My query is:
select * from cat where name REGEXP('(region_Long Island)+(.)*(sport_Outdoor Track)');
where the values "region_Long Island" and "sport_Outdoor Track" are passed in. I need to be able to match these categories, regardless of the order they are in. In the table, it is possible to have various combinations of these two categories. I need to match any records that have both of these categories, regardless of what order they are listed in.
I am not able to change the query itself, only modify what is passed into th REGEXP function.
Thank you

If you can use only a single regexp and you can't change the SQL query, then to match both A and B in any order, you need a regexp that matches AB or BA:
'region_Long Island.*sport_Outdoor Track|sport_Outdoor Track.*region_Long Island'
Re your comment:
What about cases where there are more than two, in any particular order?
If you had patterns A, B, and C any you needed to find all three in any order, you'd need a regexp that matches ABC, ACB, CAB, CBA, BAC, or BCA. This quickly starts to look like you need n! permutations if you have n patterns.
That's why a single regular expression is not a good solution for these cases. You're going to have to use another approach.
I am not able to change the query itself, only modify what is passed into the REGEXP function.
Sorry, that's not going to work.

SELECT *
FROM cat
WHERE name RLIKE 'region_Long Island'
AND name RLIKE 'sport_Outdoor Track'

Related

MYSQL select all record if contains specific number

I have a small problem, I have a table like this:
id|name|group|date_created
1|Volvo|1,3|06-04-2020 10:00:00
2|Audi|3|06-04-2020 10:00:00
etc....
Now I wish I could get all the records that have the value 1 inside the group column.
I tried LIKE "%1%", but I don't think it's a good query. Can you address me?
SELECT id FROM cars WHERE group LIKE '%1%'
The problem with your query is that it would wrongly match '1' against a list like '45,12,5' for example.
One method is to add commas on both ends before searching:
where concat(',', `group`, ',') like '%,1,%';
But in MySQL, it is much more convenient to use string function find_in_set(), whose purpose is just what you are looking for, ie search for a value in a comma-separated list:
select id from cars where find_in_set('1', `group`) > 0
Notes:
you should fix your data model, and have a separated table to store relationship between ids and groups, with each tuple on a separate row. Related reading: Is storing a delimited list in a database column really that bad?
group is a reserved word in MySQL, so not a good choice for a column name (you would need to surround it with backticks everytime you use it, which is error-prone)

Mysql: Look for specific letter in comma separated list

I've got a column with the possible a,b,c and city.
One, many or all of them may occur on each row as comma separated.
I'm trying to do a query where I look for all rows that contain c but not city.
I've tried LIKE %c% and LIKE c but that would not return correct results. I'm starting to look towards Regexp but it feels like there must be a better solution.
Any thoughts on this?
Use FIND_IN_SET for matching comma-separated values:
select * from TableName where FIND_IN_SET("c", column)

Query for multiple conditions in MySQL

I want to be able to query for multiple statements when I have a table that connects the id's from two other tables.
My three tables
destination:
id_destination, name_destination
keyword:
id_keyword, name_keyword
destination_keyword:
id_keyword, id_destination
Where the last one connects ids from the destination- and the keyword table, in order to associate destination with keywords.
A query to get the destination based on keyword would then look like
SELECT destination.name_destination FROM destination
NATURAL JOIN destination_keyword
NATURAL JOIN keyword
WHERE keyword.name_keyword like _keyword_
Is it possible to query for multiple keywords, let's say I wanted to get the destinations that matches all or some of the keywords in the list sunny, ocean, fishing and order by number of matches. How would I move forward? Should I restructure my tables? I am sort of new to SQL and would very much like some input.
Order your table joins starting with keyword and use a count on the number of time the destination is joined:
select
d.id_destination,
d.name_destination,
count(d.id_destination) as matches
from keyword k
join destination_keyword dk on dk.keyword = k.keyword
join destination d on d.id_destination = dk.id_destination
where name_keyword in ('sunny', 'ocean', 'fishing')
group by 1, 2
order by 3 desc
This query assumes that name_keyword values are single words like "sunny".
Using natural joins is not a good idea, because if the table structures change such that two naturally joined tables get altered to have columns the same name added, suddenly your query will stop working. Also by explicitly declaring the join condition, readers of your code will immediately understand how the tables are jones, and can modify it to add non-key conditions as required.
Requiring that only key columns share the same name is also restrictive, because it requires unnatural column names like "name_keyword" instead of simply "name" - the suffix "_keyword" is redundant and adds no value and exists only because your have to have it because you are using natural joins.
Natural joins save hardly any typing (and often cause more typing over all) and impose limitations on join types and names and are brittle.
They are to be avoided.
You can try something like the following:
SELECT dest.name_destination, count(*) FROM destination dest, destination_keyword dest_key, keyword key
WHERE key.id_keyword = dest_key.id_keyword
AND dest_key.id_destination = dest.id_destination
AND key.name_keyword IN ('sunny', 'ocean', 'fishing')
GROUP BY dest.name_destination
ORDER BY count(*), dest.name_destination
Haven't tested it, but if it is not correct it should show you the way to accomplish it.
You can do multiple LIKE statements:
Column LIKE 'value1' OR Column LIKE 'value2' OR ...
Or you could do a regular expression match:
Column LIKE 'something|somtthing|whatever'
The trick to ordering by number of matches has to do with understanding the GROUP BY clause and the ORDER BY clause. You either want one count for everything, or you want one count per something. So for the first case you just use the COUNT function by itself. In the second case you use the GROUP BY clause to "group" somethings/categories that you want counted. ORDER BY should be pretty straight forward.
I think based on the information you have provided your table structure is fine.
Hope this helps.
DISCLAIMER: My syntax isn't accurate.

MySQL IN() clause multiple returns

I have a special data environment where I need to be returned data in a certain way to populate a table.
This is my current query:
SELECT
bs_id,
IF(bs_board = 0, 'All Boards', (SELECT b_name FROM certboards WHERE b_id IN (REPLACE(bs_board, ';', ',')))) AS board
FROM boardsubs
As you can see I have an if statement then a special subselect.
The reason I have this is that the field bs_board is a varchar field containing multiple row IDs like so:
1;2;6;17
So, the query like it is works fine, but it only returns the first matched b_name. I need it to return all matches. For instance in this was 1;2 it should return two boards Board 1 and Board 2 in the same column. Later I can deal with adding a <br> in between each result.
But the problem I am dealing with is that it has to come back in a single column both name, or all names since the field can contain as many as the original editor selected.
This will not work the way you're thinking it will work.
Let's say bs_board is '1;2;3'
In your query, REPLACE(bs_board, ';', ',') will resolve to '1,2,3', which is a single literal string. This makes your final subquery:
SELECT b_name FROM certboards WHERE b_id IN ('1,2,3')
which is equivalent to:
SELECT b_name FROM certboards WHERE b_id = '1,2,3'
The most correct solution to the problem is to normalize your database. Your current system or storing multiple values in a single field is exactly what you should never do with an RDBMS, and this is exactly why. The database is not designed to handle this kind of field. You should have a separate table with one row for each bs_board, and then JOIN the tables.
There are no good solutions to this problem. It's a fundamental schema design flaw. The easiest way around it is to fix it with application logic. First you run:
SELECT bs_id, bs_board FROM boardsubs
From there you parse the bs_board field in your application logic and build the actual query you want to run:
SELECT bs_id,
IF(bs_board = 0, 'All Boards', (SELECT b_name FROM certboards WHERE b_id IN (<InsertedStringHere>) AS board
FROM boardsubs
There are other ways around the problem, but you will have problems with sorting order, matching, and numerous other problems. The best solution is to add a table and move this multi-valued field to that table.
The b_id IN (REPLACE(bs_board, ';', ',')) will result in b_id IN ('1,2,6,7') which is different from b_id IN (1,2,6,7) which is what you are looking for.
To make it work either parse the string before doing the query, or use prepared statements.

Return substring up to first digit

I am trying to group rows by a field that either has the pattern [:alpha:][:digit:].* or [:alpha:][:alpha:][:digit:].* by the substring up to but excluding the digit. i.e. the returned substring will either have one letter, or two.
I am thinking something along the lines of:
SELECT
LEFT(postcode,IF(ISDIGIT(postcode,2),1,2)) AS area,
COUNT(*) AS num
FROM addresses
GROUP BY
LEFT(postcode,IF(ISDIGIT(postcode,2),1,2))
Except of course there is no ISDIGIT() function.
I was also thinking of something similar to LEFT(postcode,POSITION_REGEX("\d" IN postcode)) but obviously that doesn't exist either :-/
Database server is running MySQL 4.1.24
Upgrading to 5.0 is possible but would require downtime and hasn't been done yet as it hasn't been necessary so far.
SELECT
LEFT(postcode, IF(postcode REGEXP '^.[[:digit:]]', 1, 2)) AS area,
COUNT(*) AS num
FROM addresses
GROUP BY area