如何在MySQL中模拟数组变量?


91

似乎是MySQL没有数组变量。我应该怎么用呢?


似乎有两种选择建议:set-type标量表临时表。我链接的问题暗示了前者。但是,使用这些变量而不是数组变量是一种好习惯吗?或者,如果我使用集合,那么基于集合的成语等效于foreach什么?


2
使用ELT
Pacerier,2015年

1
@Pacerier:您可以链接到更详细的ELT讨论吗,也许还有一个更长的例子?
einpoklum 2015年

Answers:


76

好吧,我一直在使用临时表而不是数组变量。不是最大的解决方案,但它可以工作。

请注意,您不需要正式定义它们的字段,只需使用SELECT创建它们:

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

(另请参见不使用Create Table从select语句创建临时表。)


1
哦:我不知道SQL有这个!!这些表仅对正在运行的所有查询有效。整齐!
iGbanam 2014年

2
@Yasky,前提是您不重用连接。因为确实它将持续整个会话。
Pacerier,2015年

而且您不能重复使用临时表。所以它不是很有用。
约翰


4
@John:是的,可以重复使用它,但是不能在同一查询中使用。
einpoklum 2015年

46

您可以使用WHILE循环在MySQL中实现:

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

4
仅在存储过程中循环不是可能的吗?
einpoklum 2012年

2
是的,它可能在存储过程,函数和触发器内部。
Omesh

1
因此,我无法使用您提供的代码...我需要一些更通用的东西。
einpoklum 2012年

您可以编写一个示例存储过程及其CALL
Omesh

1
我认为不需要数组。您可以使用临时表或UNION ALL不使用过程轻松地进行操作。
Omesh 2012年

28

尝试使用MySql的FIND_IN_SET()函数,例如

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

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

注意:如果要传递带有CSV值的参数,则不必在StoredProcedure中设置SET变量。


注意长度限制,该长度可能会非常低:stackoverflow.com/q/2567000/1333493
Nemo,

18

如今使用JSON数组将是一个显而易见的答案。

由于这是一个古老但仍然相关的问题,因此我举了一个简短的例子。自mySQL 5.7.x / MariaDB 10.2.3起提供JSON函数

与ELT()相比,我更喜欢这种解决方案,因为它实际上更像是一个数组,并且这个“数组”可以在代码中重用。

但请注意:它(JSON)肯定比使用临时表慢得多。它更方便。imo。

这是使用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();

您是说ELT慢还是JSON慢?
卡纳加维卢·苏古玛'18

2
@Kanagavelu Sugumar:在编写JSON时,速度肯定要慢一些。我编辑了答案以使其更清楚。
SeparateReality

16

不了解数组,但是有一种方法可以将逗号分隔的列表存储在普通的VARCHAR列中。

当您需要在该列表中查找内容时,可以使用FIND_IN_SET()函数。


如果我想在集合中查找子集,有什么办法吗?
Akshay Vishnoi

抱歉! 我不确定是否有可能。
wormhit 2013年

您拥有最好的解决方案
加尔文,

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

2
请说明如何使用此代码以及如何回答该问题。
einpoklum

像这里一样,您可以做一个简单的过程,该过程给出该特定字符串的一个一个元素,该元素在oracle中用作数组。
Sagar Gangwal '16

甲骨文?这个问题不是关于Oracle的。同样,看起来您正在过程中定义数组。
einpoklum '16

请检查Syntex,它仅适用于mysql
Sagar Gangwal '16

不要错过最后一个逗号!
Eagle_Eye

4

我知道这是一个较晚的响应,但是最近我不得不解决一个类似的问题,并认为这可能对其他人有用。

背景

考虑下面称为“ 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选择器。但是,由于变量必须是单个值,因此我需要模拟一个array技巧是将数组创建为逗号分隔的值列表(字符串),并将其分配给变量,如下所示

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

我知道这是一个非常具体的情况。但是,可以对其进行修改以适应几乎所有其他需要变量存储值数组的情况。

我希望这个对你有用。


3

如果您要关联数组,也许可以创建一个带有列(键,值)的临时内存表。拥有一个内存表是在mysql中拥有数组最接近的东西


嗯,我不想要关联数组,只想要数组。
einpoklum 2013年

您可以使用只有一列的临时内存表,然后使用游标遍历值,这与使用非声明性编程语言使用数组和for / while循环最接近
Pavle Lekic 2013年

该语言实际上具有此功能,即,没有语法上的原因,您不应该像选择标量那样将向量选择到变量中。
einpoklum 2013年

3

这是我的方法。

首先,我创建了一个函数来检查Long / Integer / whatever值是否在用逗号分隔的值列表中:

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;

因此,现在您可以在以逗号分隔的ID列表中搜索ID,如下所示:

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

您可以在WHERE子句中使用此函数,如下所示:

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

这是我发现将“数组”参数传递给PROCEDURE的唯一方法。


2

数组的目的不是高效的吗?如果您只是遍历值,我认为临时(或永久)表上的游标比查找逗号更有意义,不是吗?也更干净。查找“ mysql DECLARE CURSOR”。

对于随机访问,带有数字索引主键的临时表。不幸的是,您获得的最快访问是哈希表,而不是真正的随机访问。


这是评论,不是答案。我没有指出这就是我要对数组执行的操作。
einpoklum 18/09/27

2

我很惊讶没有答案提到ELT / FIELD。

ELT / FIELD的工作原理与数组非常相似,特别是在您具有静态数据的情况下。

FIND_IN_SET也可以类似地工作,但是没有内置的互补函数,但是编写一个函数很容易。

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)

1

这适用于值列表:

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

两种使用集合的版本都不适用于我(已通过MySQL 5.5测试)。函数ELT()返回整个集合。考虑到WHILE语句仅在PROCEDURE上下文中可用,因此将其添加到我的解决方案中:

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

老实说,我认为这不是一个好习惯。即使确实有必要,这也很难读懂并且很慢。


1

看到相同问题的另一种方法。希望对您有所帮助

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,');

实际上,您在说什么还不清楚。7年前的这个问题有几个相关的答案。考虑删除您的答案或解释您要直接/通常告诉我们的内容,而不是通过示例。
einpoklum

0

在5.7.x之后的MYSQL版本中,可以使用JSON类型存储数组。您可以通过MYSQL通过键获取数组的值。


7
您能举一个我将如何做的例子吗?
einpoklum

0

受函数ELT(索引号,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;

希望对您有所帮助。


0

这是MySQL的一个示例,用于循环以逗号分隔的字符串。

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;

这使用get_from_delimiter_split_string此处定义的功能:https : //stackoverflow.com/a/59666211/3068233


几年前已经建议使用逗号分隔的字符串。
einpoklum

@einpoklum是-这是与他们互动的另一种方式
Ulad Kasach

0

真的需要数组变量吗?

我问是因为我最初登陆这里是想将数组添加为MySQL表变量。我对数据库设计还比较陌生,因此尝试思考如何以典型的编程语言方式进行。

但是数据库是不同的。我以为我想将数组作为变量,但是事实证明,这并不是常见的MySQL数据库实践。

标准惯例

数组的替代解决方案是添加一个附加表,然后使用外键引用原始表。

例如,让我们想象一个应用程序,该应用程序跟踪一个家庭中每个人想要在商店购买的所有商品。

我最初设想的用于创建表的命令应如下所示:

#doesn't work
CREATE TABLE Person(
  name VARCHAR(50) PRIMARY KEY
  buy_list ARRAY
);

我想我将buy_list设想为以逗号分隔的项目或类似内容的字符串。

但是MySQL没有数组类型字段,因此我确实需要这样的东西:

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

在这里,我们定义了一个名为fk_person的约束。它说BuyList中的“ person”字段是外键。换句话说,它是另一个表中的主键,特别是Person表中的“ name”字段,这是REFERENCES所表示的。

我们还将人员和项目的组合定义为主要键,但是从技术上讲这不是必需的。

最后,如果要获取某人列表中的所有项目,则可以运行以下查询:

SELECT item FROM BuyList WHERE person='John';

这为您提供了约翰清单上的所有项目。无需数组!


我接受的解决方案使用临时表。
einpoklum

当然。然后,我将这个答案提供给了像我这样的任何人,该人到此页面寻找创建数组类型的方法-最初不了解为什么数组不是MySQL中的类型。看来是设计使然。这里没有显示一般情况,因此我将我所学的内容包括给其他人以了解通常不需要数组。我不希望您选择我的答案。这取决于用例。您已经为特定用例找到了答案,而我将为一般用例提供此答案。
kraftydevil

0

如果我们有一张这样的桌子

mysql> select * from user_mail;
+------------+-------+
| email      | user | 
+------------+-------+-
| email1@gmail |     1 | 
| email2@gmail |     2 |
+------------+-------+--------+------------+

和数组表:

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 |

我们可以使用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         |
+------------+-------+--------+------------+-------------+

我认为克林顿的答案是重复的。
einpoklum

-2

我认为我可以改善这个答案。试试这个:

参数“恶作剧”是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值字符串在循环的每次迭代中逐渐变短,我认为这对于优化会更好。


您指的是“这个答案”是什么?另外,您也没有CSV文件。
einpoklum 2013年

我不是指CSV文件,而是指CSV值,例如“ 1,2,3,4 ...等”
user2288580

-2

我会为多个集合尝试类似的方法。我是MySQL的初学者。很抱歉,函数名称无法确定最好的名称。

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

我想我理解您的建议,但这非常笨拙,而且可能也非常慢...
einpoklum

如果不进行压力测试,我将无法背书或驳回它。它基本上是在临时表(或普通表)上的主键查找和插入。在遇到问题或找到更好的方法之前,我将使用它。但是我完全在Oracle PL / SQL中做一些奇怪的事情,例如编写编译器和游戏。
戴夫

-2

与其将数据保存为数组或一行,还不如将数据存储为每个收到的值的不同行。这将使理解起来比将它们放在一起要容易得多。


请分享如何做到这一点
尼科·哈泽

-5

您是否尝试过使用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'
);

7
我没有使用PHP,因此与我无关。
einpoklum

1
使用JSON而不是序列化。它更通用且与语言无关。
瑞克·詹姆斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.