CTE'ye eklenen satırlar neden aynı ifadede güncellenemiyor?


13

PostgreSQL 9.5'te, aşağıdakilerle oluşturulmuş basit bir tablo verilmiştir:

create table tbl (
    id serial primary key,
    val integer
);

Bir değer eklemek için SQL çalıştırın, sonra aynı deyimde GÜNCELLEME:

WITH newval AS (
    INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;

Sonuç olarak GÜNCELLEME yoksayılır:

testdb=> select * from tbl;
┌────┬─────┐
 id  val 
├────┼─────┤
  1    1 
└────┴─────┘

Bu neden? Bu sınırlama SQL standardının (yani diğer veritabanlarında mevcut) bir parçası mı yoksa PostgreSQL'e özgü ve gelecekte düzeltilebilecek bir şey mi? WITH sorguları dokümantasyon birden UPDATE'ler desteklenmez diyor, ama ekler ve güncelleştirmeler söz etmez.

Yanıtlar:


15

CTE'deki tüm ifadeler neredeyse aynı anda gerçekleşir. Yani, veritabanının aynı anlık görüntüsünü temel alırlar.

UPDATEGibi altta yatan tablonun aynı durum görür INSERTile satır anlamına gelir val = 1değil, henüz. Kılavuz burada açıklığa kavuşuyor:

Tüm ifadeler aynı anlık görüntü ile yürütülür (bakınız Bölüm 13 ), böylece birbirlerinin hedef tablolar üzerindeki etkilerini "göremezler".

Her ifade edebilir başka CTE tarafından döndürülen neler olduğunu görmek RETURNINGmaddesi. Ancak altta yatan tablolar onlar için aynı görünüyor.

Yapmaya çalıştığınız şey için iki ifadeye (tek bir işlemde) ihtiyacınız olacaktır . Verilen örnek gerçekten INSERTbaşlamak için tek bir örnek olmalıdır , ancak bu basitleştirilmiş örnekten kaynaklanıyor olabilir.


15

Bu bir uygulama kararıdır. Postgres belgelerinde WITHSorgular (Ortak Tablo İfadeleri) bölümünde açıklanmıştır . Konuyla ilgili iki paragraf var.

İlk olarak, gözlemlenen davranışın nedeni:

İçindeki alt deyimlerWITH birbirleriyle ve ana sorgu ile eşzamanlı olarak yürütülür . Bu nedenle, içinde veri değiştiren ifadeler kullanıldığında WITH, belirtilen güncellemelerin gerçekleşme sırası tahmin edilemez. Tüm ifadeler aynı anlık görüntü ile yürütülür (bakınız Bölüm 13), böylece birbirlerinin hedef tablolar üzerindeki etkilerini "göremezler". Bu, satır güncellemelerinin gerçek sırasının öngörülemezliğinin etkilerini hafifletir ve verilerin, farklı alt ifadeler ile ana sorgu arasındaki değişiklikleri iletmenin tek yolu olduğu anlamına gelir . RETURNINGWITHBunun bir örneği ...

Ben pgsql-docs için bir öneri gönderdikten sonra , Marko Tiikkaja açıkladı (Erwin'in cevabına katılıyor):

Insert-update ve insert-delete durumları, UPDATE'lerin ve DELETE'lerin, INSERT gerçekleşmeden önce anlık görüntüsü alınmış olmaları nedeniyle INSERTed satırlarını görmenin bir yolu olmadığından çalışmaz. Bu iki vaka hakkında öngörülemez bir şey yok.

Dolayısıyla, ifadenizin güncellenmemesinin nedeni yukarıdaki ilk paragrafla açıklanabilir (yaklaşık "anlık görüntüler"). CTE'leri değiştirdiğinizde ne olacağı, hepsinin ve ana sorgunun yürütülmesi ve ifadenin yürütülmesinden hemen önceki verilerle (tabloların) aynı anlık görüntüsünü "görmesi" dir. CTE'ler, RETURNINGmaddeyi kullanarak birbirlerine ve ana sorguya ekledikleri / güncelledikleri / sildikleri hakkında bilgi aktarabilirler, ancak tablolardaki değişiklikleri doğrudan göremezler. Şimdi ifadenizde neler olduğunu görelim:

WITH newval AS (
    INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;

CTE ( newval) olmak üzere 2 bölümümüz var :

-- newval
     INSERT INTO tbl(val) VALUES (1) RETURNING id

ve ana sorgu:

-- main 
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id

İcra akışı şu şekildedir:

           initial data: tbl
                id  val 
                 (empty)
               /         \
              /           \
             /             \
    newval:                 \
       tbl (after newval)    \
           id  val           \
            1    1           |
                              |
    newval: returns           |
           id                 |
            1                 |
               \              |
                \             |
                 \            |
                    main query

Sonuç olarak, ana sorgu tbl(anlık görüntüde görüldüğü gibi) newvaltabloyla birleştiğinde, boş bir tabloyu 1 satırlık bir tabloyla birleştirir. Açıkçası 0 satırı güncelliyor. Yani ifade, yeni eklenen satırı değiştirmek için hiç gelmedi ve gördüğünüz şey bu.

Sizin durumunuzdaki çözüm, ya ilk etapta doğru değerleri eklemek için ifadeyi yeniden yazmak ya da 2 ifadeyi kullanmaktır. Biri ekler ve diğeri güncellenir.


İfadenin aynı satırlarda bir INSERTve sonra a olması gibi başka benzer durumlar da vardır DELETE. Silme işlemi aynı nedenlerle başarısız olur.

Güncelleme-güncelleme ve güncelleme-silme ile bazı diğer durumlar ve bunların davranışları, aşağıdaki dokümanda aynı dokümanlar sayfasında açıklanmaktadır.

Aynı satırı tek bir deyimde iki kez güncellemeye çalışmak desteklenmez. Değişikliklerden sadece biri gerçekleşir, ancak hangisini güvenilir bir şekilde tahmin etmek kolay değildir (ve bazen mümkün değildir). Bu, aynı ifadede önceden güncellenmiş bir satırı silmek için de geçerlidir: sadece güncelleme gerçekleştirilir. Bu nedenle, genellikle tek bir ifadede tek bir satırı iki kez değiştirmeye çalışmaktan kaçınmalısınız. Özellikle, ana ifade veya kardeş alt deyimi tarafından değiştirilen aynı satırları etkileyebilecek WITH alt deyimleri yazmaktan kaçının. Böyle bir ifadenin etkileri öngörülemez.

Marko Tiikkaja'nın cevabında:

Güncelleme güncelleme ve güncelleme-silme vakalar açıkça edilir değil (insert-update ve insert-silme vakalar gibi) aynı temel uygulama detay neden.
Güncelleme güncelleme vakası işe yaramaz çünkü dahili olarak Cadılar Bayramı problemine benziyor ve Postgres, hangi tuples'ların iki kez güncellenmenin uygun olacağını ve hangilerinin Cadılar Bayramı problemini yeniden başlatabileceğini bilmenin bir yolu yok.

Bu nedenle neden aynıdır (CTE'lerin nasıl değiştirildiği ve her CTE'nin aynı anlık görüntüyü nasıl gördüğü), ancak bu 2 durumda ayrıntılar daha karmaşık olduğundan ve sonuçlar güncelleme güncelleme durumunda tahmin edilemediğinden farklıdır.

Insert-update (sizin durumunuz gibi) ve benzer bir insert-delete'de sonuçlar tahmin edilebilir. İkinci işlemin (güncelleme veya silme) yeni eklenen satırları görmesi ve etkilemesi mümkün olmadığından yalnızca ekleme yapılır.


Önerilen çözüm, aynı satırları bir kereden fazla değiştirmeye çalışan tüm durumlar için aynıdır: Yapmayın. Her satırı bir kez değiştiren veya ayrı (2 veya daha fazla) deyim kullanan deyimler yazın.

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.