Question

I tried to run a dynamically generated SQL query within PHP targeting an Sybase SQL-Anywhere database and I got the following error:

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 Query String:

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

I'm not completely new to SQL or interfacing with a database, but this message has me stumped. It claims that there is an invalid expression in the WHERE clause, but I can't see how the query is illegally structured. My guess is the error involves the OR and joining of the two search results.

Furthermore, by running these three separate queries and combining the results (in Excel), returns the correct set of results:

Query A:

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

Query B

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

Query 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

To clarify what return results I want:

enter image description here

Sybase documentation about error -680 says the following:

An expression in the WHERE clause of a query that uses Transact-SQL syntax contains a comparison of a column from the NULL-supplying table with a subquery or an expression that references a column from another table.

  1. What is invalid about the original SQL query?

  2. What does the documented explanation mean?

  3. How could I edit the original SQL query to get the desired results?

Note that since this query was dynamically generated I want to know how I can change the statements between the OR clauses:

Statement Structure:

  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
  )
Was it helpful?

Solution 2

You could trivially replace the fragments:

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
  )

With tbl being the correlation name for the outer query; the outer query would become:

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

Also note that I removed the ecrs.v_InventoryMaster table from the subquery, since it is already present in the outer query, and would result in exactly the same row(s) being checked as the outer query already has found.

This will give the complete query as:

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
  )
;

My guess is that the parser is confused by the unaliased references to ecrs.v_InventoryMaster. Another possibility is that the range table is full (if you have a lot of subquery-terms)

OTHER TIPS

"NOT IN" queries are very expensive, especially if you are applying it 3+ times as in your example. I would spin this slightly differently than using "NOT IN" queries. I would do left join and look for ANY entry being NULL (ie: not found), but having AT LEAST ONE of the entries HAVING the criteria...

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

Since each of your criteria was a NOT IN based on the PK entry, I just used ONCE the inventory master. Then, did a LEFT-JOIN to the stock inventory link discounts (SILD alias) on that foreign key match AND the corresponding "DIS_FK" ID (6, 14, 25 respectively).

So, now, lets say there are 10 discounts at the (SILD) level for a particular inventory item and include IDs 1, 6, 10, 11, 22, 25, blah, blah... This table will join to find a match for both the 6 and 25 at the same time (via different aliases) and NOT find one for the 14. From your scenario, you WOULD WANT this entry.

This takes us to the WHERE clause. For this one inventory item, I want to make sure that AT LEAST ONE of the entries is NULL (ie: for discount 14), AND At least ONE of the items DID exist (ie: 6 and 25).

Now, if the inventory master had discounts of 1, 5, 12, it would be ignored because NONE of the 6, 14 or 25 would have been found never considering the rest of the WHERE clause.

You could continue to add as many (SILD) instances as you needed to and just keep the pattern going, just using (as I have), a different alias to know which one you are referring to.

The WHERE clause could even be more simplified to the following

   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

So this way, you have AT LEAST ONE condition qualified match, but AT MOST, 1 LESS than all your criteria. In this example, you have 3 criteria, so 1 OR 2 would be valid, zero fail, three fail...

If you had 6 criteria, then between 1 and 5...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top