MySQL REPLACE() in a set - mysql

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.

Related

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.

How to avoid this kind of duplicate?

This is my table for many to many relationship:
Related:
-id
-id_postA
-id_postB
I want this:
If for example there is a row with id_postA = 32 and id_postB = 67
then it must ignore the insertion of a row with id_postA = 67 AND id_postB = 32.
One option would be to create a unique index on both columns:
CREATE UNIQUE INDEX uk_related ON related (id_postA, id_postB);
And then prevent "duplicates by order inversion" using a trigger, ordering id_postA and id_postB on INSERT and UPDATE:
CREATE TRIGGER order_uk_related
BEFORE INSERT -- Duplicate this trigger also for UPDATE
ON related -- As MySQL doesn't support INSERT OR UPDATE triggers
FOR EACH ROW
BEGIN
DECLARE low INT;
DECLARE high INT;
SET low = LEAST(NEW.id_postA, NEW.id_postB);
SET high = GREATEST(NEW.id_postA, NEW.id_postB);
SET NEW.id_postA = low;
SET NEW.id_postB = high;
END;
As you can see in this SQLFiddle, the fourth insert will fail, as (2, 1) has already been switched to (1, 2) by the trigger:
INSERT INTO relation VALUES (1, null, null)
INSERT INTO relation VALUES (2, null, null)
INSERT INTO relation VALUES (3, 2, 1)
INSERT INTO relation VALUES (4, 1, 2)
Function-based indexes
In some other databases, you might be able to use a function-based index. Unfortunately, this is not possible in MySQL (Is it possible to have function-based index in MySQL?). If this were an Oracle question, you'd write:
CREATE UNIQUE INDEX uk_related ON related (
LEAST(id_postA, id_postB),
GREATEST(id_postA, id_postB)
);
you can include a where like:
For example
insert into table_name
(id_postA
,id_postB
select
col1,
col2
from table_1
where where (cast(col1 as varchar)+'~'+cast(col2 as varchar))
not in (select cast(id_postB as varchar)+'~'+cast(id_postA as varchar) from table_name)
If you always insert these with A < B, you won't have to worry about the reverse being inserted. This can be done with a simple sort, or a quick comparison before inserting.
Join tables like this are by their very nature uni-directional. There is no automatic method for detecting the reverse join and blocking it with a simple UNIQUE index.
Normally what you'd do, though, is insert in pairs:
INSERT INTO related (id_postA, id_postB) VALUES (3,4),(4,3);
If this insert fails, then one or both of those links is already present.

How Can i Insert Value in Mysql if it is Unknown?

Hello Friend I need Help About Mysql. My Database Look Like This
Table name = users
Rows
id = "7" | YourLastIP = ""
MY Query is:
UPDATE users SET `YourLastIP` = `XX.XX.XX.XX` WHERE `id` = `7`
I want to insert but it also wont work for me please guide me
INSERT INTO Users `YourLastIP` = (`XX.XX.XX.XX`) WHERE `id` = `7`
Please Help me How to Insert A value if table value not known.
You are using backticks which means in MySQL that you're meaning a column name, not text. Use single quotes ' instead.
First, the problem is that you have wrap the value with backtick, which is suppose to be single quote since it is a string literal.
UPDATE users SET `YourLastIP` = 'XX.XX.XX.XX' WHERE `id` = '7'
Second, use INSERT ... ON DUPLICATE KEY UPDATE if you want to UPDATE existing ID or otherwise INSERT if it does not exist.
INSERT INTO users(ID, YourLastIP)
VALUES (7, 'ip_here')
ON DUPLICATE KEY UPDATE YourLastIP = 'ip_here'
INSERT ... ON DUPLICATE KEY UPDATE Syntax
As a sidenote, the query is vulnerable with SQL Injection if the value(s) of the variables came from the outside. Please take a look at the article below to learn how to prevent from it. By using PreparedStatements you can get rid of using single quotes around values.
How to prevent SQL injection in PHP?
Make the column NULLABLE in definition
Then,
CREATE TABLE PRO(ID INT, YOURLASTIP VARCHAR(15));
INSERT INTO PRO values(4, "xxx.xxx.xxx.xxx");

MySQL string search between commas

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?

Remove a single character from a varchar field SQL Server 2008

I have a table with several varchar columns that are almost identical to primary keys I have in another table, with the exception of a period (.). I've looked at the replace function in T-SQL but the first argument isn't an expression. How can I remove all occurrences of a particular character with SQL? It seems like the correct answer might be to replace with a zero length string. Is that close?
To whomever felt the question didn't exhibit research effort it was mainly due to a misunderstanding of the documentation itself.
You can update the table directly using REPLACE on the column values:
UPDATE myTable
SET myColumn = REPLACE(myColumn, '.', '')
Do you want to remove all instances of the . from the string? If so, you were right about REPLACE:
DECLARE #Example TABLE
(
Value VARCHAR(100)
)
INSERT #Example (Value)
VALUES ('Test.Value'), ('An.otherT.est')
SELECT
REPLACE(Value, '.', '')
FROM
#Example
-- Replace only the first '.'
SELECT
STUFF(Value, CHARINDEX('.', Value, 0), 1, '')
FROM
#Example
Edit, making the example a little more useful since I played around with it anyway, I might as well post the example. :)
update your_table
set some_column = replace(some_column, '.', '')