表示されたMySQLは、配列変数を持っていないこと。代わりに何を使用すればよいですか?
提案されている2つの選択肢があるようです。セット型スカラーと一時テーブルです。私がリンクした質問は前者を示唆しています。しかし、配列変数の代わりにこれらを使用することは良い習慣ですか?あるいは、セットを使用する場合、セットベースのイディオムは何に相当しforeach
ますか?
表示されたMySQLは、配列変数を持っていないこと。代わりに何を使用すればよいですか?
提案されている2つの選択肢があるようです。セット型スカラーと一時テーブルです。私がリンクした質問は前者を示唆しています。しかし、配列変数の代わりにこれらを使用することは良い習慣ですか?あるいは、セットを使用する場合、セットベースのイディオムは何に相当しforeach
ますか?
回答:
ええと、私は配列変数の代わりに一時テーブルを使用してきました。最高の解決策ではありませんが、機能します。
フィールドを正式に定義する必要はなく、SELECTを使用してフィールドを作成するだけであることに注意してください。
CREATE TEMPORARY TABLE IF NOT EXISTS my_temp_table
SELECT first_name FROM people WHERE last_name = 'Smith';
(「テーブルの作成」を使用せずにselectステートメントから一時テーブルを作成するも参照してください。)
これは、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
...
);
CALL
それを作成できます。
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 / MariaDB10.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()関数を使用できます。
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 ;
これは少し遅い対応だと思いますが、最近、同様の問題を解決する必要があり、これは他の人にも役立つかもしれないと思いました。
バックグラウンド
'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);
これは非常に特殊なケースであることを私は知っています。ただし、変数に値の配列を格納する必要がある他のほとんどの場合に合わせて変更できます。
これがお役に立てば幸いです。
連想配列が必要な場合は、列(キー、値)を含む一時メモリテーブルを作成することもできます。メモリテーブルを持つことは、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に渡すために私が見つけた唯一の方法でした。
配列のポイントは効率的ではありませんか?値を反復処理するだけの場合は、一時(または永続)テーブル上のカーソルの方がコンマを探すよりも理にかなっていると思います。また、よりきれい。「mysqlDECLARECURSOR」を検索します。
ランダムアクセスの場合、数値でインデックス付けされた主キーを持つ一時テーブル。残念ながら、取得する最速のアクセスはハッシュテーブルであり、真のランダムアクセスではありません。
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)
これは、値のリストに対して正常に機能します。
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ステートメントが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__;
正直なところ、これは良い習慣ではないと思います。本当に必要だとしても、これはほとんど読めず、かなり遅いです。
同じ問題を確認する別の方法。お役に立てば幸い
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,');
5.7.x以降のMYSQLバージョンでは、JSON型を使用して配列を格納できます。MYSQLを介してキーによって配列の値を取得できます。
これは、コンマ区切りの文字列をループする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
もともとここに着陸したので、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';
これにより、ジョンのリストにあるすべてのアイテムが表示されます。配列は必要ありません!
そのようなテーブルが1つあれば
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関数を使用して、2番目のテーブルの行を1つの配列として選択できます。
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 |
+------------+-------+--------+------------+-------------+
私はこの答えを改善できると思います。これを試して:
パラメータ「いたずら」はCSVです。すなわち。'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;
この方法では、ループを繰り返すたびに、検索された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'
);
ELT
ます。