Мультиключи в SQL WHERE IN

Скажи у тебя Accounts стол где ID столбец PK и TaxID+AccountNumber это уникальное ограничение:

select * from Accounts where ID in (100, 101)

Теперь вы хотите сделать аналогичный запрос, используя естественный ключ:

select * from Accounts 
where {TaxID, AccountNumber} in 
  ({"0123456", "2000897"}, {"0125556", "2000866"})

Так что это включает в себя кортежи и выглядит довольно законно. Можно ли как-то выразить с помощью ANSI SQL? Может быть, в каком-то конкретном расширении SQL? Если нет, то почему (оценю любые предположения)?

5 ответов

Решение

Оба они являются допустимым синтаксисом ISO/ANSI Full SQL-92:

SELECT a.* 
FROM Accounts a
  INNER JOIN
    ( VALUES('0123456', '2000897'), ('0125556', '2000866')
    ) AS v(TaxID, AccountNumber) 
  ON (a.TaxID, a.AccountNumber) = (v.TaxID, v.AccountNumber)

SELECT * 
FROM Accounts a
WHERE (a.TaxID, a.AccountNumber) IN 
    ( VALUES ('0123456', '2000897'), ('0125556', '2000866') )

Но я не думаю, что какой-либо из них работает в любой текущей СУБД.


Это также допустимый полный синтаксис SQL-92 (он не работает в SQL-Server 2008 из-за NATURAL JOIN):

SELECT a.* 
FROM Accounts a
  NATURAL JOIN
    ( VALUES('0123456', '2000897'), ('0125556', '2000866')
    ) AS v(TaxID, AccountNumber) 

Это также допустимый SQL (не уверен, что он в спецификации 92 или новее) - и это то, что у вас есть (но с использованием скобок, а не фигурных скобок).
Он поддерживается MySQL, Postgres, DB2 (но не SQL Server):

SELECT a.* 
FROM Accounts a
WHERE (TaxID, AccountNumber) IN
    ( ('0123456', '2000897'), ('0125556', '2000866') )
  ;

Был похожий вопрос в DBA.SE с различными другими способами сформулировать это:
выбрав, где два столбца находятся в наборе

Если вы используете T-SQL, то вариант, похожий на ваш гипотетический запрос, заключается в использовании табличных литералов, например:

select * 
from Accounts a
inner join (values('0123456', '2000897'),('0125556', '2000866')) 
    as v(TaxID, AccountNumber) 
    on a.TaxID = v.TaxID and a.AccountNumber = v.AccountNumber

Здесь вы создаете табличный литерал с именем v который содержит поля TaxID а также AccountNumber, Теперь вы можете объединить литерал таблицы в двух полях, чтобы получить желаемый результат. Одно предостережение состоит в том, что литерал таблицы может содержать только 1000 строк. Вы можете прочитать больше о поддержке T-SQL для литералов таблиц на этой странице.

Изменить: эта страница указывает, что эта конструкция также работает в PostgreSQL.

Будьте осторожны, как это истолковать. Ответ Акулы сработает, но вернется

TaxID        AccountNumber
1234         8765
1234         7654
2345         8765
2345         7654

Что может быть не тем, что вы хотите... Например, если вы хотите, чтобы номер счета 8765 только для налогового идентификатора 1234 и 7654 для налогового идентификатора 2345, вам понадобится предложение WHERE, например:

WHERE (taxId'1234' and accountnumber='8765') OR 
      (taxid='2345' and accountNumber='7654')

Грубым способом было бы объединить 2 значения вместе.

например

SELECT *
FROM Accounts
WHERE CAST(TaxID AS VARCHAR(10)) + '-' + CAST(AccountNumber AS VARCHAR(10)) 
IN ('0123456-2000897', '......', ....)

Однако, например, в SQL Server это не сможет использовать индекс.

Вы можете добавить вычисляемый столбец, который объединяет оба значения в 1, а затем сопоставить по этому:

SELECT * FROM Accounts WHERE MyComputedColumn IN ('0123456-2000897', ....)


Или вы можете сделать:

SELECT a.*
FROM Accounts a
    JOIN 
    (
        SELECT '0123456' AS TaxID, '2000897' AS AccountNumber
        UNION ALL
        SELECT '0125556', '2000866'
    ) x ON a.TaxID = x.TaxID AND a.AccountNumber = x.Number

В Oracle SQL вы можете просто заменить скобки на фигурные скобки "{}" в исходном посте (второй пример). Может не быть стандартом ANSII, но он близок и работает нормально.

Объединять значения не рекомендуется, даже с необычными разделителями всегда есть небольшой риск неправильного соответствия свободно вводимых текстовых значений. Лучше всего не заводиться.

Другие вопросы по тегам