MySQL'de 'yoksa' nasıl eklenir?


838

Google'la başladım ve muteks tablolarından bahseden bu makaleyi buldum .

Yaklaşık 14 milyon kayıt içeren bir masam var. Aynı formatta daha fazla veri eklemek istersem, eklemek istediğim kaydın bir çift sorgu kullanmadan mevcut olmadığından emin olmanın bir yolu var mı (yani, kontrol etmek için bir sorgu ve eklemek için bir sonuç kümesi boş)?

Alandaki bir uniquekısıtlama, insertzaten oradaysa başarısızlığın garantisini veriyor mu?

Görünüşe göre sadece bir kısıtlama ile, ben php yoluyla eklemek sorunu, script dolandırıcı.



Auto_inc değerlerini yakmama hakkında tartışma için stackoverflow.com/questions/44550788/… adresine bakın .
Rick James

@RickJames - bu ilginç bir q .. ama doğrudan bu q ile ilgili olduğundan emin değilim :)
warren

1
Bir yorumda bahsedildi ve diğer Soru bu Sorunun "tam bir kopya" olduğunu iddia etti. Bu yüzden, soruları başkalarının yararına bir araya getirmenin iyi bir fikir olduğunu hissettim.
Rick James

1
Oh, asla yan çubuğa bakmayı düşünmüyorum.
Rick James

Yanıtlar:


806

kullanım INSERT IGNORE INTO table

bkz. http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html

INSERT … ON DUPLICATE KEY UPDATEsözdizimi de var , dev.mysql.com adresinde açıklama bulabilirsiniz.


Google'ın web önbelleğine göre bogdan.org.ua'dan yayın yapın :

18 Ekim 2007

Başlamak için: en son MySQL'den itibaren, başlıkta sunulan sözdizimi mümkün değildir. Ancak, mevcut işlevleri kullanarak beklenenleri gerçekleştirmenin birkaç kolay yolu vardır.

3 olası çözüm vardır: DUPLICATE KEY UPDATE ÜZERİNE INSERT IGNORE, REPLACE veya INSERT… kullanma.

Bir masamız olduğunu hayal edin:

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Şimdi, Ensembl'den transkript meta verilerini içe aktaran otomatik bir boru hattımız olduğunu ve çeşitli nedenlerle boru hattının herhangi bir yürütme adımında kırılabileceğini hayal edin. Bu nedenle, iki şeyi sağlamalıyız:

  1. boru hattının tekrar tekrar yürütülmesi veritabanımızı yok etmeyecektir

  2. yinelenen yürütmeler 'yinelenen birincil anahtar' hataları nedeniyle ölmeyecek.

Yöntem 1: REPLACE kullanma

Çok basit:

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Kayıt varsa, üzerine yazılır; henüz mevcut değilse oluşturulacaktır. Ancak, bu yöntemi kullanmak bizim durumumuz için verimli değildir: mevcut kayıtların üzerine yazmamız gerekmez, bunları atlamak iyidir.

Yöntem 2: INSERT IGNORE kullanma Ayrıca çok basit:

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Burada, 'ensembl_transcript_id' veritabanında zaten varsa, sessizce atlanır (yoksayılır). (Daha kesin olmak gerekirse, MySQL referans kılavuzundan bir alıntı: “IGNORE anahtar sözcüğünü kullanırsanız, INSERT ifadesini yürütürken oluşan hatalar bunun yerine uyarı olarak kabul edilir. Örneğin, IGNORE olmadan mevcut bir UNIQUE dizinini çoğaltan bir satır veya tablodaki PRIMARY KEY değeri yinelenen anahtar hatasına neden oluyor ve deyim iptal ediliyor. ”.) Kayıt henüz yoksa, oluşturulur.

Bu ikinci yöntemin, başka herhangi bir sorun olması durumunda sorgunun kürtaj yapılmaması da dahil olmak üzere çeşitli potansiyel zayıflıkları vardır (kılavuza bakınız). Bu nedenle, daha önce IGNORE anahtar sözcüğü olmadan test edilmişse kullanılmalıdır.

Yöntem 3: INSERT… kullanarak DUPLICATE KEY UPDATE:

Üçüncü seçenek kullanmaktır INSERT … ON DUPLICATE KEY UPDATE sözdizimini ve UPDATE bölümünde 0 + 0 hesaplaması gibi anlamsız (boş) bir işlem yapmayın (Geoffray, bu işlemi yoksaymak için MySQL optimizasyon motorunun id = id atamasını yapmanızı önerir). Bu yöntemin avantajı, yalnızca yinelenen önemli olayları yok sayması ve yine de diğer hatalardan vazgeçmesidir.

Son bir uyarı olarak: Bu yazı Xaprb'den ilham aldı. Ben de esnek SQL sorguları yazma konusunda onun diğer yazı danışmak tavsiye ederim.


3
ve betiği hızlandırmak için bunu "gecikmeli" ile birleştirebilir miyim?
warren

3
evet, gecikmeli insert sizin için işleri hızlandırabilir. denemek o
knittl

32
Evet ve REPLACE
INTO'YU

10
INSERT … ON DUPLICATE KEY UPDATEauto_incrementsütunları ve diğer verileri koruyarak satırı silmediği için daha iyidir .
redolent

15
Herkesi bilgilendirmek için. INSERT … ON DUPLICATE KEY UPDATEYöntemi kullanmak , ekleme başarısız olan AUTO_INCREMENT sütununu artırır. Muhtemelen gerçekten başarısız olmadığı için değil, GÜNCELLENDİ.
not2qubit

216

Çözüm:

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 

Açıklama:

En içteki sorgu

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1

WHERE NOT EXISTS-condition , kullanılan verilere sahip bir satır olup olmadığını algılar. Bu tür bir satır bulunduktan sonra sorgu durabilir, dolayısıyla LIMIT 1(mikro-optimizasyon atlanabilir).

Ara sorgu

SELECT 'stuff for value1', 'stuff for value2' FROM DUAL

eklenecek değerleri temsil eder. DUALtüm Oracle veritabanlarında varsayılan olarak bulunan özel bir satır, bir sütun tablosu anlamına gelir (bkz. https://en.wikipedia.org/wiki/DUAL_table ). MySQL-Server 5.7.26 sürümünde, atlarken geçerli bir sorgu aldım FROM DUAL, ancak eski sürümler (5.5.60 gibi) FROMbilgi gerektiriyor gibi görünüyor . KullanarakWHERE NOT EXISTSEn içteki sorgu eşleşen veriler bulduysa, ara sorguyu boş bir sonuç kümesi döndürür.

Dış sorgu

INSERT INTO `table` (`value1`, `value2`) 

ara sorgu tarafından döndürülürse verileri ekler.


4
bunun nasıl kullanılacağı hakkında daha fazla bilgi verebilir misiniz?
Alex V

36
Bu varyant, tabloda benzersiz bir anahtar yoksa ( INSERT IGNOREve INSERT ON DUPLICATE KEYbenzersiz anahtar kısıtlamaları gerektiriyorsa) uygundur.
rabudde

2
Satır 2'de "tablodan" yerine "ikili'den" kullanırsanız, "sınır 1" deyimine ihtiyacınız yoktur.
Zengin

6
Ya aynı stuff for value1ve stuff for value2aynı ise? Bu birDuplicate column name
Robin

1
Ben de alt sorgular SELECT 1yerine çok tercih ederim SELECT *. Bunun bir indeks tarafından karşılanması çok daha muhtemeldir.
Arth

58

yinelenen anahtar güncellemesinde veya yoksayma ekle MySQL ile uygulanabilir çözümler olabilir.


Nın bir örneği yinelenen anahtar güncelleme üzerinde mysql.com dayalı güncelleme

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

Örnek ekin görmezden mysql.com göre

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

Veya:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

Veya:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

24

Bir istisna kabul edilebilirse, herhangi bir basit kısıtlama işi yapmalıdır. Örnekler:

  • yedek değilse birincil anahtar
  • bir sütunda benzersiz kısıtlama
  • çok sütunlu benzersiz kısıtlama

Üzgünüm, bu aldatıcı bir şekilde basit görünüyor. Bizimle paylaştığınız bağlantıyla yüzleşmenin kötü göründüğünü biliyorum. ;-(

Ama ben yine de bu cevabı verdim, çünkü ihtiyacınızı karşılıyor gibi görünüyor. (Değilse, gereksinimlerinizi güncellemenizi tetikleyebilir, bu da "İyi Bir Şey" (TM) olacaktır).

Düzenlendi : Bir ekleme veritabanı benzersiz kısıtlamasını kıracaksa, sürücü tarafından aktarılan veritabanı düzeyinde bir istisna atılır. Senaryonuzu kesinlikle durduracaktır, başarısızlıkla. PHP bu durumda adres mümkün olmalıdır ...


1
soruya bir açıklama ekledim - cevabınız hala geçerli mi?
warren

2
İnanıyorum. Benzersiz bir kısıtlama yanlış kesici uçların arızalanmasına neden olur. Not: Kodunuzdaki bu hatayla uğraşmanız gerekir, ancak bu oldukça standarttır.
KLE

1
Şimdilik ben kabul çözümü ile sopa gidiyorum - ama daha fazla uygulama olarak vb INSERT hatalarını ele içine bakacağız büyür
warren

3
INSERT IGNOREtemel olarak tüm hataları uyarılarla değiştirir, böylece betiğiniz kesilmez. Ardından komutla tüm uyarıları görüntüleyebilirsiniz SHOW WARNINGS. Ve bir başka önemli not : UNIQUE kısıtlamaları NULL değerleri ile çalışmaz, yani. row1 (1, NULL) ve row2 (1, NULL) her ikisi de eklenir (birincil anahtar gibi başka bir kısıtlama bozuk olmadıkça). Talihsiz.
Simon East

18

Burada, yalnızca belirtilen tüm sütun değerleri tabloda zaten yoksa bir satır ekleyecek bir PHP işlevi bulunmaktadır.

  • Sütunlardan biri farklıysa, satır eklenir.

  • Tablo boşsa, satır eklenir.

  • Belirtilen tüm sütunların belirtilen değerlere sahip olduğu bir satır varsa, satır eklenmez.

    function insert_unique($table, $vars)
    {
      if (count($vars)) {
        $table = mysql_real_escape_string($table);
        $vars = array_map('mysql_real_escape_string', $vars);
    
        $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
        $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
        $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
    
        foreach ($vars AS $col => $val)
          $req .= "`$col`='$val' AND ";
    
        $req = substr($req, 0, -5) . ") LIMIT 1";
    
        $res = mysql_query($req) OR die();
        return mysql_insert_id();
      }
    
      return False;
    }

Örnek kullanım:

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>

5
Büyük miktarda eklemeniz varsa oldukça pahalı.
Эџad Дьdulяңмaи

belirli kontroller eklemeniz gerekiyorsa doğru, ancak etkili
Charles Forest

1
Uyarı: mysql_* PHP 5.5.0'dan itibaren uzantı kullanımdan kaldırılmıştır ve PHP 7.0.0'dan itibaren kaldırılmıştır. Bunun yerine, mysqli veya PDO_MySQL uzantısı kullanılmalıdır. Ayrıca MySQL API'sini seçerken daha fazla yardım için MySQL API'sine Genel Bakış konusuna bakın .
Dharman

17
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Kayıt varsa, üzerine yazılır; henüz mevcut değilse oluşturulacaktır.


10
REPLACEsatırı silip güncelleme yerine ekleme yapabilir. Yan etki, kısıtlamaların diğer nesneleri ve silme tetikleyicilerinin tetiklenebilmesidir.
xmedeko

1
MySQL kılavuzundan: "REPLACE yalnızca bir tablonun PRIMARY KEY veya UNIQUE dizini varsa mantıklıdır. Aksi takdirde, INSERT ile eşdeğer olur, çünkü yeni bir satırın diğerini çoğaltıp kopyalamayacağını belirlemek için kullanılacak bir dizin yoktur."
BurninLeo

16

Takip etmeyi dene:

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END

5
Deneyin Bu cevaplar StackOverflow'da düşük değerlidir, çünkü OP'yi ve gelecekteki binlerce araştırmacıyı eğitmek için çok az şey yaparlar. Lütfen bu cevabı, çözümün nasıl çalıştığını ve neden iyi bir fikir olduğunu içerecek şekilde düzenleyin.
mickmackusa

1
Eşleşen alanların anahtar olmaması durumunda mükemmel çözüm ..!
Leo

6

Veya UNIQUEile kontrol edebileceğiniz bir dizininiz varsa, bunun nasıl çözüleceğini kapsayan birkaç cevap vardır . Bu her zaman böyle değildir ve uzunluk sınırlaması (1000 bayt) olduğu için bunu değiştiremeyebilirsiniz. Örneğin, WordPress ( ) içindeki meta verilerle çalışmak zorunda kaldım .ON DUPLICATE KEYINSERT IGNOREUNIQUEwp_postmeta

Sonunda iki sorgu ile çözdüm:

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);

Sorgu 1, UPDATEsöz konusu veri kümesi orada olmadığında hiçbir etkisi olmayan normal bir sorgudur. Sorgu 2 birINSERT bağlı olan bir şeydir NOT EXISTS, yani INSERTyalnızca veri kümesi olmadığında yürütülür.


2

Dikkate değer bir şey, INSERT IGNORE'un ifadenin başarılı olup olmadığı gibi normal bir INSERT gibi birincil anahtarı arttıracağıdır.

Bu, birincil anahtarlarınızda bir programcıyı zihinsel olarak kararsız hale getirebilecek boşluklara neden olacaktır. Veya uygulamanız kötü tasarlanmışsa ve mükemmel artımlı birincil anahtarlara bağlıysa, baş ağrısı olabilir.

innodb_autoinc_lock_mode = 0(Sunucu ayarı ve hafif bir performans isabeti ile birlikte) içine bakın veya sorgunuzun başarısız olmayacağından emin olmak için önce bir SELECT kullanın (bu da performans isabeti ve ekstra kodla birlikte gelir).


Neden "birincil anahtarlarınızdaki boşluklar" - potansiyel olarak - “bir programcıyı zihinsel olarak dengesiz hale getirir”? Birincil anahtarlarda her zaman boşluklar oluşur - örneğin bir kaydı her sildiğinizde.
warren

Bir SELECTyenilgiyle başlamak, büyük bir INSERTs grubunu dağıtmak ve kopyalar hakkında endişelenmek istememek amacını taşır .
warren

2

Bilinen birincil anahtar olmadan güncelleme veya ekleme

Zaten benzersiz veya birincil anahtarınız varsa, diğeri INSERT INTO ... ON DUPLICATE KEY UPDATE ...veyaREPLACE INTO ... iyi çalışmalıdır (varsa, silerek değiştir ve sonra ekler - böylece mevcut değerleri kısmen güncellemez).

Ancak some_column_idve some_typekombinasyonlarının benzersiz olduğu bilinen ve değerlerine sahipseniz . some_valueVarsa güncellemek veya yoksa eklemek istiyorsunuz. Ve bunu tek bir sorguda yapmak istiyorsunuz (bir işlem kullanmaktan kaçınmak için). Bu bir çözüm olabilir:

INSERT INTO my_table (id, some_column_id, some_type, some_value)
SELECT t.id, t.some_column_id, t.some_type, t.some_value
FROM (
    SELECT id, some_column_id, some_type, some_value
    FROM my_table
    WHERE some_column_id = ? AND some_type = ?
    UNION ALL
    SELECT s.id, s.some_column_id, s.some_type, s.some_value
    FROM (SELECT NULL AS id, ? AS some_column_id, ? AS some_type, ? AS some_value) AS s
) AS t
LIMIT 1
ON DUPLICATE KEY UPDATE
some_value = ?

Temel olarak, sorgu şu şekilde yürütülür (göründüğünden daha az karmaşık):

  • WHERECümle eşleşmesi aracılığıyla mevcut bir satırı seçin .
  • sSütun değerlerinin açıkça verildiği potansiyel yeni bir satırla (tablo ) sonuçlanan birleşim (s.id NULL, bu nedenle yeni bir otomatik artış tanımlayıcısı oluşturur).
  • Var olan bir satır bulunursa, o zaman tablodan potansiyel yeni bir satır s(tablo LİMİT 1 nedeniyle atılır t) ve her zaman bir tetikleyecek ON DUPLICATE KEYolan olacak sütun.UPDATEsome_value
  • Mevcut bir satır bulunamazsa, potansiyel yeni satır eklenir (tablo tarafından verildiği gibi s).

Not: İlişkisel veritabanındaki her tablonun en az bir birincil otomatik artış idsütunu olmalıdır. Buna sahip değilseniz, ilk bakışta ihtiyacınız olmasa bile ekleyin. Bu "numara" için kesinlikle gereklidir.


Diğer birkaç cevaplayıcı bir INSERT INTO ... SELECT FROMformat sağladı . Neden yaptın?
warren

2
@warren Ya cevabımı okumadınız, anlamıyorsunuz, ya da doğru bir şekilde açıklamamıştım. Her durumda, aşağıdakileri vurgulayayım: Bu sadece normal bir INSERT INTO... SELECT FROM...çözüm değil. Lütfen bana aynı cevaba link verin, eğer bulabilirseniz bu cevabı sileceğim, aksi takdirde cevabımı oylayın (anlaşma?). Bağlayacağınız yanıtın yalnızca 1 sorgu (güncelleme + ekleme için) kullandığını, işlem olmadığını ve benzersiz olduğu bilinen sütun kombinasyonlarını hedefleyebildiğinden emin olun (bu nedenle ayrı ayrı sütunlar benzersiz olması gerekir).
Yeti
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.