• subscribe
February 22, 2006 12:00 AM

Catch That Bug!

SQL Server Pro
InstantDoc ID #49041
Downloads
49041.zip

February's Bug Solution: From the Customers and Orders tables that you create and populate by running the code in Listing A, you need to return the details of customers who made orders.You build the following query to return the details:

SELECT custid, custname 
FROM dbo.Customers 
WHERE custid IN(SELECT custid 
  FROM dbo.Orders); 

Given the sample data in the Customers and Orders tables, you probably expect your query to return customers A and B. Instead, you get back all customers, including those who made no orders. Why?

The bug here is elusive. Look carefully at the definition of both tables in Listing A. Notice in the Customers table that the column for customer ID is called custid, but the Orders table calls this column customerid. So, the query just given looks for a column name, custid, that doesn't exist in Orders.

With this discrepancy in column names, why does the query return all customers rather than producing an error that the custid column doesn't exist in Orders? If you run only the inner query

WHERE custid IN(SELECT custid 
  FROM dbo.Orders); 

you'll get the expected error. But SQL Server resolves query attributes beginning from the current level and working outward. So, SQL Server first looks for a column called custid in Orders and doesn't find one. Then, it looks for custid in the outer level (Customers) and finds it. Thus, unintentionally, the query becomes a correlated one, as if you had written:

SELECT custid, custname 
FROM dbo.Customers 
WHERE custid IN(SELECT 
  Customers.custid 
  FROM dbo.Orders); 

Now the reason for the unexpected result is clear: The filter ends up comparing the value to itself, returning TRUE. A best practice that can help you avoid such bugs is to always table-qualify columns in queries, even when the queries are self-contained. For example, if you had written the original query as follows, SQL Server would have trapped the error at resolution time, letting you know that Orders doesn't have a custid column:

SELECT custid, custname 
FROM dbo.Customers AS C 
WHERE custid IN(SELECT O.custid 
  FROM dbo.Orders AS O); 

You could have then changed the query to

SELECT custid, custname 
FROM dbo.Customers AS C 
WHERE custid IN(SELECT 
  O.customerid FROM dbo.Orders 
  AS O); 

This query correctly returns customers A and B.

March's Bug: The following query returns the maximum OrderID from the Orders table in the Northwind database:

SELECT MAX(OrderID) FROM 
  dbo.Orders 

And SQL Server's system stored procedure sp_who returns current users and processes. You use the following code to try to run both statements in the same batch:

SELECT MAX(OrderID) FROM 
 dbo.Orders 
sp_who 

However, you get only the max OrderID from Orders.You don't receive any output from sp_who—not even an error. It seems that only the first line of code ran and that sp_who wasn't invoked at all. Can you find the bug in this code?



ARTICLE TOOLS

Comments
  • BB&D
    6 years ago
    Mar 10, 2006

    To answer the reason for the new bug, it is really quite simple, although when I first hit it it took me quite a while to figure it out: SQL thinks sp_who is an alias for dbo.orders.

    SELECT MAX(OrderID) FROM
    dbo.Orders
    EXEC sp_who

    and it works.

    Re the previous bug (with the custid column), I've stumbled across that one before as well and really put it down to a bug in SQL. You enlightened me, thanks. On the other hand, why not join the two tables directly instead of having an IN statement?

  • SCOTT
    6 years ago
    Mar 06, 2006

    Interesting problem. Since the last line in the select statement was a table name for the FROM clause the sp_who was interpreted as a table alias. The following illustrates that.

    SELECT MAX(sp_who.OrderID) FROM dbo.Orders sp_who

    As previously mentioned use an EXEC or a GO statement to seperate the two batches.

  • Mark
    6 years ago
    Feb 27, 2006

    If a stored procedure is not the first line in a batch, it must be called with an EXECUTE keyword. Either split the commands into separate batches with a GO command, or use "EXEC sp_who".

  • Dinakar
    6 years ago
    Feb 24, 2006

    According to Books Online:

    "When you have to execute a stored procedure, use the Transact-SQL EXECUTE statement. You can execute a stored procedure without using the EXECUTE keyword if the stored procedure is the first statement in the batch."

    Since in the example provided, the stored proc is NOT the first line in the batch you need to provide an EXEC statement.

    Using an EXEC keyword makes the T-SQL to run in a different batch.
    So, if you run the above code with :

    SELECT MAX(OrderID) FROM
    dbo.Orders
    EXEC sp_who
    The 2 statements run in DIFFERENT batches, hence they will not throw any errors.

    NOTE: It doesnt matter if its a system stored proc or a user created one. The above explanation holds good either way.

  • maddy1978
    6 years ago
    Feb 23, 2006

    SELECT MAX(OrderID) FROM
    dbo.Orders And SQL Server's system stored procedure sp_who returns current users and processes. You use the following code to try to run both statements in the same batch:

    SELECT MAX(OrderID) FROM
    dbo.Orders
    sp_who


    if we tried to replace the command like this
    SELECT MAX(OrderID) FROM dbo.Orders
    exec sp_who

    it will work

You must log on before posting a comment.

Are you a new visitor? Register Here
  • SP1?
    I know there is a SP1 for SQL 2008 R2 available....and there is a "feature pack" as well... ...
  • SQL database mirroring
    I have SQL Server 2008 R2 Enterprise 64bit on Windows 2008 R2 Enterprise 64bit.  Each SQL Server has...
  • Dell Compellent Disk Drive
    Does anybody has experience with Dell Compellent Disk Drive? Basically, this system manages all disk...
  • Sql server performance tuning
    I need to find a tool that help me to optimize sql server,queries,improve the performance and solve ...