MySQL string search between commas - mysql

My db is built as follows:
value1,value2,value3 | 1
value4,value5,val"u6 | 2
value 5, value 6, value 8 |3
(Two columns, one with a key separated by commas and the other just a normal var-char)
I'm looking for the most reliable way to find a query within the quotes and I'm getting kinda lost here.
I'm using the word boundaries for that:
SELECT * FROM ABC WHERE content REGEXP '[[:<:]]value 5[[:>:]]'
The problem is when I'm doing this query:
SELECT * FROM ABC WHERE content REGEXP '[[:<:]]5[[:>:]]'
It will also return the value, which is not what I'm looking for. Another problem is that the word boundaries refer to quotes as a word boundary
How can I solve this and create a simple query that will only fetch the exact full query between the quotes?
BTW
I don't have an option to change the DB structure...

As #MarcB commented, you really should try to normalise your schema:
CREATE TABLE ABC_values (
id INT,
content VARCHAR(10),
FOREIGN KEY (id) REFERENCES ABC (id)
);
INSERT INTO ABC_values
(id, content)
VALUES
(1, 'value1'), (1, 'value2'), (1, 'value3'),
(2, 'value4'), (2, 'value5'), (2, 'val"u6'),
(3, 'value 5'), (3, 'value 6'), (3, 'value 8')
;
ALTER TABLE ABC DROP content;
Then, as required, you can perform a SQL join between your tables and group the results:
SELECT id, GROUP_CONCAT(ABC_values.content) AS content
FROM ABC LEFT JOIN ABC_values USING (id) NATURAL JOIN (
SELECT id FROM ABC_values WHERE content = 'value 5'
) t
GROUP BY id
If it is completely impossible to change the schema, you can try FIND_IN_SET():
SELECT * FROM ABC WHERE FIND_IN_SET('value 5', content)

Another workaround is to use LIKE with the delimiters of the items in your list:
WHERE content LIKE ',5,'
But the item you're looking for may be at the start or end of the list. So you have to modify the list on the fly to include the delimiters at the start and end.
WHERE CONCAT(',', content, ',') LIKE '%,5,%' -> this works for me on mysql
This works, and in some sense it's no worse than any other search that you do for an item in a comma-separated list. That's because such a search is bound to do a table-scan and therefore it's very inefficient. As the data in your table grows, you'll find it can't perform well enough to be useful.
See also my answer to Is storing a delimited list in a database column really that bad?

Related

MySQL REPLACE without all fields provided

I have mysql query to replace some records:
REPLACE INTO product_core
(id, title, description, category_id)
VALUES (2, 'new_title', 'new_description', 33)
Can I do the same, but not providing all needed values? Example:
REPLACE INTO product_core
(id, title, description, category_id)
VALUES (2, 'new_title', 'new_description') #no category_id
Got error wrong number of values here near
I want to bulk replace many records, but I do not want to query all fields before. In this example, I want to update category_id for some records, but not for all.
REPLACE INTO product_core
(id, title, description, category_id)
VALUES (2, 'new_title_2', 'new_description_2'), #no category_id
(3, 'new_title_3', 'new_description_3', 34) #with category_id
Is it real to do this? Replace some fields for one record and other fields for second record in one query.
Or if is it real to provide special variable meaning that some fields will be the same as before replace (category_id)?
VALUES (2, 'new_title_2', 'new_description_2', #category_id_same_as_before)
Can I do the same, but not providing all needed values? Example:
REPLACE INTO product_core (id, title, description, category_id) VALUES
(2, 'new_title', 'new_description') #no category_id
Yes, the correct query is:
REPLACE INTO product_core
(id, title, description)
VALUES (2, 'new_title', 'new_description') #no category_id
EDIT: As Tom commented below the above might be misleading as for the omitted columns default values will be used, not the ones set for the record which is being replaced.
Is it real to do this? Replace some fields for one record and other fields for second record in one query.
It's not possible in MySQL. The column list is common for all the values sets.
Or if is it real to provide special variable meaning that some fields
will be the same as before replace (category_id)?
It's perhaps possible, but not straightforward and not in all MySQL versions. In the docs they say: "You cannot refer to values from the current row and use them in the new row".
In MySQL > 8.0.19 perhaps VALUES(ROW) can help. Or you can perhaps write you own UDF which does it.
You can't omit these columns from a REPLACE command, unless it is the default value for the column.
According to the documentation:
Any missing columns are set to their default values. [...] You cannot refer to values from the current row and use them in the new row.
It may be better to use a standard UPDATE command instead, which can reference the current column value.

SELECT FROM Table WHERE exact number not partial is in a string SQL

I have a table that contains a bunch of numbers seperated by a comma.
I would like to retrieve rows from table where an exact number not a partial number is within the string.
EXAMPLE:
CREATE TABLE IF NOT EXISTS `teams` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`uids` text NOT NULL,
`islive` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
INSERT INTO `teams` (`id`, `name`, `uids`, `islive`) VALUES
(1, 'Test Team', '1,2,8', 1),
(3, 'Test Team 2', '14,18,19', 1),
(4, 'Another Team', '1,8,20,23', 1);
I would like to search where 1 is within the string.
At present if I use Contains or LIKE it brings back all rows with 1, but 18, 19 etc is not 1 but does have 1 within it.
I have setup a sqlfiddle here
Do I need to do a regex?
You only need 1 condition:
select *
from teams
where concat(',', uids, ',') like '%,1,%'
I would search for all four possible locations of the ID you are searching for:
As the only element of the list.
As the first element of the list.
As the last element of the list.
As an inner element of the list.
The query would look like:
select *
from teams
where uids = '1' -- only
or uids like '1,%' -- first
or uids like '%,1' -- last
or uids like '%,1,%' -- inner
You could probably catch them all with a OR
SELECT ...
WHERE uids LIKE '1,%'
OR uids LIKE '%,1'
OR uids LIKE '%, 1'
OR uids LIKE '%,1,%'
OR uids = '1'
You didn't specify which version of SQL Server you're using, but if you're using 2016+ you have access to the STRING_SPLIT function which you can use in this case. Here is an example:
CREATE TABLE #T
(
id int,
string varchar(20)
)
INSERT INTO #T
SELECT 1, '1,2,8' UNION
SELECT 2, '14,18,19' UNION
SELECT 3, '1,8,20,23'
SELECT * FROM #T
CROSS APPLY string_split(string, ',')
WHERE value = 1
You SQL Fiddle is using MySQL and your syntax is consistent with MySQL. There is a built-in function to use:
select t.*
from teams t
where find_in_set(1, uids) > 0;
Having said that, FIX YOUR DATA MODEL SO YOU ARE NOT STORING LISTS IN A SINGLE COLUMN. Sorry that came out so loudly, it is just an important principle of database design.
You should have a table called teamUsers with one row per team and per user on that team. There are numerous reasons why your method of storing the data is bad:
Numbers should be stored as numbers, not strings.
Columns should contain a single value.
Foreign key relationships should be properly declared.
SQL (in general) has lousy string handling functions.
The resulting queries cannot be optimized.
Simple things like listing the uids in order or removing duplicate are unnecessarily hard.

MySQL REPLACE() in a set

So I have a table of banners, and in that table there is a field that tells me on which sites they should be shown, like a poor man's relational table.
Banner.jpg | ABC,CDE,FGH
Banner2.jpg | IPO,IPOMON,IPOHLM
And so on. And I use find_in_set() to collect banners for a specific site.
Now, I want to write a script that replaces these values, say "ABC" should be "XZY". So I run this:
update banners set sites = replace(sites, 'ABC', 'XZY')
And it's a done deal. But, problem arises if I want to change "IPO" to "APO" for instance, since this query:
update banners set sites = replace(sites, 'IPO', 'APO')
Would result in this data:
Banner.jpg | XYZ,CDE,FGH
Banner2.jpg | APO,APOMON,APOHLM
Which is undesirable. So how do I change just "IPO" and not "IPOMON" when doing a replace() or similar function? I would want a "replace_in_set()" function so to speak.
Any ideas?
You can use a query like this. first it adds a ',' at the begin and the end of the set. Then you can change your set and TRIM remove the ','.
UPDATE banners
SET sites = TRIM(BOTH ',' FROM REPLACE( CONCAT(',',sites,','), ',IPO,', ',APO,') );
I don't think there is a nice/pretty way to do this. In order to have IPO as a singular and IPO as part of a list changed you'd have to do something like this.
UPDATE banners SET
sites = REPLACE(sites,
IF(sites LIKE '%IPO,%', 'IPO,', 'IPO'),
IF(sites LIKE '%IPO,%', 'APO,', 'APO')
);
The more things to catch the more complex the IFs have to be but you get the idea.
DO the fact you ha a string the you could use 'IPO,'
update banners set sites = replace(sites, 'IPO,', 'APO,')
You should have the tables normalized so that you can use an UPDATE on the field in which value equals your desired replacement. Take a look at this example:
/* create the banner data table and add banner.jpg and banner2.jpg */
CREATE TABLE banner
(`banner_id` int, `description` varchar(11))
;
INSERT INTO banner
(`banner_id`, `description`)
VALUES
(1, 'banner.jpg'),
(2, 'banner2.jpg')
;
/* create the website data table and:
ABC, CDE, FGH, IPO, IPOMON, and IPOHLM */
CREATE TABLE website
(`site_id` int, `banner_id` int, `site` varchar(6))
;
INSERT INTO website
(`site_id`, `banner_id`, `site`)
VALUES
(1, 1, 'ABC'),
(2, 1, 'CDE'),
(3, 1, 'FGH'),
(4, 2, 'IPO'),
(5, 2, 'IPOMON'),
(6, 2, 'IPOHLM')
;
/* update the website data table to change IPO to APO */
UPDATE `website`
SET `site` = "APO"
WHERE `site` = "IPO";
SELECT * FROM `website`
Fiddle: Live Demo
I gave a +1 to Bernd Buffin's answer, because it at least accomplishes the substitution without fetching the data into an application to split and rejoin the string.
The following doesn't answer your question, but I thought I'd post it here to benefit other readers in the future.
I tried to test the new REGEXP_REPLACE() function in MySQL 8.0.4, because I thought the following would work, but it doesn't:
mysql> SELECT REGEXP_REPLACE('IPO,IPOMON,IPOHLM', '[[:<:]]IPO[[:>:]]', 'APO');
ERROR 3686 (HY000): Illegal argument to a regular expression.
MySQL changed their regexp library in 8.0.4 and the new library doesn't support the [[:<:]] [[:>:]] word-boundary patterns.

Trouble understanding how to create a Join & Require query

So, I guess I should explain what I mean by a Join & Require Query as this probably isn't the correct term. For this question I set up a very simple subset of tables that would be out of a wireframe for a video game. I apologise in advance, SQLFiddle isn't working right now.
CREATE TABLE reqs (
crafted_id INT, item_id INT );
CREATE TABLE items (
id INT, name VARCHAR(30) );
CREATE TABLE crafted (
id INT, name VARCHAR(30) );
With these tables, I also created some sample data. I didn't use auto_increment in these tables so you could have a better visual.
INSERT INTO items (id, name) VALUES (1, 'Herbs');
INSERT INTO items (id, name) VALUES (2, 'Health Potion');
INSERT INTO items (id, name) VALUES (3, 'Leather');
INSERT INTO items (id, name) VALUES (4, 'Sticks');
INSERT INTO crafted (id, name) VALUES (101, 'Cured Leather');
INSERT INTO crafted (id, name) VALUES (102, 'Tea');
INSERT INTO reqs (crafted_id, item_id) VALUES (101, 1);
INSERT INTO reqs (crafted_id, item_id) VALUES (101, 3);
INSERT INTO reqs (crafted_id, item_id) VALUES (102, 1);
In this example I have setup a very minimalistic example of a crafting system in a game, where a crafted item requires other items.
I need to create a query in which I can pull all of the available values from the crafted table based on the items that I have. For example, based on the data above, I would do the following:
requestCraftableItems(itemArray) {
// Execute query based on array here
};
requestCraftableItems([1, 2, 3])
This should execute a query stating that the user has items 1, 3, & 4 and try to find a crafted entry where all of the reqs.item_id for reqs.crafted_id are met in the item array. In this example nothing should be returned, because the crafted_id for cured leather requires items 1 & 3 and the crafted_id for tea requires item 1
So if the user executed the following:
requestCraftableItems([1, 3, 4]);
It should return the crafted entry for Cured Leather AND Tea as the user supplied 1 & 3 to the query.
This is not my originally use case and is just an example to get the point of what I'm trying to do across on a very simple scale.
EDIT: To clairfy, if the value (1) is passed to the query, Cured Leather should not appear in the results, as Cured Leather requires BOTH 1 and 3.
I may have misunderstood what you are looking for, but a simple join should return the result you want:
SELECT crafted.name
FROM items
JOIN reqs
ON reqs.item_id = items.id
JOIN crafted
ON crafted.id = reqs.crafted_id
WHERE items.id in (1,3,4)
GROUP BY crafted.id
This would return the results in your examples.
Not tested, may contain typos

MySQL: show strings in result that were not found

I have a MySQL db table with a column containing strings. And I have a list of strings. Some of the strings from the list can be found in my table, others can't. See my table TableName:
<TableName>
IntegerColumn, StringColumn
1, one
2, two
3, three
4, four
If I execute the query
SELECT * FROM TableName WHERE StringColumn NOT IN ('three', 'four', 'five', 'six');
I get a result of two rows, which contain nothing but NULL.
How can I see for which of the strings there was no match? Because I want to add them to the db table
Thx in advance
Using the following sample
CREATE TABLE sample_table
(
id int auto_increment primary key,
details varchar(30)
);
INSERT INTO sample_table
(id, details)
VALUES
(1, 'One'),
(2, 'Two'),
(3, 'Three'),
(4, 'Four'),
(5, 'Five');
I ran the query
SELECT * FROM sample_table
WHERE details NOT IN ('two', 'three', 'nine');
which gave the correct output of:
1 One
4 Four
5 Five
If you've got NULL returned then there is something you're not explaining in your question. Can you provide schema information or even a SQL Fiddle and I'm sure you'll get a much better answer.
I think what you want is, 'three', 'four', 'five', 'six' if any of this string is not present in the database you want to identify that.
Using query I think it will be tough. You can just use below query to get the available strings and the counts. Then, if you are using a programming language you can identify which string are not present in the result and then proceed further.
SELECT StringColumn, count(*) FROM TableName group by StringColumn
Not sure if this is what you are looking for.
This should not go this way. Check the following demo to ensure that your code is correct. Now what really matters is the set of data present in the table.
DEMO
Here is the DDL:
create table tab1 (IntegerColumn int(2), StringColumn varchar(20));
insert into tab1 values(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four');