Ele parece que o MySQL não tem variáveis de matriz. O que devo usar no lugar?
Parece haver duas alternativas sugeridas: Um escalar do tipo conjunto e tabelas temporárias . A pergunta a que vinculei sugere a primeira opção. Mas é uma boa prática usar isso em vez de variáveis de array? Alternativamente, se eu for com conjuntos, a que equivaleria o idioma baseado em conjuntos foreach
?
Você pode fazer isso no 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: Como alternativa, você pode fazer isso 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 ... );
fonte
CALL
isso.UNION ALL
sem usar o procedimento.Tente usar a função FIND_IN_SET () do MySql, por exemplo
SET @c = 'xxx,yyy,zzz'; SELECT * from countries WHERE FIND_IN_SET(countryname,@c);
Observação: você não precisa definir a variável em StoredProcedure se estiver passando parâmetros com valores CSV.
fonte
Hoje em dia, usar um array JSON seria uma resposta óbvia.
Uma vez que esta é uma questão antiga, mas ainda relevante, produzi um pequeno exemplo. As funções JSON estão disponíveis desde mySQL 5.7.x / MariaDB 10.2.3
Eu prefiro esta solução em vez de ELT () porque é realmente mais como um array e este 'array' pode ser reutilizado no código.
Mas tenha cuidado: ele (JSON) é certamente muito mais lento do que usar uma tabela temporária. É apenas mais prático. imo.
Aqui está como usar uma 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
E aqui está um pequeno exemplo para mostrar como funciona em uma função / procedimento:
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();
fonte
Não sei sobre os arrays, mas existe uma maneira de armazenar listas separadas por vírgulas na coluna VARCHAR normal.
E quando você precisa encontrar algo nessa lista, você pode usar a função FIND_IN_SET () .
fonte
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 ;
fonte
Eu sei que esta é uma resposta um pouco tardia, mas recentemente tive que resolver um problema semelhante e pensei que isso pode ser útil para outras pessoas.
fundo
Considere a tabela abaixo chamada 'mytable':
O problema era manter apenas os últimos 3 registros e excluir todos os registros mais antigos cujo systemid = 1 (pode haver muitos outros registros na tabela com outros valores de systemid)
Seria bom se você pudesse fazer isso simplesmente usando a instrução
DELETE FROM mytable WHERE id IN (SELECT id FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3)
No entanto, isso ainda não é suportado no MySQL e se você tentar fazer isso, receberá um erro como
Portanto, é necessária uma solução alternativa pela qual uma matriz de valores é passada para o seletor IN usando a variável. No entanto, como as variáveis precisam ser valores únicos, eu precisaria simular uma matriz . O truque é criar o array como uma lista de valores separados por vírgulas (string) e atribuir isso à variável como segue
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);
O resultado armazenado em @myvar é
Em seguida, o seletor FIND_IN_SET é usado para selecionar a partir da matriz simulada
SELECT * FROM mytable WHERE FIND_IN_SET(id,@myvar);
O resultado final combinado é o seguinte:
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);
Estou ciente de que este é um caso muito específico. No entanto, pode ser modificado para se adequar a praticamente qualquer outro caso em que uma variável precise armazenar uma matriz de valores.
Eu espero que isso ajude.
fonte
Talvez crie uma tabela de memória temporária com colunas (chave, valor) se quiser matrizes associativas. Ter uma tabela de memória é a coisa mais próxima de ter matrizes no mysql
fonte
Veja como eu fiz.
Primeiro, criei uma função que verifica se um Long / Integer / qualquer valor está em uma lista de valores separados por vírgulas:
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;
Portanto, agora você pode pesquisar um ID em uma lista separada por vírgulas de IDs, como esta:
select `is_id_in_ids`('1001,1002,1003',1002);
E você pode usar essa função dentro de uma cláusula WHERE, como esta:
SELECT * FROM table1 WHERE `is_id_in_ids`('1001,1002,1003',table1_id);
Esta foi a única maneira que encontrei de passar um parâmetro "array" para um PROCEDURE.
fonte
O objetivo dos arrays não é ser eficiente? Se você estiver apenas iterando por valores, acho que um cursor em uma tabela temporária (ou permanente) faz mais sentido do que buscar vírgulas, não? Também mais limpo. Procure "mysql DECLARE CURSOR".
Para acesso aleatório, uma tabela temporária com chave primária indexada numericamente. Infelizmente, o acesso mais rápido que você obterá é uma tabela hash, não um acesso aleatório verdadeiro.
fonte
Estou surpreso que nenhuma das respostas menciona ELT / FIELD.
ELT / FIELD funciona de forma muito semelhante a uma matriz, especialmente se você tiver dados estáticos.
FIND_IN_SET também funciona de forma semelhante, mas não tem uma função complementar embutida, mas é fácil de escrever.
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)
fonte
Isso funciona bem para a 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;
fonte
Ambas as versões usando conjuntos não funcionaram para mim (testado com MySQL 5.5). A função ELT () retorna todo o conjunto. Considerando que a instrução WHILE está disponível apenas no contexto PROCEDURE, eu a adicionei à minha solução:
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, não acho que seja uma boa prática. Mesmo que seja realmente necessário, dificilmente é legível e é bastante lento.
fonte
Outra maneira de ver o mesmo 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,');
fonte
Na versão MYSQL após 5.7.x, você pode usar o tipo JSON para armazenar uma matriz. Você pode obter o valor de um array por meio de uma chave via MYSQL.
fonte
Inspirado pela função ELT (número do índice, string1, string2, string3, ...), acho que o exemplo a seguir funciona como um exemplo de array:
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 ajude.
fonte
Aqui está um exemplo de MySQL para loop por uma string delimitada por vírgulas.
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;
isso usa a
get_from_delimiter_split_string
função definida aqui: https://stackoverflow.com/a/59666211/3068233fonte
Uma variável de array é realmente necessária?
Eu pergunto porque originalmente cheguei aqui querendo adicionar uma matriz como uma variável de tabela do MySQL. Eu era relativamente novo no design de banco de dados e estava tentando pensar em como faria isso em uma linguagem de programação típica.
Mas os bancos de dados são diferentes. Eu pensei eu quis um array como uma variável, mas verifica-se que não é apenas uma prática base de dados comum MySQL.
Prática padrão
A solução alternativa para matrizes é adicionar uma tabela adicional e, em seguida, referenciar sua tabela original com uma chave estrangeira.
Como exemplo, vamos imaginar um aplicativo que monitora todos os itens que cada pessoa em uma casa deseja comprar na loja.
Os comandos para criar a tabela que eu imaginei originalmente seriam parecidos com estes:
#doesn't work CREATE TABLE Person( name VARCHAR(50) PRIMARY KEY buy_list ARRAY );
Acho que imaginei buy_list como uma string de itens separados por vírgulas ou algo parecido.
Mas o MySQL não tem um campo do tipo array, então eu realmente precisava de algo assim:
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) );
Aqui definimos uma restrição chamada fk_person. Diz que o campo 'pessoa' em BuyList é uma chave estrangeira. Em outras palavras, é uma chave primária em outra tabela, especificamente o campo 'nome' na tabela Person, que é o que REFERENCES denota.
Também definimos a combinação de pessoa e item para ser a chave primária, mas tecnicamente isso não é necessário.
Finalmente, se você deseja obter todos os itens da lista de uma pessoa, pode executar esta consulta:
SELECT item FROM BuyList WHERE person='John';
Isso dá a você todos os itens da lista de John. Sem matrizes necessárias!
fonte
Se tivermos uma mesa assim
mysql> select * from user_mail; +------------+-------+ | email | user | +------------+-------+- | email1@gmail | 1 | | email2@gmail | 2 | +------------+-------+--------+------------+
e a tabela array:
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 selecionar as linhas da segunda tabela como uma matriz com a função 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 | +------------+-------+--------+------------+-------------+
fonte
Acho que posso melhorar essa resposta. Experimente isto:
O parâmetro 'Pranks' é um CSV. ie. '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 torna a string pesquisada de valores CSV progressivamente mais curta com cada iteração do loop, o que eu acredito que seria melhor para otimização.
fonte
Eu tentaria algo assim para várias coleções. Sou um iniciante em MySQL. Desculpe pelos nomes das funções, não consegui decidir quais nomes seriam os melhores.
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');
fonte
Em vez de salvar os dados como um array ou em uma linha apenas, você deve criar linhas diferentes para cada valor recebido. Isso tornará muito mais simples de entender, em vez de colocar tudo junto.
fonte
Você tentou usar serialize () do PHP? Isso permite que você armazene o conteúdo de um array de variável em uma string que o PHP entende e é segura para o banco de dados (supondo que você tenha escapado primeiro).
$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());
Você também pode fazer exatamente o mesmo sem uma matriz numerada
ou
fonte