web-dev-qa-db-ja.com

MySQLで配列変数をシミュレートするにはどうすればよいですか?

表示される MySQLには配列変数がありません。代わりに何を使うべきですか?


2つの代替案が提案されているようです:Aset-type scalarおよびtemporary tables。私がリンクした質問は前者を示唆しています。しかし、配列変数の代わりにこれらを使用するのは良い習慣ですか?あるいは、セットを使用する場合、foreachと同等のセットベースのイディオムは何でしょうか?

67
einpoklum

さて、配列変数の代わりに一時テーブルを使用しています。最大の解決策ではありませんが、機能します。

正式にフィールドを定義する必要はなく、SELECTを使用して作成するだけです。

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

テーブルの作成を使用せずにselectステートメントから一時テーブルを作成する も参照してください。)

61
einpoklum

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

MySqlのFIND_IN_SET()関数を使用してみてください。

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

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

注:CSV値でパラメーターを渡す場合、StoredProcedureで変数を設定する必要はありません。

19
Himalaya Garg

配列については知りませんが、通常のVARCHAR列にコンマ区切りリストを保存する方法があります。

そして、そのリストで何かを見つける必要があるとき、 FIND_IN_SET() 関数を使用できます。

15
wormhit

現在、JSON配列を使用することは明らかな答えでしょう。

これは古くても関連する質問なので、簡単な例を作成しました。 JSON関数はmySQL 5.7.x/MariaDB 10.2.3以降で使用可能です

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();
9
SeparateReality
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 ;
4
Sagar Gangwal

連想配列が必要な場合は、列(キー、値)で一時メモリテーブルを作成することもできます。メモリテーブルを持つことは、mysqlで配列を持つことに最も近いものです

3
Pavle Lekic

これが私がやった方法です。

最初に、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に渡す唯一の方法でした。

3
chuckedw

これは、値のリストに対して正常に機能します。

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
user2664904

配列のポイントは効率的ではありませんか?値を繰り返し処理しているだけなら、コンマを探すよりも一時(または永続)テーブルのカーソルの方が意味があると思いますか?またきれい。 「mysql DECLARE CURSOR」を検索します。

ランダムアクセスの場合、数値インデックス付きの主キーを持つ一時テーブル。残念ながら、最速のアクセスはハッシュテーブルであり、真のランダムアクセスではありません。

1
Amaigus

これは少し遅い応答であることは知っていますが、最近、同様の問題を解決する必要があり、これは他の人にも役立つかもしれないと考えました。

背景

「mytable」と呼ばれる以下の表を検討してください。

Starting table

問題は、最新の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);

これは非常に特殊なケースであることを認識しています。ただし、変数に値の配列を格納する必要がある他のケースに合わせて変更できます。

これが役立つことを願っています。

1
Clinton

セットを使用する両方のバージョンは、私にとっては機能しませんでした(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
jmmeier

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('CC','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)
0
Jongab

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

お役に立てば幸いです。

0
edward

5.7.x以降のMYSQLバージョンでは、JSON型を使用して配列を保存できます。 MYSQLを介してキーで配列の値を取得できます。

0
nick darn