MySQL полоса нечисловых символов для сравнения - PullRequest
30 голосов
/ 13 ноября 2008

Я ищу в таблице записи, которые соответствуют конкретному номеру, который вводит пользователь. Таким образом, пользователь может ввести 12345, но это может быть 123zz4-5 в базе данных.

Я полагаю, что что-то подобное будет работать, если бы функции PHP работали в MySQL.

SELECT * FROM foo WHERE preg_replace("/[^0-9]/","",bar) = '12345'

Какая эквивалентная функция или способ сделать это только с MySQL?

Ответы [ 13 ]

37 голосов
/ 21 августа 2012

Я понимаю, что это древняя тема, но, погуглив эту проблему, я не смог найти простого решения (я видел почтенных агентов, но думаю, что это более простое решение), так что вот функция, которую я написал, кажется, работает довольно хорошо .

DROP FUNCTION IF EXISTS STRIP_NON_DIGIT;
DELIMITER $$
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
   RETURNS VARCHAR(255)
BEGIN
   DECLARE output   VARCHAR(255) DEFAULT '';
   DECLARE iterator INT          DEFAULT 1;
   WHILE iterator < (LENGTH(input) + 1) DO
      IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
         SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
      END IF;
      SET iterator = iterator + 1;
   END WHILE;
   RETURN output;
END
$$
7 голосов
/ 13 ноября 2008

Нет замены регулярного выражения, только простая строка REPLACE ().

В MySQL есть оператор REGEXP, но это всего лишь тестер совпадений, а не заменитель, поэтому вам придется вывернуть логику наизнанку:

SELECT * FROM foo WHERE bar REGEXP '[^0-9]*1[^0-9]*2[^0-9]*3[^0-9]*4[^0-9]*5[^0-9]*';

Это похоже на вашу версию с LIKE, но более точно соответствует. Оба будут работать одинаково плохо, требуя полного сканирования таблицы без индексов.

7 голосов
/ 13 ноября 2008

Хотя это не красиво и показывает результаты, которые не совпадают, это помогает:

SELECT * FROM foo WHERE bar LIKE = '%1%2%3%4%5%'

Я все же хотел бы найти лучшее решение, подобное пункту в исходном вопросе.

6 голосов
/ 10 марта 2016

Самый популярный ответ (@ user1467716) не самый быстрый. Полное спасибо им за то, что они дали рабочее предложение отскочить!

Это улучшенная версия:

DELIMITER ;;
DROP FUNCTION IF EXISTS `STRIP_NON_DIGIT`;;

CREATE DEFINER=`root`@`localhost` FUNCTION `STRIP_NON_DIGIT`(input VARCHAR(255)) RETURNS VARCHAR(255) CHARSET utf8
READS SQL DATA
BEGIN
   DECLARE output    VARCHAR(255) DEFAULT '';
   DECLARE iterator  INT          DEFAULT 1;
   DECLARE lastDigit INT          DEFAULT 1;
   DECLARE len       INT;

   SET len = LENGTH(input) + 1;
   WHILE iterator < len DO
      -- skip past all digits
      SET lastDigit = iterator;
      WHILE ORD(SUBSTRING(input, iterator, 1)) BETWEEN 48 AND 57 AND iterator < len DO
         SET iterator = iterator + 1;
      END WHILE;

      IF iterator != lastDigit THEN
         SET output = CONCAT(output, SUBSTRING(input, lastDigit, iterator - lastDigit));
      END IF;

      WHILE ORD(SUBSTRING(input, iterator, 1)) NOT BETWEEN 48 AND 57 AND iterator < len DO
         SET iterator = iterator + 1;
      END WHILE;
   END WHILE;

   RETURN output;
END;;

Тестирование 5000 раз на тестовом сервере:

-- original
Execution Time : 7.389 sec
Execution Time : 7.257 sec
Execution Time : 7.506 sec

-- ORD between not string IN
Execution Time : 4.031 sec

-- With less substrings
Execution Time : 3.243 sec
Execution Time : 3.415 sec
Execution Time : 2.848 sec
3 голосов
/ 04 мая 2018

Вы можете легко делать то, что вы хотите с REGEXP_REPLACE (совместимо с MySQL 8+ и MariaDB 10.0.5 +)

REGEXP_REPLACE(expr, pat, repl[, pos[, occurrence[, match_type]]])

Заменяет вхождения в строке expr, которые соответствуют регулярному выражению, указанному в шаблоне pat, на строку замены repl и возвращает результирующую строку. Если expr, pat или repl имеют значение NULL, возвращаемое значение равно NULL.

Перейти к REGEXP_REPLACE doc: MySQL или MariaDB

Попробуйте:

SELECT REGEXP_REPLACE('123asd12333', '[a-zA-Z]+', '');

Выход:

12312333
3 голосов
/ 01 марта 2012

В этом сообщении блога рассказывается, как вырезать нечисловые символы из строки с помощью функции MySQL:

SELECT NumericOnly("asdf11asf");

возвращает 11

http://venerableagents.wordpress.com/2011/01/29/mysql-numeric-functions/

3 голосов
/ 13 ноября 2008

Самый простой способ сделать это - использовать оператор MySQL REGEXP а-ля:

WHERE foo LIKE '1\D*2\D*3\D*4\D*5'

Это не особенно красиво, но MySQL не имеет функции preg_replace, поэтому я думаю, что это лучшее, что вы получите.

Лично, если эти только числовые данные так важны, я бы оставил отдельное поле только для того, чтобы содержать удаленные данные. Это сделает ваш поиск намного быстрее, чем при поиске по регулярному выражению.

1 голос
/ 05 декабря 2014

Я сталкивался с этим решением. Верхний ответ user1467716 будет работать в phpMyAdmin с небольшим изменением: добавьте второй тег разделителя в конец кода.

phpMyAdmin версия 4.1.14; MySQL версия 5.6.20

Я также добавил ограничитель длины, используя

DECLARE count INT DEFAULT 0; в декларациях

AND count < 5 в операторе WHILE

SET COUNT=COUNT+1; в операторе IF

Окончательная форма:

DROP FUNCTION IF EXISTS STRIP_NON_DIGIT;
DELIMITER $$
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
   RETURNS VARCHAR(255)
BEGIN
   DECLARE output   VARCHAR(255) DEFAULT '';
   DECLARE iterator INT          DEFAULT 1;
   DECLARE count INT DEFAULT 0;
   WHILE iterator < (LENGTH(input) + 1) AND count < 5 DO --limits to 5 chars
      IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
         SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
         SET COUNT=COUNT+1;
      END IF;
      SET iterator = iterator + 1;
   END WHILE;
   RETURN output;
END
$$
DELIMITER $$ --added this
1 голос
/ 19 ноября 2011

У меня похожая ситуация, при сопоставлении продуктов со штрих-кодами, где штрих-код иногда не хранит никаких буквенно-цифровых символов, поэтому при поиске 1022234 необходимо найти 102.2234 в БД.

В конце я просто добавил новое поле reference_number в таблицы продуктов и заставил php удалить буквенные цифры none в product_number, чтобы заполнить reference_number при каждом добавлении новых продуктов.

Вам нужно будет выполнить одноразовое сканирование таблицы, чтобы создать все поля reference_number для существующих продуктов.

После этого вы можете настроить свой индекс, даже если скорость не является фактором для этой операции, но все равно будет хорошей идеей поддерживать работоспособность базы данных, чтобы этот запрос не затормозил ее и не замедлил другие запросы.

0 голосов
/ 27 февраля 2019

думал, что поделюсь этим, так как я построил эту функцию отсюда. Я переставил так, чтобы мне было легче читать (я просто на стороне сервера).

Вы вызываете его, передавая имя таблицы и имя столбца, чтобы убрать все существующие нечисловые символы из этого столбца. Я унаследовал много плохих табличных структур, которые помещают тонну полей int как varchar, поэтому мне нужен был способ быстро их очистить, прежде чем я смогу изменить столбец на целое число.

drop procedure if exists strip_non_numeric_characters;
DELIMITER ;;

CREATE PROCEDURE `strip_non_numeric_characters`(
    tablename varchar(100)
    ,columnname varchar(100)
    )
BEGIN

-- =============================================
-- Author:      <Author,,David Melton>
-- Create date: <Create Date,,2/26/2019>
-- Description: <Description,,loops through data and strips out the bad characters in whatever table and column you pass it>
-- =============================================

#this idea was generated from the idea STRIP_NON_DIGIT function
#/211925/mysql-polosa-nechislovyh-simvolov-dlya-sravneniya

declare input,output varchar(255);
declare iterator,lastDigit,len,counter int;
declare date_updated varchar(100);

select column_name 
    into date_updated
    from information_schema.columns 
    where table_schema = database() 
    and extra rlike 'on update CURRENT_TIMESTAMP'
    and table_name = tablename
    limit 1;

#only goes up to 255 so people don't run this for a longtext field
#just to be careful, i've excluded columns that are part of keys, that could potentially mess something else up
set @find_column_length = 
concat("select character_maximum_length
    into @len
    from information_schema.columns
    where table_schema = '",database(),"'
    and column_name = '",columnname,"'
    and table_name = '",tablename,"'
    and length(ifnull(character_maximum_length,100)) < 255
    and data_type in ('char','varchar')
    and column_key = '';");

prepare stmt from @find_column_length;
execute stmt;
deallocate prepare stmt;

set counter = 1;        
set len = @len;

while counter <= ifnull(len,1) DO

    #this just removes it by putting all the characters before and after the character i'm looking at
    #you have to start at the end of the field otherwise the lengths don't stay in order and you have to run it multiple times
    set @update_query = 
    concat("update `",tablename,"`
        set `",columnname,"` = concat(substring(`",columnname,"`,1,",len - counter,"),SUBSTRING(`",columnname,"`,",len - counter,",",counter - 1,"))
        ",if(date_updated is not null,concat(",`",date_updated,"` = `",date_updated,"`
        "),''),
        "where SUBSTRING(`",columnname,"`,",len - counter,", 1) not REGEXP '^[0-9]+$';");

    prepare stmt from @update_query;
    execute stmt;
    deallocate prepare stmt;

    set counter = counter + 1;

end while;

END ;;
DELIMITER ;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...