views:

396

answers:

5

Hi Sql Server gurus, I need to perform a query like this:

SELECT *, 
    (SELECT Table1.Column 
     FROM Table1 
     INNER JOIN Table2 ON Table1.Table2Id = Table2.Id 
    ) as tmp 
FROM Table2 WHERE tmp = 1

I know I can take a workaround but I would like to know if this syntax is possible as it is (I think) in Mysql.

+2  A: 
SELECT  *
FROM    (
        SELECT  t.*,
                (
                SELECT  Table1.Column
                FROM    Table1
                INNER JOIN
                        Table2
                ON      Table1.Table2Id = Table2.Id
                ) as tmp
        FROM    Table2 t
        ) q
WHERE   tmp = 1

This is valid syntax, but it will fail (both in MySQL and in SQL Server) if the subquery returns more than 1 row

What exactly are you trying to do?

Please provide some sample data and desired resultset.

Quassnoi
At least this answer does not assume he *really wanted* to select a column value.
Neil Trodden
+8  A: 

The query you posted won't work on sql server, because the sub query in your select clause could possibly return more than one row. I don't know how MySQL will treat it, but from what I'm reading MySQL will also yield an error if the sub query returns any duplicates. I do know that SQL Server won't even compile it.

The difference is that MySQL will at least attempt to run the query and if you're very lucky (Table2Id is unique in Table1) it will succeed. More probably is will return an error. SQL Server won't try to run it at all.

Here is a query that should run on either system, and won't cause an error if Table2Id is not unique in Table1. It will return "duplicate" rows in that case, where the only difference is the source of the Table1.Column value:

SELECT  Table2.*, Table1.Column AS tmp
FROM Table1 
INNER JOIN Table2 ON Table1.Table2Id = Table2.Id
WHERE Table1.Column = 1

Perhaps if you shared what you were trying to accomplish we could help you write a query that does it.

Joel Coehoorn
my thoughts exactly...
gbn
I often use left joins instead of inner joins. The left join will guarentee one, and only one occurance of a row of data in a table I care about. That being said, I think Table2 is the table you care about, and should be listed first: 'FROM Table2 LEFT JOIN Table1 ...'
Kieveli
This site is *broken* if incorrect answers like this are getting voted up.
Neil Trodden
"left join will guarentee one, and only one occurance of a row of data in a table I care about." -- That is WRONG. A left join will guarantee one OR MORE occurance of the row.
Joel Coehoorn
@Neil: care to explain why it's wrong?
Joel Coehoorn
+1 It looks to me like this answer would give the same result as the OP's query (if that query were legal).
Bill Karwin
@Bill Karwin: actually, the query in this answer will return rows in cases where the OP query would have failed with an exception. The query in this answer returns a different result set, unless we take a big leap and pre-assume that Table2Id column in Table1 is UNIQUE.
spencer7593
@Joel Coehoom: this doesn't answer the OP question. Is this syntax supported by SQL Server? The answer is NO.
spencer7593
Added some text that should appease the doubters.
Joel Coehoorn
@Joel Coehoorn: you are correct that the syntax provided in the OP question [sic] won't work on sql server. What makes it invalid is the reference (in the predicate) to the alias in the SELECT list. If that reference is removed, SQL Server will actually prepare a plan and attempt to execute the query. You are correct, in that if a "too many rows" exception is thrown during execution, the query will fail and not return a result set. A simple test case will show SQL Server can and will return a valid result set from such a query.
spencer7593
A: 

That kind of syntax is basically valid (you need to move the where tmp=... to on outer "select * from (....)", though), although it's ambiguous since you have two sets named "Table2"- you should probably define aliases on at least one of your usages of that table to clear up the ambiguity.

Unless you intended that to return a column from table1 corresponding to columns in table2 ... in which case you might have wanted to simply join the tables?

araqnid
It's not permitted (in SQL Server 2005) to reference an alias for an expression in the SELECT list in the query predicate.
spencer7593
good point, and in fact that's true in all DBs I've used as far as I can remember.
araqnid
+2  A: 

I agree with Joel's solution but I want to discuss why your query would be a bad idea to use (even though the syntax is essentially valid). This is a correlated subquery. The first issue with these is that they don't work if the subquery could possibly return more than one value for a record. The second and more critical problem (in my mind) is that they must work row by row rather than on the set of data. This means they will virtually always affect performance. So correlated subqueries should almost never be used in a production system. In this simple case, the join Joel showed is the correct solution.

If the subquery is more complicated, you may want to turn it into a derived table instead (this also fixes the more than one value associated to a record problem). While a derived table looks a lot like a correlated subquery to the uninitated, it does not perform the same way because it acts on the set of data rather than row-by row and thus will often be significantly faster. You are essentially making the query a table in the join.

Below is an example of your query re-written as a derived table. (Of course in production code you would not use select * either especially in a join, spell out the fields you need)

SELECT *     
FROM Table2 t2
JOIN
(SELECT Table1.[Column], Table1.Table2Id  as tmp      
FROM Table1      
INNER JOIN Table2 ON Table1.Table2Id = Table2.Id     ) as t
ON t.Table2Id = Table2.Id
WHERE tmp = 1
HLGEM
+1 Good points, but FWIW, MySQL doesn't use the square brackets for delimited identifiers. That's a Microsoft/Sybase convention. In MySQL, use back-quotes by default, or set SQL_MODE to ANSI_QUOTES and use standard double-quotes.
Bill Karwin
I figured he didn't really have a column named column, but I did not know mysql didn't allow the brackets. I just put the brackets in to test my syntax. Thanks for the info, never know when I might someday need to know mysql.
HLGEM
A: 

You've already got a variety of answers, some of them more useful than others. But to answer your question directly:

No, SQL Server will not allow you to reference the column alias (defined in the select list) in the predicate (the WHERE clause). I think that is sufficient to answer the question you asked.

Additional details:

(this discussion goes beyond the original question you asked.)

As you noted, there are several workarounds available.

Most problematic with the query you posted (as others have already pointed out) is that we aren't guaranteed that the subquery in the SELECT list returns only one row. If it does return more than one row, SQL Server will throw a "too many rows" exception:

 
    Subquery returned more than 1 value.
     This is not permitted when the subquery
     follows =, !=, , >= or when the
     subquery is used as an expression.

For the following discussion, I'm going to assume that issue is already sufficiently addressed.

Sometimes, the easiest way to make the alias available in the predicate is to use an inline view.

SELECT v.*
  FROM ( SELECT * 
              , (SELECT Table1.Column 
                   FROM Table1 
                   JOIN Table2 ON Table1.Table2Id = Table2.Id
                  WHERE Table1.Column = 1
                ) as tmp 
           FROM Table2
       ) v   
 WHERE v.tmp = 1

Note that SQL Server won't push the predicate for the outer query (WHERE v.tmp = 1) into the subquery in the inline view. So you need to push that in yourself, by including the WHERE Table1.Column = 1 predicate in the subquery, particularly if you're depending on that to make the subquery return only one value.

That's just one approach to working around the problem, there are others. I suspect that query plan for this SQL Server query is not going to be optimal, for performance, you probably want to go with a JOIN or an EXISTS predicate.

NOTE: I'm not an expert on using MySQL. I'm not all that familiar with MySQL support for subqueries. I do know (from painful experience) that subqueries weren't supported in MySQL 3.23, which made migrating an application from Oracle 8 to MySQL 3.23 particularly painful.

Oh and btw... of no interest to anyone in particular, the Teradata DBMS engine DOES have an extension that allows for the NAMED keyword in place of the AS keyword, and a NAMED expression CAN be referenced elsewhere in the QUERY, including the WHERE clause, the GROUP BY clause and the ORDER BY clause. Shuh-weeeet

spencer7593