Загадочная ошибка SQL от Sybase (ошибка -680)

Я попытался запустить динамически сгенерированный SQL-запрос в PHP, нацеленный на базу данных Sybase SQL-Anywhere, и получил следующую ошибку:

Warning: sybase_query(): message: SQL Anywhere Error -680: Invalid expression in WHERE clause of Transact-SQL outer join (severity 16) in /path/to/file.php

Строка запроса SQL:

SELECT DISTINCT v_InventoryMaster.INV_ScanCode, v_InventoryMaster.INV_ReceiptAlias 
FROM ecrs.v_InventoryMaster
WHERE ( v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = 6 
  )
  OR v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = 14 
  )
  OR v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = 25 
  )
  -- more OR clause subqueries with different ILD_DIS_FK values
)
ORDER BY v_InventoryMaster.INV_ScanCode

Я не совсем новичок в SQL или взаимодействии с базой данных, но это сообщение поставило меня в тупик. Он утверждает, что в предложении WHERE есть недопустимое выражение, но я не вижу, как неправильно структурирован запрос. Я предполагаю, что ошибка связана с OR и объединением двух результатов поиска.

Кроме того, выполнение этих трех отдельных запросов и объединение результатов (в Excel) возвращает правильный набор результатов:

Запрос А:

SELECT DISTINCT v_InventoryMaster.INV_ScanCode, v_InventoryMaster.INV_ReceiptAlias 
FROM ecrs.v_InventoryMaster
WHERE ( v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = 6 
  )
ORDER BY v_InventoryMaster.INV_ScanCode

Запрос Б

SELECT DISTINCT v_InventoryMaster.INV_ScanCode, v_InventoryMaster.INV_ReceiptAlias 
FROM ecrs.v_InventoryMaster
WHERE ( v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = 14 
  )
ORDER BY v_InventoryMaster.INV_ScanCode

Запрос C

SELECT DISTINCT v_InventoryMaster.INV_ScanCode, v_InventoryMaster.INV_ReceiptAlias 
FROM ecrs.v_InventoryMaster
WHERE ( v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = 25 
  )
ORDER BY v_InventoryMaster.INV_ScanCode

Чтобы уточнить, какие результаты возврата я хочу:

введите здесь описание изображения

В документации Sybase о error -680 говорится следующее:

Выражение в предложении WHERE запроса, использующего синтаксис Transact-SQL, содержит сравнение столбца из таблицы, предоставляющей NULL, с подзапросом или выражением, которое ссылается на столбец из другой таблицы.

  1. Что неверно в исходном SQL-запросе?

  2. Что означает документированное объяснение?

  3. Как я могу отредактировать исходный SQL-запрос, чтобы получить желаемые результаты?

Обратите внимание, что, поскольку этот запрос был сгенерирован динамически, я хочу знать, как я могу изменить операторы между предложениями OR:

Структура заявления:

  v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = value -- value dynamically chosen by user
  )

person recursion.ninja    schedule 01.12.2012    source источник
comment
Возможно, запрос слишком длинный и усекается во время выполнения...   -  person pascal    schedule 04.01.2013
comment
Кстати, я не уверен, что понимаю цель запроса... Вам нужны InventoryMaster, которые не связаны с всеми предоставленными идентификаторами скидок (ILD_DIS_FK), эквивалентными NOT(INV_PK IN (...ILD_DIS_FK=14) AND INV_PK IN (...ILD_DIS_FK=6) ...)?   -  person pascal    schedule 04.01.2013
comment
Дубликат этого вопроса?   -  person pascal    schedule 04.01.2013
comment
@pascal Чтобы уточнить, мне нужны InventoryMaster, которые не связаны с каким-либо одним или несколькими предоставленными идентификаторами скидок (ILD_DIS_FK). Отсюда и OR заявления.   -  person recursion.ninja    schedule 13.01.2013
comment
При выполнении предложения IN или EXISTS в лучшем случае избыточно указывать DISTINCT, в худшем случае движок будет тупо работать и фактически выполнит работу по устранению дубликатов.   -  person Damien_The_Unbeliever    schedule 13.01.2013
comment
Как я уже говорил в другом месте, вы пытались выполнить этот запрос через dbisql вместо PHP?   -  person pascal    schedule 14.01.2013
comment
@pascal не имеет значения, работает ли он в dbisql, веб-приложение основано на PHP, поэтому динамически генерируемые запросы должны работать через соединение PHP с базой данных.   -  person recursion.ninja    schedule 14.01.2013
comment
Это помогло бы отделить проблему с самим SQL от проблемы усечения запроса... которая потребовала бы разных ответов.   -  person pascal    schedule 14.01.2013
comment
@pascal для полноты картины я выполнил исходную строку запроса в dbisql для той же базы данных, на которую нацелен PHP-скрипт. Я получил тот же код ошибки (-680) и такое же сообщение об ошибке от dbisql.   -  person recursion.ninja    schedule 14.01.2013


Ответы (2)


Вы можете тривиально заменить фрагменты:

v_InventoryMaster.INV_PK NOT IN (
    SELECT DISTINCT v_InventoryMaster.INV_PK 
    FROM ecrs.v_InventoryMaster, ecrs.StockInventoryLinkDiscounts
    WHERE v_InventoryMaster.INV_PK = StockInventoryLinkDiscounts.ILD_INV_FK
    AND StockInventoryLinkDiscounts.ILD_DIS_FK = value -- value dynamically chosen by user
  )

By:

 NOT EXISTS (
    SELECT * 
    FROM  ecrs.StockInventoryLinkDiscounts sild
    WHERE sild.ILD_INV_FK = tbl.INV_PK
    AND sild.ILD_DIS_FK = value -- value dynamically chosen by user
    --           ^^^^^ NOTE: this should probably be sild.ILD_DIS_PK
  )

При этом tbl является корреляционным именем для внешнего запроса; внешний запрос будет выглядеть так:

SELECT DISTINCT v_InventoryMaster.INV_ScanCode
      , v_InventoryMaster.INV_ReceiptAlias 
FROM ecrs.v_InventoryMaster tbl

Также обратите внимание, что я удалил таблицу ecrs.v_InventoryMaster из подзапроса, поскольку она уже присутствует во внешнем запросе, и это приведет к проверке точно тех же строк, которые уже были найдены внешним запросом.

Это даст полный запрос как:

SELECT DISTINCT v_InventoryMaster.INV_ScanCode
      , v_InventoryMaster.INV_ReceiptAlias 
FROM ecrs.v_InventoryMaster tbl
WHERE NOT EXISTS (
    SELECT * 
    FROM  ecrs.StockInventoryLinkDiscounts sild
    WHERE sild.ILD_INV_FK = tbl.INV_PK
    AND sild.ILD_DIS_FK = 6
  )
OR NOT EXISTS (
    SELECT * 
    FROM  ecrs.StockInventoryLinkDiscounts sild
    WHERE sild.ILD_INV_FK = tbl.INV_PK
    AND sild.ILD_DIS_FK = 14
  )
OR NOT EXISTS (
    SELECT * 
    FROM  ecrs.StockInventoryLinkDiscounts sild
    WHERE sild.ILD_INV_FK = tbl.INV_PK
    AND sild.ILD_DIS_FK = 25
  )
;

Я предполагаю, что синтаксический анализатор сбит с толку ссылками без псевдонимов на ecrs.v_InventoryMaster. Другая возможность заключается в том, что таблица диапазонов заполнена (если у вас много подзапросов)

person wildplasser    schedule 13.01.2013
comment
Ваше изменение шаблона на основе NOT EXISTS возвращает ожидаемые результаты в моем примере. В настоящее время я выполняю некоторые тесты, чтобы убедиться, что изменение шаблона с INV_PK NOT IN на NOT EXISTS не нарушает работу других компонентов шаблона запроса. возлагаю большие надежды :) - person recursion.ninja; 13.01.2013
comment
Форма not exists более или менее эквивалентна варианту left join ... where otherkey IS NULL, но немного более удобочитаема. Существует третий вариант (подсчет различных категорий, который применим, если количество терминов слишком велико для существующего (или LEFT JOIN) варианта). (Я не эксперт по Sybase, и описание очень расплывчатое, по крайней мере, для меня) - person wildplasser; 13.01.2013

Запросы «НЕ В» очень дороги, особенно если вы применяете их 3+ раза, как в вашем примере. Я бы раскрутил это немного иначе, чем используя запросы «НЕ В». Я бы сделал левое соединение и искал ЛЮБУЮ запись NULL (т.е. не найденную), но имеющую ПО КРАЙНЕЙ МЕРЕ ОДНУ из записей, ИМЕЮЩИХ критерии...

SELECT DISTINCT 
      vIM.INV_ScanCode, 
      vIM.INV_ReceiptAlias 
   FROM
      ecrs.v_InventoryMaster vIM
         LEFT JOIN ecrs.StockInventoryLinkDiscounts SILD6
            ON vIM.INV_PK = SILD6.ILD_INV_FK
            AND SILD6.ILD_DIS_FK = 6 
         LEFT JOIN ecrs.StockInventoryLinkDiscounts SILD14
            ON vIM.INV_PK = SILD14.ILD_INV_FK
            AND SILD14.ILD_DIS_FK = 14 
         LEFT JOIN ecrs.StockInventoryLinkDiscounts SILD25
            ON vIM.INV_PK = SILD25.ILD_INV_FK
            AND SILD25.ILD_DIS_FK = 25 
   WHERE
          (    SILD6.ILD_INV_FK IS NULL
            OR SILD14.ILD_INV_FK IS NULL
            OR SILD25.ILD_INV_FK IS NULL )
      AND (  case when SILD6.ILD_INV_FK IS NULL THEN 0 ELSE 1 end
           + case when SILD14.ILD_INV_FK IS NULL THEN 0 ELSE 1 end
           + case when SILD25.ILD_INV_FK IS NULL THEN 0 ELSE 1 end ) > 0
   ORDER BY 
      vIM.INV_ScanCode

Поскольку каждый из ваших критериев был NOT IN на основе записи PK, я просто использовал мастер инвентаризации ОДИН РАЗ. Затем выполнил LEFT-JOIN для скидок на ссылки на складские запасы (псевдоним SILD) для этого совпадения внешнего ключа И соответствующего идентификатора «DIS_FK» (6, 14, 25 соответственно).

Итак, теперь допустим, что существует 10 скидок на уровне (SILD) для определенного предмета инвентаря и включают идентификаторы 1, 6, 10, 11, 22, 25, бла-бла... Эта таблица будет объединена, чтобы найти совпадение для 6 и 25 одновременно (по разным псевдонимам) и НЕ найти ни одного для 14. Исходя из вашего сценария, вы ХОТИТЕ эту запись.

Это приводит нас к предложению WHERE. Для этого одного элемента инвентаря я хочу убедиться, что ПО КРАЙНЕЙ МЕРЕ ОДНА из записей имеет значение NULL (т. е. для скидки 14), И ПО КРАЙНЕЙ МЕРЕ ОДИН из элементов ДЕЙСТВИТЕЛЬНО существовал (т. е.: 6 и 25).

Теперь, если мастер запасов имеет скидки 1, 5, 12, он будет проигнорирован, потому что НИ ОДИН из 6, 14 или 25 не будет найден без учета остальной части предложения WHERE.

Вы можете продолжать добавлять столько экземпляров (SILD), сколько вам нужно, и просто поддерживать работу шаблона, просто используя (как я) другой псевдоним, чтобы знать, на какой из них вы ссылаетесь.

Предложение WHERE можно было бы еще упростить до следующего

   WHERE
      (  case when SILD6.ILD_INV_FK IS NULL THEN 0 ELSE 1 end
           + case when SILD14.ILD_INV_FK IS NULL THEN 0 ELSE 1 end
           + case when SILD25.ILD_INV_FK IS NULL THEN 0 ELSE 1 end ) between 1 and 2

Таким образом, у вас есть МИНИМУМ ОДНО условие, удовлетворяющее требованиям, но МАКСИМАЛЬНО НА 1 МЕНЬШЕ, чем все ваши критерии. В этом примере у вас есть 3 критерия, поэтому 1 ИЛИ 2 будут действительны, ноль не соответствует, три не соответствуют...

Если у вас было 6 критериев, то между 1 и 5...

person DRapp    schedule 13.01.2013
comment
Поскольку запросы динамически создаются за кулисами посредством ввода нетехническим пользователем, я не могу допустить, чтобы запросы так радикально отклонялись от моего базового шаблона без внедрения постпроцессора для оптимизации и реструктуризации запроса после ввода пользователем. Это определенно выходит за рамки моего проекта. Хотя я уверен, что ваши реструктурированные запросы работают правильно, они не будут работать для моего базового шаблона. - person recursion.ninja; 13.01.2013
comment
@awashburn, тогда я бы подумал об обновлении вашего шаблона, чтобы он работал и имел больше ясности. Упрощение задач для пользователя обычно означает большую работу программиста... даже если это каким-то образом шаблонно. - person DRapp; 14.01.2013