web-dev-qa-db-fra.com

Comment puis-je simuler une variable de tableau dans MySQL?

C'est apparaît que MySQL n'a pas de variables de tableau. Que devrais-je utiliser à la place? 


Il semble y avoir deux alternatives suggérées: Un set-type scalar et tables temporaires. La question que j'ai liée suggère l'ancien. Mais est-ce une bonne pratique d’utiliser ces variables au lieu de variables matricielles? Alternativement, si je vais avec des ensembles, quel serait l'idiome basé sur un ensemble équivalent à foreach

63
einpoklum

Eh bien, j'ai utilisé des tables temporaires au lieu de variables de tableau. Pas la meilleure solution, mais ça marche.

Notez que vous n'avez pas besoin de définir formellement leurs champs, il suffit de les créer en utilisant un SELECT:

CREATE TEMPORARY TABLE IF NOT EXISTS my_temp_table
SELECT first_name FROM people WHERE last_name = 'Smith';

(Voir aussi Créer une table temporaire à partir de l'instruction select sans utiliser Create Table .)

61
einpoklum

Vous pouvez y parvenir dans MySQL en utilisant la boucle WHILE:

SET @myArrayOfValue = '2,5,2,23,6,';

WHILE (LOCATE(',', @myArrayOfValue) > 0)
DO
    SET @value = ELT(1, @myArrayOfValue);
    SET @myArrayOfValue= SUBSTRING(@myArrayOfValue, LOCATE(',',@myArrayOfValue) + 1);

    INSERT INTO `EXEMPLE` VALUES(@value, 'hello');
END WHILE;

EDIT: Sinon, vous pouvez le faire avec UNION ALL:

INSERT INTO `EXEMPLE`
(
 `value`, `message`
)
(
 SELECT 2 AS `value`, 'hello' AS `message`
 UNION ALL
 SELECT 5 AS `value`, 'hello' AS `message`
 UNION ALL
 SELECT 2 AS `value`, 'hello' AS `message`
 UNION ALL
 ...
);
33
Omesh

Essayez d’utiliser la fonction FIND_IN_SET () de MySql Ex. 

SET @c = 'xxx,yyy,zzz';

SELECT * from countries 
WHERE FIND_IN_SET(countryname,@c);

Remarque: Vous n'avez pas besoin de définir la variable dans StoredProcedure si vous transmettez un paramètre avec des valeurs CSV.

16
Himalaya Garg

Ne sait pas à propos des tableaux, mais il existe un moyen de stocker des listes séparées par des virgules dans la colonne VARCHAR normale.

Et lorsque vous avez besoin de trouver quelque chose dans cette liste, vous pouvez utiliser la fonction FIND_IN_SET () .

13
wormhit

De nos jours, utiliser un tableau JSON serait une réponse évidente.

Comme il s’agit d’une question ancienne mais toujours pertinente, j’ai produit un bref exemple. Les fonctions JSON sont disponibles depuis mySQL 5.7.x/MariaDB 10.2.3.

Je préfère cette solution à ELT () car elle ressemble plus à un tableau et ce "tableau" peut être réutilisé dans le code.

Mais soyez prudent: le format (JSON) est certainement beaucoup plus lent que l'utilisation d'une table temporaire. C'est juste plus pratique. imo.

Voici comment utiliser un tableau JSON:

SET @myjson = '["gmail.com","mail.ru","arcor.de","gmx.de","t-online.de",
                "web.de","googlemail.com","freenet.de","yahoo.de","gmx.net",
                "me.com","bluewin.ch","hotmail.com","hotmail.de","live.de",
                "icloud.com","hotmail.co.uk","yahoo.co.jp","yandex.ru"]';

SELECT JSON_LENGTH(@myjson);
-- result: 19

SELECT JSON_VALUE(@myjson, '$[0]');
-- result: gmail.com

Et voici un petit exemple pour montrer comment cela fonctionne dans une fonction/procédure:

DELIMITER //
CREATE OR REPLACE FUNCTION example() RETURNS varchar(1000) DETERMINISTIC
BEGIN
  DECLARE _result varchar(1000) DEFAULT '';
  DECLARE _counter INT DEFAULT 0;
  DECLARE _value varchar(50);

  SET @myjson = '["gmail.com","mail.ru","arcor.de","gmx.de","t-online.de",
                "web.de","googlemail.com","freenet.de","yahoo.de","gmx.net",
                "me.com","bluewin.ch","hotmail.com","hotmail.de","live.de",
                "icloud.com","hotmail.co.uk","yahoo.co.jp","yandex.ru"]';

  WHILE _counter < JSON_LENGTH(@myjson) DO
    -- do whatever, e.g. add-up strings...
    SET _result = CONCAT(_result, _counter, '-', JSON_VALUE(@myjson, CONCAT('$[',_counter,']')), '#');

    SET _counter = _counter + 1;
  END WHILE;

  RETURN _result;
END //
DELIMITER ;

SELECT example();
6
SeparateReality

Peut-être créer une table mémoire temporaire avec des colonnes (clé, valeur) si vous voulez des tableaux associatifs. Avoir une table mémoire est la chose la plus proche d'avoir des tableaux dans mysql

3
Pavle Lekic

Voici comment je l’ai fait.

Tout d'abord, j'ai créé une fonction qui vérifie si une valeur Long/Integer/quelle que soit la valeur est dans une liste de valeurs séparée par des virgules:

CREATE DEFINER = 'root'@'localhost' FUNCTION `is_id_in_ids`(
        `strIDs` VARCHAR(255),
        `_id` BIGINT
    )
    RETURNS BIT(1)
    NOT DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY DEFINER
    COMMENT ''
BEGIN

  DECLARE strLen    INT DEFAULT 0;
  DECLARE subStrLen INT DEFAULT 0;
  DECLARE subs      VARCHAR(255);

  IF strIDs IS NULL THEN
    SET strIDs = '';
  END IF;

  do_this:
    LOOP
      SET strLen = LENGTH(strIDs);
      SET subs = SUBSTRING_INDEX(strIDs, ',', 1);

      if ( CAST(subs AS UNSIGNED) = _id ) THEN
        -- founded
        return(1);
      END IF;

      SET subStrLen = LENGTH(SUBSTRING_INDEX(strIDs, ',', 1));
      SET strIDs = MID(strIDs, subStrLen+2, strLen);

      IF strIDs = NULL or trim(strIds) = '' THEN
        LEAVE do_this;
      END IF;

  END LOOP do_this;

   -- not founded
  return(0);

END;

Alors maintenant, vous pouvez rechercher un identifiant dans une liste d'identifiants séparés par des virgules, comme ceci:

select `is_id_in_ids`('1001,1002,1003',1002);

Et vous pouvez utiliser cette fonction dans une clause WHERE, comme ceci:

SELECT * FROM table1 WHERE `is_id_in_ids`('1001,1002,1003',table1_id);

C’est le seul moyen que j’ai trouvé de passer un paramètre "array" à une PROCEDURE.

3
chuckedw
DELIMITER $$
CREATE DEFINER=`mysqldb`@`%` PROCEDURE `abc`()
BEGIN
  BEGIN 
    set @value :='11,2,3,1,'; 
    WHILE (LOCATE(',', @value) > 0) DO
      SET @V_DESIGNATION = SUBSTRING(@value,1, LOCATE(',',@value)-1); 
      SET @value = SUBSTRING(@value, LOCATE(',',@value) + 1); 
      select @V_DESIGNATION;
    END WHILE;
  END;
END$$
DELIMITER ;
3
Sagar Gangwal

Cela fonctionne très bien pour une liste de valeurs:

SET @myArrayOfValue = '2,5,2,23,6,';

WHILE (LOCATE(',', @myArrayOfValue) > 0)
DO
SET @value = ELT(1, @myArrayOfValue);
    SET @STR = SUBSTRING(@myArrayOfValue, 1, LOCATE(',',@myArrayOfValue)-1);
    SET @myArrayOfValue = SUBSTRING(@myArrayOfValue, LOCATE(',', @myArrayOfValue) + 1);

    INSERT INTO `Demo` VALUES(@STR, 'hello');
END WHILE;
1
user2664904

Le point des tableaux n'est-il pas efficace? Si vous ne faites que parcourir des valeurs, je pense qu'un curseur sur une table temporaire (ou permanente) a plus de sens que de rechercher des virgules, non? Aussi plus propre. Recherchez "mysql DECLARE CURSOR".

Pour un accès aléatoire, une table temporaire avec clé primaire indexée numériquement. Malheureusement, l'accès le plus rapide que vous obtiendrez est une table de hachage, et non un véritable accès aléatoire.

1
Amaigus

Les deux versions utilisant des ensembles ne fonctionnaient pas pour moi (testé avec MySQL 5.5). La fonction ELT () renvoie l'ensemble. Considérant que l'instruction WHILE n'est disponible qu'en contexte PROCEDURE, je l'ai ajoutée à ma solution:

DROP PROCEDURE IF EXISTS __main__;

DELIMITER $
CREATE PROCEDURE __main__()
BEGIN
    SET @myArrayOfValue = '2,5,2,23,6,';

    WHILE (LOCATE(',', @myArrayOfValue) > 0)
    DO
        SET @value = LEFT(@myArrayOfValue, LOCATE(',',@myArrayOfValue) - 1);    
        SET @myArrayOfValue = SUBSTRING(@myArrayOfValue, LOCATE(',',@myArrayOfValue) + 1);
    END WHILE;
END;
$
DELIMITER ;

CALL __main__;

Pour être honnête, je ne pense pas que ce soit une bonne pratique. Même si c'est vraiment nécessaire, c'est à peine lisible et assez lent. 

1
jmmeier

Je suis surpris qu'aucune des réponses ne mentionne ELT/FIELD.

ELT/FIELD fonctionne de manière très similaire à un tableau, surtout si vous avez des données statiques.

FIND_IN_SET fonctionne également de la même manière mais n’a pas de fonction complémentaire intégrée, mais il est assez facile d’en écrire une.

mysql> select elt(2,'AA','BB','CC');
+-----------------------+
| elt(2,'AA','BB','CC') |
+-----------------------+
| BB                    |
+-----------------------+
1 row in set (0.00 sec)

mysql> select field('BB','AA','BB','CC');
+----------------------------+
| field('BB','AA','BB','CC') |
+----------------------------+
|                          2 |
+----------------------------+
1 row in set (0.00 sec)

mysql> select find_in_set('CC','AA,BB,CC');
+------------------------------+
| find_in_set('BB','AA,BB,CC') |
+------------------------------+
|                            2 |
+------------------------------+
1 row in set (0.00 sec)

mysql>  SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('AA,BB,CC',',',2),',',-1);
+-----------------------------------------------------------+
| SUBSTRING_INDEX(SUBSTRING_INDEX('AA,BB,CC',',',2),',',-1) |
+-----------------------------------------------------------+
| BB                                                        |
+-----------------------------------------------------------+
1 row in set (0.01 sec)
0
Jongab

Je sais que la réponse est un peu tardive, mais j'ai récemment dû résoudre un problème similaire et je pensais que cela pourrait être utile à d'autres.

Contexte

Considérez le tableau ci-dessous appelé 'mytable':

 Starting table

Le problème était de ne conserver que les 3 derniers enregistrements et de supprimer tous les anciens enregistrements dont systemid = 1 (il pourrait y avoir de nombreux autres enregistrements dans la table avec d'autres valeurs systemid).

Ce serait bien si vous pouviez le faire simplement en utilisant la déclaration 

DELETE FROM mytable WHERE id IN (SELECT id FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3)

Cependant, cela n’est pas encore supporté par MySQL et si vous essayez cela, vous obtiendrez une erreur comme 

...doesn't yet support 'LIMIT & IN/ALL/SOME subquery'

Une solution de contournement est donc nécessaire, selon laquelle un tableau de valeurs est transmis au sélecteur IN à l’aide de variable. Cependant, comme les variables doivent être des valeurs uniques, il faudrait simuler un tableau. L'astuce consiste à créer le tableau sous forme de liste de valeurs (chaîne) séparées par des virgules et à l'affecter à la variable

SET @myvar := (SELECT GROUP_CONCAT(id SEPARATOR ',') AS myval FROM (SELECT * FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3 ) A GROUP BY A.systemid);

Le résultat stocké dans @myvar est

5,6,7

Ensuite, le sélecteur FIND_IN_SET est utilisé pour sélectionner dans le tableau simulé

SELECT * FROM mytable WHERE FIND_IN_SET(id,@myvar);

Le résultat final combiné est le suivant:

SET @myvar := (SELECT GROUP_CONCAT(id SEPARATOR ',') AS myval FROM (SELECT * FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3 ) A GROUP BY A.systemid);
DELETE FROM mytable WHERE FIND_IN_SET(id,@myvar);

Je suis conscient qu'il s'agit d'un cas très spécifique. Cependant, il peut être modifié pour convenir à peu près à tous les autres cas où une variable doit stocker un tableau de valeurs. 

J'espère que ca aide.

0
Clinton

Inspiré par la fonction ELT (numéro d'index, chaîne1, chaîne2, chaîne3,…), je pense que l'exemple suivant fonctionne comme un exemple de tableau:

set @i := 1;
while @i <= 3
do
  insert into table(val) values (ELT(@i ,'val1','val2','val3'...));
set @i = @i + 1;
end while;

J'espère que ça vous aidera.

0
edward

Dans la version MYSQL après la version 5.7.x, vous pouvez utiliser le type JSON pour stocker un tableau. Vous pouvez obtenir la valeur d'un tableau par une clé via MYSQL.

0
nick darn