web-dev-qa-db-ja.com

1つのステートメント内でMySQLの自動インクリメント値にアクセスできますか?

ユーザーのテーブルを含むMySQLデータベースがあります。テーブルの主キーは「userid」で、自動インクリメントフィールドに設定されています。

私がやりたいのは、新しいユーザーをテーブルに挿入するときに、自動インクリメントが別のフィールド「default_assignment」の「userid」フィールドに作成しているのと同じ値を使用することです。

例えば.

私はこのような声明が欲しいです:

INSERT INTO users ('username','default_assignment') VALUES ('barry', value_of_auto_increment_field())

したがって、ユーザー「Barry」を作成すると、「userid」は16として生成されます(たとえば)が、「default_assignment」にも同じ値の16を設定する必要があります。

これを達成する方法はありますか?

ありがとう!

更新:

返信ありがとうございます。 default_assignmentフィールドは冗長ではありません。 default_assigmentは、usersテーブル内の任意のユーザーを参照できます。ユーザーを作成するとき、default_assignmentとして別のユーザーを選択できるフォームがすでにありますが、同じユーザーに設定する必要がある場合があるため、私の質問です。

更新:

わかりました。更新トリガーの提案を試しましたが、それでも機能しません。これが私が作成したトリガーです:

CREATE TRIGGER default_assignment_self BEFORE INSERT ON `users`  
FOR EACH ROW BEGIN
SET NEW.default_assignment = NEW.userid;
END;

ただし、新しいユーザーを挿入する場合、default_assignmentは常に0に設定されます。

ユーザーIDを手動で設定した場合、default_assignmentはユーザーIDに設定されます。

したがって、自動割り当て生成プロセスは、トリガーが有効になった後に明らかに発生します。

22
TheKeys

別のテーブルを作成する必要はありません。max()では、テーブルのauto_increment値に応じて問題が発生します。次のようにします。

CREATE TRIGGER trigger_name BEFORE INSERT ON tbl FOR EACH ROW
BEGIN
   DECLARE next_id INT;
   SET next_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='tbl');
   SET NEW.field=next_id;
END

Next_id変数は通常、他の方法で使用されるため(*)宣言しますが、直接new.field =(select ...)を実行することもできます。

(*) To auto-name an image:
SET NEW.field = CONCAT('image_', next_id, '.gif');
(*) To create a hash:
SET NEW.field = CONCAT( MD5( next_id ) , MD5( FLOOR( Rand( ) *10000000 ) ) );
34
Resegue

これを試して

INSERT INTO users (default_assignment) VALUES (LAST_INSERT_ID()+1);
3
Navrattan Yadav

この場合、last_insert_id()が機能しないことを確認すると、はい、トリガーがそれを実現する唯一の方法になります。

しかし、私は自問します:この機能は何のために必要ですか?なぜユーザーIDを2回保存するのですか?個人的には、このような冗長データを保存するのは好きではありません。おそらく、その不吉なdefault_assignment列をNULLにし、default_assignmentがNULLの場合はアプリケーションコードでユーザーIDを使用することで、アプリケーションコードでこれを解決します。 。

2
pilif

実際、私は上記で提案されたのと同じことをやろうとしました。しかし、Mysqlは、行が実際にコミットされる前に、挿入されたIDを生成しないようです。したがって、NEW.useridは、挿入前トリガーで常に0を返します。

上記は、AFTER INSERTクエリで値を更新できないため、BEFOREINSERTトリガーでない限り機能しません。

Mysqlフォーラムの投稿からこれを処理する唯一の方法は、シーケンスとして追加のテーブルを使用することです。トリガーが外部ソースから値を取得できるようにします。

CREATE TABLE `lritstsequence` (
  `idsequence` int(11) NOT NULL auto_increment,
  PRIMARY KEY  (`idsequence`)
) ENGINE=InnoDB;

CREATE TABLE `lritst` (
`id` int(10) unsigned NOT NULL auto_increment,
`bp_nr` decimal(10,0) default '0',
`descr` varchar(128) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `dir1` (`bp_nr`)
) ENGINE=InnoDB;

DELIMITER $$

DROP TRIGGER /*!50032 IF EXISTS */ `lritst_bi_set_bp_nr`$$

CREATE TRIGGER `lritst_bi_set_bp_nr` BEFORE INSERT ON `lritst`
FOR EACH ROW
BEGIN
    DECLARE secuencia INT;
    INSERT INTO lritstsequence (idsequence) VALUES (NULL);
    SET secuencia = LAST_INSERT_ID();
    SET NEW.id = secuencia;
    SET NEW.bp_nr = secuencia;
END;$$

DELIMITER ;

INSERT INTO lritst (descr) VALUES ('test1');
INSERT INTO lritst (descr) VALUES ('test2');
INSERT INTO lritst (descr) VALUES ('test3');

SELECT * FROM lritst;

Result:

    id   bp_nr  descr 
------  ------  ------
     1       1  test1 
     2       2  test2 
     3       3  test3

これはforums.mysql.com/read.php?99,186171,186241#msg-186241からコピーされましたが、リンクを投稿することはまだ許可されていません。

2
Sias Mey

追加のテーブルなしでこの問題を解決することがわかったのは、次の数値を自分で計算し、それを必要なフィールドに入力することだけです。

CREATE TABLE `Temp` (
  `id` int(11) NOT NULL auto_increment,
  `value` varchar(255) ,
  PRIMARY KEY  (`idsequence`)
) ENGINE=InnoDB;


CREATE TRIGGER temp_before_insert BEFORE INSERT ON `Temp`
FOR EACH ROW 
BEGIN
    DECLARE m INT;

    SELECT IFNULL(MAX(id), 0) + 1 INTO m FROM Temp;
    SET NEW.value = m;
    -- NOT NEEDED but to be save that no other record can be inserted in the meanwhile
    SET NEW.id = m;   
END;
2

上記のトリガーのアイデアを10個の同時スレッドでテストし、挿入を実行しました。約25kの挿入後に、2つまたは3つの重複が1000件以上発生しました。

DROP TABLE IF EXISTS test_table CASCADE;

CREATE TABLE `test_table` (
  `id`             INT         NOT NULL  AUTO_INCREMENT,
  `update_me`      VARCHAR(36),
  `otherdata`      VARCHAR(36) NOT NULL,

  PRIMARY KEY (`id`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8
  COMMENT 'test table for trigger testing';

delimiter $$

DROP TRIGGER IF EXISTS setnum_test_table;
$$
CREATE TRIGGER setnum_test_table
BEFORE INSERT ON test_table FOR EACH ROW
-- SET OLD.update_me = CONCAT(NEW.id, 'xyz');

BEGIN
   DECLARE next_id INT;
   SET next_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='test_table' LOCK IN SHARE MODE );
--    SET NEW.update_me = CONCAT(next_id, 'qrst');
    SET NEW.update_me = next_id;
END

$$

delimiter ;

-- SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='test_table'

INSERT INTO test_table (otherdata) VALUES ('hi mom2');

SELECT count(*) FROM test_table;
SELECT * FROM test_table;

-- select count(*) from (
select * from (
SELECT count(*) as cnt ,update_me FROM test_table group by update_me) q1
where cnt > 1
order by cnt desc

私は10を使用しました:

while true ; do echo "INSERT INTO test_table (otherdata) VALUES ('hi mom2');" | mysql --user xyz testdb ; done &

そして最後のクエリを実行して重複を監視しました

出力例: '3'、 '4217''3'、 '13491''2'、 '10037''2'、 '14658''2'、 '5080''2'、 '14201' .. ..

「ロックインシェアモード」は何も変更しなかったことに注意してください。ありとなしで、ほぼ同じ割合で重複が発生しました。 MySQL AUTO_INCREMENTはPostgresのnext_val()のようには機能せず、並行性に対して安全ではないようです。

0
jeffbuhrt

単純なサブクエリを使用して、これを確実に行うことができます。

INSERT INTO users ('username','default_assignment')
    SELECT 'barry', Auto_increment FROM information_schema.tables WHERE TABLE_NAME='users'
0
cronoklee

基本的に、解決策はResegueと言ったようなものです。
ただし、1つのステートメントでそれが必要な場合は、次のいずれかの方法を使用します。

1。 1つの長いステートメント:

INSERT INTO `t_name`(field) VALUES((SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='t_name'))

または番号付きのテキストの場合:

INSERT INTO `t_name`(field) VALUES(CONCAT('Item No. ',CONVERT((SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='t_name') USING utf8)))

pHPではより明確に見えます:

$pre_name='Item No. ';
$auto_inc_id_qry = "(SELECT AUTO_INCREMENT FROM information_schema.TABLES
    WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$table')";
$new_name_qry = "CONCAT('$pre_name',CONVERT($auto_inc_id_qry USING utf8))";
mysql_query("INSERT INTO `$table`(title) VALUES($new_name_qry)");

2。関数の使用:(まだテストされていません)

CREATE FUNCTION next_auto_inc(table TINYTEXT) RETURNS INT
BEGIN
DECLARE next_id INT;
SELECT AUTO_INCREMENT FROM information_schema.TABLES
    WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=table INTO next_id;
RETURN next_id;
END

INSERT INTO users ('username','default_assignment')
    VALUES ('barry', next_auto_inc('users'))
0
Dani-Br
$ret = $mysqli->query("SELECT Auto_increment FROM information_schema.tables WHERE table_schema = DATABASE() ");l
while ($row = mysqli_fetch_array($ret)) {
    $user_id=$row['Auto_increment'];
}
0
George

この投稿が2010年のものであることは知っていますが、適切な解決策を見つけることができませんでした。カウンターを保持する別のテーブルを作成することで、これを解決しました。列の一意の識別子を生成する必要がある場合は、ストアドプロシージャを呼び出すだけです。

CREATE DEFINER=`root`@`localhost` PROCEDURE `IncrementCounter`(in id varchar(255))
BEGIN
declare x int;
-- begin;
start transaction;
-- Get the last counter (=teller) and mark record for update.
select Counter+1 from tabel.Counter where CounterId=id into x for update;
-- check if given counter exists and increment value, otherwise create it.
if x is null then
    set x = 1;
    insert into tabel.Counters(CounterId, Counter) values(id, x);
else
    update tabel.Counters set Counter = x where CounterId = id;
end if;
-- select the new value and commit the transaction
select x;
commit;
END

'for update'ステートメントは、countersテーブルの行をロックします。これにより、複数のスレッドによる重複が回避されます。

0
Vinzz