Как я могу имитировать переменную массива в MySQL?
Это появляется что MySQL не имеет переменных массива. Что я должен использовать вместо этого?
Кажется, есть две предложенные альтернативы: a набор типа скаляр и временные таблицы. Вопрос, с которым я связался, предполагает первое. Но является ли хорошей практикой использовать их вместо переменных массива? В качестве альтернативы, если я пойду с наборами, какой будет идиома на основе набора, эквивалентная foreach
?
16 ответов
Ну, я использовал временные таблицы вместо переменных массива. Не самое лучшее решение, но оно работает.
обратите внимание, что вам не нужно формально определять их поля, просто создайте их с помощью SELECT:
CREATE TEMPORARY TABLE IF NOT EXISTS my_temp_table
SELECT first_name FROM people WHERE last_name = 'Smith';
(см. Также создать временную таблицу из инструкции select без использования Create Table.)
вы можете достичь этого в MySQL, используя 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;
изменить:
В качестве альтернативы вы можете сделать это с помощью 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
...
);
попробуйте использовать функцию Find_in_set () MySql например,
SET @c = 'xxx,yyy,zzz';
SELECT * from countries
WHERE FIND_IN_SET(countryname,@c);
Примечание: вам не нужно устанавливать переменную в StoredProcedure, если вы передаете параметр со значениями CSV.
Не знаю о массивах, но есть способ хранить списки, разделенные запятыми, в обычном столбце VARCHAR.
и когда вам нужно найти что-то в этом списке, вы можете использовать FIND_IN_SET ()
возможно, создайте временную таблицу памяти со столбцами (ключ, значение), если вы хотите ассоциативные массивы. Наличие таблицы памяти-это самое близкое к наличию массивов в mysql
вот как я это сделал.
во-первых, я создал функцию, которая проверяет, является ли длинное / целое / любое значение в списке значений, разделенных запятыми:
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;
Теперь вы можете искать идентификатор в списке идентификаторов, разделенных запятыми, например:
select `is_id_in_ids`('1001,1002,1003',1002);
и вы можете использовать эту функцию внутри предложения WHERE, например:
SELECT * FROM table1 WHERE `is_id_in_ids`('1001,1002,1003',table1_id);
Это был единственный способ передать параметр "array" процедуре.
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 ;
в настоящее время с помощью массив JSON был бы очевидный ответ.
поскольку это старый, но все еще актуальный вопрос, я привел короткий пример. Функции JSON доступны с mySQL 5.7.x / MariaDB 10.2.3
Я предпочитаю это решение над ELT (), потому что это действительно больше похоже на массив, и этот "массив" можно повторно использовать в коде.
но будьте осторожны: это (JSON), безусловно, намного медленнее, чем использование временной таблицы. Its just больше удобный. ММО.
вот как использовать массив 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
и вот небольшой пример, чтобы показать, как он работает в функции / процедуре:
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();
это отлично работает для списка значений:
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;
обе версии с использованием наборов не работали для меня (протестировано с MySQL 5.5). Функция ELT () возвращает весь набор. Учитывая, что оператор WHILE доступен только в контексте процедуры, я добавил его в свое решение:
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__;
честно говоря, я не думаю, что это хорошая практика. Даже если это действительно необходимо, это едва читается и довольно медленно.
разве точка массивов не должна быть эффективной? Если вы просто перебираете значения, я думаю, что курсор на временной (или постоянной) таблице имеет больше смысла, чем поиск запятых, нет? И чище. Поиск "mysql объявляет курсор".
для произвольного доступа временная таблица с численно индексированным первичным ключом. К сожалению, самый быстрый доступ, который вы получите, - это хэш-таблица, а не истинный случайный доступ.
в версии MYSQL после 5.7.x, вы можете использовать тип JSON для хранения массива. Вы можете получить значение массива ключом через MYSQL.
вдохновленный функцией ELT(index number, string1, string2, string3,...), я думаю, что следующий пример работает как пример массива:
set @i := 1;
while @i <= 3
do
insert into table(val) values (ELT(@i ,'val1','val2','val3'...));
set @i = @i + 1;
end while;
надеюсь, что это поможет.
Я знаю, что это немного поздний ответ, но недавно мне пришлось решить аналогичную проблему и подумал, что это может быть полезно для других.
фон
Рассмотрим таблицу ниже под названием "mytable":
проблема заключалась в том, чтобы сохранить только последние 3 записи и удалить любые старые записи, systemid которых=1 (в таблице может быть много других записей с другим systemid значения)
было бы хорошо, если бы вы могли сделать это просто с помощью оператора
DELETE FROM mytable WHERE id IN (SELECT id FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3)
однако это еще не поддерживается в MySQL, и если вы попробуете это, то вы получите ошибку, как
...doesn't yet support 'LIMIT & IN/ALL/SOME subquery'
таким образом, требуется обходной путь, при котором массив значений передается селектору IN с помощью переменной. Однако, поскольку переменные должны быть одиночными значениями, мне нужно будет имитация массива. фокус в том, чтобы создать массив с разделителями разделенный список значений (строка) и присвоить это переменной следующим образом
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);
результат, хранящийся в @myvar, -
5,6,7
далее селектор FIND_IN_SET используется для выберите из моделируемого массива
SELECT * FROM mytable WHERE FIND_IN_SET(id,@myvar);
совокупный конечный результат выглядит следующим образом:
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);
Я знаю, что это очень специфический случай. Однако он может быть изменен в соответствии с любым другим случаем где переменная должна хранить массив значений.
Я надеюсь, что это помогает.
Я думаю, что могу улучшить этот ответ. Попробуйте это:
параметр "шалости" является CSV. то есть. '1,2,3,4.....и т. д.
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;
этот метод делает строку поиска значений CSV постепенно короче с каждой итерацией цикла, что, я считаю, было бы лучше для оптимизации.
вы пробовали использовать PHP serialize()? Это позволяет хранить содержимое массива переменной в строке, которую PHP понимает и безопасен для базы данных (при условии, что вы сначала ее избежали).
$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());
вы также можете сделать то же самое без нумерованного массива
$array2 = array(
'something' => 'something else'
);
или
$array3 = array(
'somethingNew'
);