簡體   English   中英

如何在 MySQL 中模擬數組變量?

[英]How can I simulate an array variable in MySQL?

MySQL似乎沒有數組變量。 我應該改用什么?


似乎有兩種選擇:一個集合類型的標量臨時表 我鏈接到的問題暗示了前者。 但是使用這些而不是數組變量是一種好習慣嗎? 或者,如果我使用集合,那么基於集合的成語相當於foreach是什么?

好吧,我一直在使用臨時表而不是數組變量。 不是最好的解決方案,但它有效。

請注意,您不需要正式定義它們的字段,只需使用 SELECT 創建它們:

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

(另請參閱不使用 Create Table 從 select 語句創建臨時表。)

您可以使用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
 ...
);

嘗試使用 MySql 的 FIND_IN_SET() 函數,例如

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

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

注意:如果您使用 CSV 值傳遞參數,則不必在 StoredProcedure 中設置變量。

現在使用JSON 數組將是一個明顯的答案。

由於這是一個古老但仍然相關的問題,我制作了一個簡短的例子。 JSON 函數自 mySQL 5.7.x / MariaDB 10.2.3 起可用

與 ELT() 相比,我更喜歡這個解決方案,因為它實際上更像一個數組,並且這個“數組”可以在代碼中重用。

但要小心:它(JSON)肯定比使用臨時表慢得多。 它只是更方便。 海事組織

以下是如何使用 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();

不知道數組,但是有一種方法可以將逗號分隔的列表存儲在普通的 VARCHAR 列中。

當您需要在該列表中查找某些內容時,您可以使用FIND_IN_SET()函數。

我知道這有點晚了,但我最近不得不解決一個類似的問題,並認為這可能對其他人有用。

背景

考慮下面名為“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);

我知道這是一個非常具體的案例。 但是,可以對其進行修改以適應變量需要存儲值數組的任何其他情況。

我希望這個對你有用。

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 ;

如果你想要關聯數組,也許可以創建一個包含列(鍵、值)的臨時內存表。 擁有一個內存表是最接近在 mysql 中擁有數組的東西

這就是我的做法。

首先,我創建了一個函數來檢查 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 的唯一方法。

我很驚訝沒有一個答案提到 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)

數組變量真的有必要嗎?

我問是因為我最初來到這里是想添加一個數組作為 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 表中的“名稱”字段,這就是 REFERENCES 所表示的。

我們還定義了 person 和 item 的組合作為主鍵,但從技術上講,這不是必需的。

最后,如果你想獲取一個人列表中的所有項目,你可以運行這個查詢:

SELECT item FROM BuyList WHERE person='John';

這為您提供了 John 列表中的所有項目。 不需要數組!

這適用於值列表:

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;

這是我使用包含元素列表的變量的解決方案。 您可以在簡單查詢中使用它(無需使用存儲過程或創建表)。

我在網站的其他地方發現了使用 JSON_TABLE 函數的技巧(它在 mysql 8 中有效,我不知道它在其他版本中有效)。

set @x = '1,2,3,4' ;

select c.NAME
from colors c
where 
  c.COD in ( 
    select * 
    from json_table( 
      concat('[',@x,']'),
      '$[*]' columns (id int path '$') ) t ) ;

此外,您可能需要管理一個或多個變量設置為empty_string的情況。 在這種情況下,我添加了另一個技巧(即使xyx 和 y都是空字符串,查詢也不會返回錯誤):

set @x = '' ;
set @y = 'yellow' ;

select c.NAME
from colors 
where 
  if(@y = '', 1 = 1, c.NAME = @y)
  and if(@x = '', 1, c.COD) in ( 
    select * 
    from json_table(
      concat('[',if(@x = '', 1, @x),']'),
      '$[*]' columns (id int path '$') ) t) ;

使用集合的兩個版本都不適合我(用 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__;

老實說,我認為這不是一個好習慣。 即使它真的有必要,這也很難讀,而且速度很慢。

數組不是要高效嗎? 如果您只是遍歷值,我認為臨時(或永久)表上的游標比尋找逗號更有意義,不是嗎? 也更干凈。 查找“mysql DECLARE CURSOR”。

對於隨機訪問具有數字索引主鍵的臨時表。 不幸的是,您將獲得的最快訪問是哈希表,而不是真正的隨機訪問。

看到同樣問題的另一種方式。 希望有幫助

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

如果我們有一張這樣的桌子

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

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

希望它有所幫助。

這是一個用於循環逗號分隔字符串的 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

我想我可以改進這個答案。 嘗試這個:

參數“惡作劇”是一個 CSV。 IE。 '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 值字符串隨着循環的每次迭代而逐漸變短,我相信這對於優化來說會更好。

我會為多個收藏嘗試這樣的事情。 我是 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');

而不是將數據保存為數組或僅保存在一行中,您應該為收到的每個值制作不同的行。 這將使它更容易理解,而不是放在一起。

你試過使用 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'
);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM