web-dev-qa-db-fra.com

Supprimer les caractères non numériques de MySQL à comparer

Je cherche à trouver des enregistrements dans une table qui correspondent à un nombre spécifique que l'utilisateur entre. Ainsi, l'utilisateur peut entrer 12345, mais cela pourrait être 123zz4-5 dans la base de données.

J'imagine que quelque chose comme cela fonctionnerait si les fonctions PHP fonctionnaient dans MySQL.

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

Quelle est la fonction ou le moyen équivalent de le faire avec MySQL uniquement?

29
Chris Bartow

Bien que ce ne soit pas joli et que les résultats ne correspondent pas, cela aide:

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

J'aimerais quand même trouver une meilleure solution similaire à celle de la question initiale.

7
Chris Bartow

Je réalise que c'est un sujet ancien, mais après avoir recherché ce problème sur Google, je n'ai pas trouvé de solution simple (j'ai vu les vénérables agents mais je pense que c'est une solution plus simple). Voici donc une fonction que j'ai écrite et qui semble fonctionner assez bien.

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
$$
35
user1467716

Il n'y a pas d'expression rationnelle à remplacer, seulement une chaîne simple REPLACE ().

MySQL a l'opérateur REGEXP, mais il ne s'agit que d'un testeur de correspondance et non d'un substitut;

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

Cela ressemble à votre version avec LIKE mais correspond plus précisément. Les deux fonctionneront également mal, nécessitant une analyse complète de la table sans index.

7
bobince

La réponse la plus votée (@ user1467716) n'est pas la plus rapide. Bravo à eux pour avoir donné une proposition de travail à rebondir!

Ceci est une version améliorée:

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;;

Tester 5000 fois sur un serveur de test:

-- 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
5
wally

La façon la plus simple de le faire consiste à utiliser l'opérateur MySQL REGEXP à la fois:

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

Ce n’est pas particulièrement joli, mais MySQL n’a pas de fonction preg_replace donc je pense que c’est le meilleur choix que vous puissiez obtenir.

Personnellement, si ces données uniquement numériques sont si importantes, je conserverais un champ séparé uniquement pour contenir les données supprimées. Vos recherches seront beaucoup plus rapides qu'avec la recherche par expression régulière.

3
Gareth

J'ai une situation similaire, en faisant correspondre les produits à des codes à barres où le code à barres ne stocke parfois pas de chiffres alpha, donc il faut trouver 102.2234 dans la base de données lors de la recherche de 1022234.

En fin de compte, je viens d'ajouter un nouveau champ, reference_number aux tables products, et php supprime les nombres sans alpha du product_number pour renseigner reference_number chaque fois qu'un nouveau produit est ajouté.

Vous devez effectuer une analyse ponctuelle de la table pour créer tous les champs numéro de référence des produits existants.

Vous pouvez ensuite configurer votre index, même si la rapidité n’est pas un facteur déterminant pour cette opération. Il est néanmoins judicieux de maintenir le bon fonctionnement de la base de données afin que cette requête ne l’empêche pas de ralentir les autres requêtes.

1
user396149

Je suis tombé sur cette solution. La première réponse de l'utilisateur 1467716 fonctionnera dans phpMyAdmin avec une petite modification: ajoutez une deuxième balise de délimiteur à la fin du code. 

la version de phpMyAdmin est 4.1.14; MySQL version 5.6.20

J'ai aussi ajouté un limiteur de longueur en utilisant 

DECLARE count INT DEFAULT 0; dans les déclarations

AND count < 5 dans l'instruction WHILE

SET COUNT=COUNT+1; dans l'instruction IF

Forme définitive:

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
modle13

Pour moi, il n'y a pas de regex à remplacer, mais j'ai trouvé cette solution;

--Create a table with numbers
DROP TABLE IF EXISTS ints;
CREATE TABLE ints (i INT UNSIGNED NOT NULL PRIMARY KEY);

INSERT INTO ints (i) VALUES
( 1), ( 2), ( 3), ( 4), ( 5), ( 6), ( 7), ( 8), ( 9), (10),
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);

--Then extract the numbers from the specified column
SELECT
    bar,
    GROUP_CONCAT(SUBSTRING(bar, i, 1) ORDER BY i SEPARATOR '')
FROM foo
JOIN ints ON i BETWEEN 1 AND LENGTH(bar)
WHERE
    SUBSTRING(bar, i, 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
GROUP BY bar;

Cela fonctionne pour moi et j'utilise MySQL 5.0

J'ai aussi trouvé ce lieu qui pourrait aider.

0
Nelson Miranda

Quelle est la taille de la table avec foo? Si elle est petite et que la vitesse importe peu, vous pouvez extraire l'ID de la ligne et foo, la parcourir à l'aide des fonctions de remplacement PHP à comparer, puis extraire les informations souhaitées par numéro de ligne.

Bien sûr, si la table est trop grande, cela ne fonctionnera pas bien.

0
Ben Doom

je pensais partager cela puisque je l'ai construit à partir de la fonction. Je me suis réorganisé pour que je puisse le lire plus facilement (je ne suis que côté serveur).

Vous l'appelez en passant un nom de table et un nom de colonne pour qu'il supprime tous les caractères non numériques existants de cette colonne. J'ai hérité d'un grand nombre de structures de table incorrectes qui placent une tonne de champs int sous la forme 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
#https://stackoverflow.com/questions/287105/mysql-strip-non-numeric-characters-to-compare

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 ;
0
user11122383

essayez cet exemple. Ceci est utilisé pour les numéros de téléphone, mais vous pouvez le modifier selon vos besoins.

   -- function removes non numberic characters from input
-- returne only the numbers in the string

CREATE DEFINER =`root`@`localhost` FUNCTION `remove_alpha`(inputPhoneNumber VARCHAR(50))
  RETURNS VARCHAR(50)
  CHARSET latin1
DETERMINISTIC
  BEGIN


    DECLARE inputLenght INT DEFAULT 0;
    -- var for our iteration 
    DECLARE counter INT DEFAULT 1;
    -- if null is passed, we still return an tempty string
    DECLARE sanitizedText VARCHAR(50) DEFAULT '';
    -- holder of each character during the iteration
    DECLARE oneChar VARCHAR(1) DEFAULT '';


    -- we'll process only if it is not null.
    IF NOT ISNULL(inputPhoneNumber)
    THEN
      SET inputLenght = LENGTH(inputPhoneNumber);
      WHILE counter <= inputLenght DO
        SET oneChar = SUBSTRING(inputPhoneNumber, counter, 1);
        IF (oneChar REGEXP ('^[0-9]+$'))
        THEN
          SET sanitizedText = Concat(sanitizedText, oneChar);
        END IF;

        SET counter = counter + 1;
      END WHILE;
    END IF;

    RETURN sanitizedText;
      END

pour utiliser cette fonction définie par l'utilisateur (UDF) ..__, disons que vous avez une colonne de numéros de téléphone:

col1
(513)983-3983
1-838-338-9898
phone983-889-8383

select remove_alpha(col1) from mytable

Le résultat serait;

5139833983
18383389898
9838898383
0
Hugo R