BİLİNMEYEN döndüren bir Boole ifadesini ters çevir


11

Misal

Masam var

ID  myField
------------
 1  someValue
 2  NULL
 3  someOtherValue

ve DOĞRU, YANLIŞ veya (SQL'in üçlü mantığı nedeniyle) olarak değerlendirilebilecek bir T-SQL Boole ifadesi BİLİNMEYEN:

SELECT * FROM myTable WHERE myField = 'someValue'

-- yields record 1

Diğer tüm kayıtları almak istersem , ifadeyi basitçe reddedemem

SELECT * FROM myTable WHERE NOT (myField = 'someValue')

-- yields only record 3

Bunun neden olduğunu (üçlü mantık) ve bu özel sorunun nasıl çözüleceğini biliyorum.

Ben sadece kullanabilirsiniz biliyorum myField = 'someValue' AND NOT myField IS NULLve ben asla bilinmeyen verir bir "tersine çevrilebilir" ifade olsun:

SELECT * FROM myTable WHERE NOT (myField = 'someValue' AND myField IS NOT NULL)

-- yields records 2 and 3, hooray!

Genel dava

Şimdi genel durum hakkında konuşalım. Diyelim ki myField = 'someValue'çok fazla alan ve koşul içeren bazı karmaşık ifadelerim var, belki alt sorgular:

SELECT * FROM myTable WHERE ...some complex Boolean expression...

Bu yolculuğu "tersine çevirmenin" genel bir yolu var mı? Alt ifadeler için çalışıyorsa bonus puanları:

SELECT * FROM myTable 
 WHERE ...some expression which stays... 
   AND ...some expression which I might want to invert...

SQL Server 2008-2014'ü desteklemem gerekiyor, ancak 2008'den daha yeni bir sürüm gerektiren zarif bir çözüm varsa, bunu da duymak istiyorum.

Yanıtlar:


15

Koşulu, ikili sonuç döndüren bir CASE ifadesinde (örneğin 1 veya 0) içine alabilirsiniz:

SELECT
  ...
FROM
  ...
WHERE
  CASE WHEN someColumn = someValue THEN 1 ELSE 0 END = 1
;

İfadeyi reddetmek, someColumn öğesinin boş olduğu satırlar da dahil olmak üzere aynı veri kaynağındaki diğer tüm satırları verir :

SELECT
  ...
FROM
  ...
WHERE
  NOT CASE WHEN someColumn = someValue THEN 1 ELSE 0 END = 1
  -- or: CASE WHEN someColumn = someValue THEN 1 ELSE 0 END <> 1
;

SQL Server 2012'den beri , yukarıdaki gibi bir ikili CASE'in etrafındaki bir sarıcı olan IIF işlevine de sahipsiniz . Yani, bu CASE ifadesi:

CASE WHEN someColumn = someValue THEN 1 ELSE 0 END

IIF kullanılarak yeniden yazılırsa şöyle görünecektir:

IIF(someColumn = someValue, 1, 0)

Ve bunu tam olarak CASE ifadesiyle aynı şekilde kullanabilirsiniz. Performansta bir fark olmayacak, sadece kod biraz daha özlü olacak, muhtemelen bu şekilde daha temiz olacak.


Bu güzel bir fikir! Bir Boole ifadesini çalışabileceği bir ifadeye "dönüştürmek" için CASE komutunu kullanın ve daha sonra bu ifadeyi Boole ifadesine "dönüştürmek" için bir karşılaştırma kullanın.
Heinzi

10

Bana olan ilk düşünce:

DECLARE @T AS table (c1 integer NULL);

INSERT @T (c1)
VALUES (1), (NULL), (2);

-- Original expression c1 = 1
SELECT T.c1
FROM @T AS T
WHERE c1 = 1;

İadeler:

sonuç

-- Negated
SELECT T.c1
FROM @T AS T
WHERE NOT EXISTS (SELECT 1 WHERE c1 = 1);

İadeler:

Olumsuz sonuç

Bu EXISTSher zaman doğru ya da yanlış , asla bilinmeyen bir şekilde geri döner . İhtiyacı SELECT 1 WHEREmaalesef gerekli, ancak örneğin, gereksinimi için çalışılabilir olabilir:

sql = "
    SELECT * 
    FROM someTable 
    WHERE " + someExpression + 
    " AND NOT EXISTS (SELECT 1 WHERE " + 
    someOtherExpression + ")";
result = executeAndShow(sql);

Bkz. EXISTS (Transact-sql)


Bireysel tahminleri tersine çevirmek için yöntemlerin EXISTSveya CASE/IIFyöntemlerin nasıl uygulanabileceğini gösteren biraz daha karmaşık bir çalışma örneği :

DECLARE @T AS table 
(
    c1 integer NULL,
    c2 integer NULL,
    c3 integer NULL
);

INSERT @T 
    (c1, c2, c3)
VALUES 
    (1, NULL, 2),
    (2, 2, 3),
    (NULL, 1, 4);

Kod:

-- Original
SELECT 
    T.c1,
    T.c2,
    T.c3
FROM @T AS T
WHERE
    1 = 1
    -- Predicate #1
    AND T.c1 = 2
    -- Predicate #2
    AND T.c2 =
    (
        SELECT MAX(T2.c2)
        FROM @T AS T2
        WHERE T2.c2 IS NOT NULL
    )
    -- Predicate #3
    AND T.c3 IN (3, 4)
    ;

-- Invert predicates #1 and #2
SELECT 
    T.c1,
    T.c2,
    T.c3
FROM @T AS T
WHERE
    1 = 1
    AND NOT EXISTS (SELECT 1 WHERE 1 = 1
        -- Predicate #1
        AND T.c1 = 2)
    AND NOT EXISTS (SELECT 1 WHERE 1 = 1
        -- Predicate #2
            AND T.c2 =
            (
                SELECT MAX(T2.c2)
                FROM @T AS T2
                WHERE T2.c2 IS NOT NULL
            ))
    -- Predicate #3
    AND T.c3 IN (3, 4)
    ;

3

Alt ifadeleri önceden yeniden yazmanın sakıncası yoksa şunları kullanabilirsiniz COALESCE:

SELECT *
FROM myTable
WHERE NOT (COALESCE(myField, 'notSomeValue') = 'someValue')

Sen gerekir emin olmak 'notSomeValue'ayrıdır 'someValue'; tercihen, sütun için tamamen yasadışı bir değer olacaktır. (Tabii ki de olamaz NULL.) Uzun bir listeniz olsa bile bunu reddetmek kolaydır:

SELECT *
FROM myTable
WHERE NOT (
    COALESCE(myField, 'notSomeValue') = 'someValue' AND
    COALESCE(myField2, 'notSomeValue') = 'someValue2' AND
    COALESCE(myField3, 'notSomeValue') = 'someValue3' AND
    COALESCE(myField4, 'notSomeValue') = 'someValue4'
)

Temizleyici, daha basit ve daha bariz CASEya IIFbence. Ana dezavantajı, eşit olmadığını bildiğiniz ikinci bir değere sahip olmak, ancak gerçek değeri ön bilmiyorsanız, bu gerçekten sadece bir sorundur. Bu durumda, Hanno Binder'in önerdiği gibi kullanabilirsiniz ve COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'( 'someValue'gerçekten parametrelenecek yer) kullanabilirsiniz.

COALESCE SQL Server 2005'ten itibaren kullanılabileceği belgelenmiştir.

Sorgunuzla karıştırmanın (burada önerilen yöntemlerden herhangi birini kullanarak) veritabanının sorgunuzu optimize etmesini zorlaştırabileceğini unutmayın. Büyük veri kümeleri için IS NULLsürümün optimize edilmesi daha kolaydır.


1
COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'herhangi bir "someValue" ve tablodaki herhangi bir veri için çalışmalıdır.
JimmyB

2

İkinci bir sorgunun sonuçlarını ilk sorgudan etkili bir şekilde kaldıran yerleşik EXCEPT set operatörü vardır.

select * from table
except
select * from table
where <really complex predicates>

Umarım bu küçük bir tablo :-)
Lennart

-4

COALESCE kullanılabilir mi?

SELECT * FROM myTable WHERE NOT COALESCE(myField = 'someValue', FALSE)

4
Evet, COALESCE kullanılabilir, ancak hayır, bu çalışmaz: (a) COALESCE, bir Boole ifadesini kabul etmez (bu arada ISNULL da olmaz) ve (b) FALSE doğruluk değeri SQL'de doğrudan kullanılamaz değişmez. Deneyin ve bir sözdizimi hatası alırsınız.
2016'da Heinzi

@Heinzi - Denedim, işe yaradı, bu yüzden gönderdim. Belki T-SQL üzerinde çalışmaz, ancak Postgres ve MySQL için iyidir.
Malvolio

2
@Malvolio: Soru olsa da etiketlenir veya sql-serverdeğil . mysqlpostgresql
Andriy M

@Malvolio, Postgres'in bir BOOLEANtürü olması ve MySQL'in işlevin BOOLEANparametreleri olabilen (sahte) bir türü olmasıdır COALESCE(). Soru sql-agnosticveya ile etiketlenmiş sql-standardolsaydı, cevap iyi olurdu.
ypercubeᵀᴹ

@ ypercubeᵀᴹ - eh, sana ne söyleyebilirim? Daha iyi bir veritabanı edinin.
Malvolio
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.