Se parece que MySQL no tiene variables de matriz. ¿Qué debo usar en su lugar?
Parece que se sugieren dos alternativas: un escalar de tipo conjunto y tablas temporales . La pregunta a la que me vinculé sugiere lo primero. Pero, ¿es una buena práctica usar estos en lugar de variables de matriz? Alternativamente, si voy con conjuntos, ¿cuál sería el equivalente idiomático basado en conjuntos foreach
?
ELT
.Respuestas:
Bueno, he estado usando tablas temporales en lugar de variables de matriz. No es la mejor solución, pero funciona.
Tenga en cuenta que no necesita definir formalmente sus campos, solo créelos usando un SELECT:
CREATE TEMPORARY TABLE IF NOT EXISTS my_temp_table SELECT first_name FROM people WHERE last_name = 'Smith';
(Consulte también Crear tabla temporal a partir de la instrucción de selección sin usar Crear tabla ).
fuente
Puede lograr esto en MySQL usando
WHILE
loop: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;
EDITAR: Alternativamente, puede hacerlo usando
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 ... );
fuente
CALL
eso.UNION ALL
sin usar procedimiento.Intente usar la función FIND_IN_SET () de MySql, por ejemplo
SET @c = 'xxx,yyy,zzz'; SELECT * from countries WHERE FIND_IN_SET(countryname,@c);
Nota: No tiene que SET variable en StoredProcedure si está pasando parámetros con valores CSV.
fuente
Hoy en día, usar una matriz JSON sería una respuesta obvia.
Dado que esta es una pregunta antigua pero aún relevante, presenté un breve ejemplo. Las funciones JSON están disponibles desde mySQL 5.7.x / MariaDB 10.2.3
Prefiero esta solución sobre ELT () porque en realidad es más como una matriz y esta 'matriz' se puede reutilizar en el código.
Pero tenga cuidado: es (JSON) ciertamente mucho más lento que usar una tabla temporal. Es más útil. imo.
A continuación, se explica cómo utilizar una matriz 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
Y aquí un pequeño ejemplo para mostrar cómo funciona en una función / procedimiento:
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();
fuente
No conozco las matrices, pero hay una forma de almacenar listas separadas por comas en la columna VARCHAR normal.
Y cuando necesite encontrar algo en esa lista, puede usar la función FIND_IN_SET () .
fuente
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 ;
fuente
Sé que esta es una respuesta un poco tardía, pero recientemente tuve que resolver un problema similar y pensé que esto podría ser útil para otros.
Antecedentes
Considere la siguiente tabla llamada 'mytable':
El problema era mantener solo los últimos 3 registros y eliminar los registros más antiguos cuyo systemid = 1 (podría haber muchos otros registros en la tabla con otros valores de systemid)
Sería bueno si pudiera hacer esto simplemente usando la declaración
DELETE FROM mytable WHERE id IN (SELECT id FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3)
Sin embargo, esto aún no es compatible con MySQL y si lo intentas, obtendrás un error como
Por lo tanto, se necesita una solución alternativa mediante la cual se pase una matriz de valores al selector IN mediante la variable. Sin embargo, como las variables deben ser valores únicos, necesitaría simular una matriz . El truco consiste en crear la matriz como una lista de valores (cadena) separados por comas y asignar esto a la variable de la siguiente manera
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);
El resultado almacenado en @myvar es
A continuación, el selector FIND_IN_SET se utiliza para seleccionar de la matriz simulada
SELECT * FROM mytable WHERE FIND_IN_SET(id,@myvar);
El resultado final combinado es el siguiente:
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);
Soy consciente de que este es un caso muy específico. Sin embargo, se puede modificar para adaptarse a casi cualquier otro caso en el que una variable necesite almacenar una matriz de valores.
Espero que esto ayude.
fuente
Tal vez cree una tabla de memoria temporal con columnas (clave, valor) si desea matrices asociativas. Tener una tabla de memoria es lo más parecido a tener matrices en mysql
fuente
Así es como lo hice.
Primero, creé una función que verifica si un valor largo / entero / cualquier valor está en una lista de valores separados por comas:
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;
Así que ahora puede buscar un ID en una lista de ID separados por comas, como esta:
select `is_id_in_ids`('1001,1002,1003',1002);
Y puede usar esta función dentro de una cláusula WHERE, como esta:
SELECT * FROM table1 WHERE `is_id_in_ids`('1001,1002,1003',table1_id);
Esta fue la única forma que encontré de pasar un parámetro de "matriz" a un PROCEDIMIENTO.
fuente
¿No es el objetivo de las matrices ser eficientes? Si solo está iterando a través de valores, creo que un cursor en una tabla temporal (o permanente) tiene más sentido que buscar comas, ¿no? También más limpia. Busque "mysql DECLARE CURSOR".
Para acceso aleatorio, una tabla temporal con clave primaria indexada numéricamente. Desafortunadamente, el acceso más rápido que obtendrá es una tabla hash, no un verdadero acceso aleatorio.
fuente
Me sorprende que ninguna de las respuestas mencione ELT / FIELD.
ELT / FIELD funciona de manera muy similar a una matriz, especialmente si tiene datos estáticos.
FIND_IN_SET también funciona de manera similar, pero no tiene una función complementaria incorporada, pero es bastante fácil escribir una.
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('BB','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)
fuente
Esto funciona bien para la lista de valores:
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;
fuente
Ambas versiones que usaban conjuntos no funcionaron para mí (probadas con MySQL 5.5). La función ELT () devuelve el conjunto completo. Teniendo en cuenta que la declaración WHILE solo está disponible en el contexto PROCEDURE, la agregué a mi solución:
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__;
Para ser honesto, no creo que sea una buena práctica. Incluso si es realmente necesario, esto es apenas legible y bastante lento.
fuente
Otra forma de ver el mismo problema. Espero útil
DELIMITER $$ CREATE PROCEDURE ARR(v_value VARCHAR(100)) BEGIN DECLARE v_tam VARCHAR(100); DECLARE v_pos VARCHAR(100); CREATE TEMPORARY TABLE IF NOT EXISTS split (split VARCHAR(50)); SET v_tam = (SELECT (LENGTH(v_value) - LENGTH(REPLACE(v_value,',','')))); SET v_pos = 1; WHILE (v_tam >= v_pos) DO INSERT INTO split SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(v_value,',',v_pos),',', -1); SET v_pos = v_pos + 1; END WHILE; SELECT * FROM split; DROP TEMPORARY TABLE split; END$$ CALL ARR('1006212,1006404,1003404,1006505,444,');
fuente
En la versión MYSQL posterior a 5.7.x, puede usar el tipo JSON para almacenar una matriz. Puede obtener el valor de una matriz mediante una clave a través de MYSQL.
fuente
Inspirado por la función ELT (número de índice, cadena1, cadena2, cadena3,…), creo que el siguiente ejemplo funciona como ejemplo de matriz:
set @i := 1; while @i <= 3 do insert into table(val) values (ELT(@i ,'val1','val2','val3'...)); set @i = @i + 1; end while;
Espero que te ayude.
fuente
Aquí hay un ejemplo de MySQL para recorrer una cadena delimitada por comas.
DECLARE v_delimited_string_access_index INT; DECLARE v_delimited_string_access_value VARCHAR(255); DECLARE v_can_still_find_values_in_delimited_string BOOLEAN; SET v_can_still_find_values_in_delimited_string = true; SET v_delimited_string_access_index = 0; WHILE (v_can_still_find_values_in_delimited_string) DO SET v_delimited_string_access_value = get_from_delimiter_split_string(in_array, ',', v_delimited_string_access_index); -- get value from string SET v_delimited_string_access_index = v_delimited_string_access_index + 1; IF (v_delimited_string_access_value = '') THEN SET v_can_still_find_values_in_delimited_string = false; -- no value at this index, stop looping ELSE -- DO WHAT YOU WANT WITH v_delimited_string_access_value HERE END IF; END WHILE;
esto usa la
get_from_delimiter_split_string
función definida aquí: https://stackoverflow.com/a/59666211/3068233fuente
¿Es realmente necesaria una variable de matriz?
Lo pregunto porque originalmente aterricé aquí con el deseo de agregar una matriz como variable de tabla MySQL. Era relativamente nuevo en el diseño de bases de datos y estaba tratando de pensar en cómo lo haría en un lenguaje de programación típico.
Pero las bases de datos son diferentes. Yo pensé que quería una matriz como una variable, pero resulta que no es sólo una práctica base de datos MySQL común.
Práctica estándar
La solución alternativa a las matrices es agregar una tabla adicional y luego hacer referencia a su tabla original con una clave externa.
Como ejemplo, imaginemos una aplicación que realiza un seguimiento de todos los artículos que todas las personas de un hogar quieren comprar en la tienda.
Los comandos para crear la tabla que imaginé originalmente se verían así:
#doesn't work CREATE TABLE Person( name VARCHAR(50) PRIMARY KEY buy_list ARRAY );
Creo que imaginé buy_list como una cadena de elementos separados por comas o algo así.
Pero MySQL no tiene un campo de tipo de matriz, por lo que realmente necesitaba algo como esto:
CREATE TABLE Person( name VARCHAR(50) PRIMARY KEY ); CREATE TABLE BuyList( person VARCHAR(50), item VARCHAR(50), PRIMARY KEY (person, item), CONSTRAINT fk_person FOREIGN KEY (person) REFERENCES Person(name) );
Aquí definimos una restricción llamada fk_person. Dice que el campo 'persona' en BuyList es una clave externa. En otras palabras, es una clave principal en otra tabla, específicamente el campo 'nombre' en la tabla Persona, que es lo que indica REFERENCIAS.
También definimos la combinación de persona y elemento como la clave principal, pero técnicamente no es necesario.
Finalmente, si desea obtener todos los elementos de la lista de una persona, puede ejecutar esta consulta:
SELECT item FROM BuyList WHERE person='John';
Esto le da todos los elementos de la lista de John. ¡No se necesitan matrices!
fuente
Si tenemos una mesa como esa
mysql> select * from user_mail; +------------+-------+ | email | user | +------------+-------+- | email1@gmail | 1 | | email2@gmail | 2 | +------------+-------+--------+------------+
y la tabla de matriz:
mysql> select * from user_mail_array; +------------+-------+-------------+ | email | user | preferences | +------------+-------+-------------+ | email1@gmail | 1 | 1 | | email1@gmail | 1 | 2 | | email1@gmail | 1 | 3 | | email1@gmail | 1 | 4 | | email2@gmail | 2 | 5 | | email2@gmail | 2 | 6 |
Podemos seleccionar las filas de la segunda tabla como una matriz con la función CONCAT:
mysql> SELECT t1.*, GROUP_CONCAT(t2.preferences) AS preferences FROM user_mail t1,user_mail_array t2 where t1.email=t2.email and t1.user=t2.user GROUP BY t1.email,t1.user; +------------+-------+--------+------------+-------------+ | email | user | preferences | +------------+-------+--------+------------+-------------+ |email1@gmail | 1 | 1,3,2,4 | |email2@gmail | 2 | 5,6 | +------------+-------+--------+------------+-------------+
fuente
Creo que puedo mejorar esta respuesta. Prueba esto:
El parámetro 'Bromas' es un CSV. es decir. '1,2,3,4 ..... etc'
CREATE PROCEDURE AddRanks( IN Pranks TEXT ) BEGIN DECLARE VCounter INTEGER; DECLARE VStringToAdd VARCHAR(50); SET VCounter = 0; START TRANSACTION; REPEAT SET VStringToAdd = (SELECT TRIM(SUBSTRING_INDEX(Pranks, ',', 1))); SET Pranks = (SELECT RIGHT(Pranks, TRIM(LENGTH(Pranks) - LENGTH(SUBSTRING_INDEX(Pranks, ',', 1))-1))); INSERT INTO tbl_rank_names(rank) VALUES(VStringToAdd); SET VCounter = VCounter + 1; UNTIL (Pranks = '') END REPEAT; SELECT VCounter AS 'Records added'; COMMIT; END;
Este método hace que la cadena buscada de valores CSV sea cada vez más corta con cada iteración del ciclo, lo que creo que sería mejor para la optimización.
fuente
Intentaría algo como esto para varias colecciones. Soy un principiante de MySQL. Lo siento por los nombres de las funciones, no pude decidir qué nombres serían los mejores.
delimiter // drop procedure init_ // create procedure init_() begin CREATE TEMPORARY TABLE if not exists val_store( realm varchar(30) , id varchar(30) , val varchar(255) , primary key ( realm , id ) ); end; // drop function if exists get_ // create function get_( p_realm varchar(30) , p_id varchar(30) ) returns varchar(255) reads sql data begin declare ret_val varchar(255); declare continue handler for 1146 set ret_val = null; select val into ret_val from val_store where id = p_id; return ret_val; end; // drop procedure if exists set_ // create procedure set_( p_realm varchar(30) , p_id varchar(30) , p_val varchar(255) ) begin call init_(); insert into val_store (realm,id,val) values (p_realm , p_id , p_val) on duplicate key update val = p_val; end; // drop procedure if exists remove_ // create procedure remove_( p_realm varchar(30) , p_id varchar(30) ) begin call init_(); delete from val_store where realm = p_realm and id = p_id; end; // drop procedure if exists erase_ // create procedure erase_( p_realm varchar(30) ) begin call init_(); delete from val_store where realm = p_realm; end; // call set_('my_array_table_name','my_key','my_value'); select get_('my_array_table_name','my_key');
fuente
En lugar de guardar datos como una matriz o en una sola fila, debe crear filas diferentes para cada valor recibido. Esto hará que sea mucho más sencillo de entender en lugar de poner todo junto.
fuente
¿Ha intentado usar serialize () de PHP? Eso le permite almacenar el contenido de la matriz de una variable en una cadena que PHP entiende y es segura para la base de datos (suponiendo que la haya escapado primero).
$array = array( 1 => 'some data', 2 => 'some more' ); //Assuming you're already connected to the database $sql = sprintf("INSERT INTO `yourTable` (`rowID`, `rowContent`) VALUES (NULL, '%s')" , serialize(mysql_real_escape_string($array, $dbConnection))); mysql_query($sql, $dbConnection) or die(mysql_error());
También puede hacer exactamente lo mismo sin una matriz numerada
o
fuente