A "UNION" query can be very useful. Take sorting for example.  If I want to select 2 types of data from the same table using different conditions and order by my conditions union is a great tool. See my previous post on Using UNION in your queries.  If you also like using TOP you may run into an unexpected behavior when using UNION.  Let's say you had 2 tables, portalUsers and intranetUsers and you wanted to get the most recent entries in each that were unapproved. If you try something like this:
<cfquery name="myquery" datasource="mydsn">
      SELECT    TOP 1 fname, lname, 
            'Portal' AS sourceTable, 
            dateAdded
      FROM    portalUsers
      WHERE   unApproved = 1
   UNION
      SELECT TOP 1 fname, lname, 
            'Intranet' AS sourceTable, 
            dateAdded
      FROM   intranetUsers
      WHERE   Approved = 0
   ORDER BY dateAdded      
</cfquery>
You might expect to get 2 records equal to the most recently added portal user and the most recently added intranet user - right? You could take the first record and you would be good to go. Actually, unless the "portalUsers" table has a clustered index ordered by dateAdded, this is 
not the case.
 
									
								
								
	The reason is simple, the condition of top is met for both of the queries before the sort order is applied.  If you think about it, this makes sense.  To do it any other way would require the order by clause to be run three times - once for each query and then once for the returning result set.  Instead, the UNION clause goes out and gets the data for query 1, then the data for query 2 and then sorts the data. The result is that you will not be able to depend on getting the records you want from this UNION query. 
The Fix: Use a View
	One fix would be to use a view. Simply create a view without the TOP key word - like so:
	
<cfquery name="myquery" datasource="mydsn">
CREATE VIEW vwUnApprovedUsers
AS
      SELECT    fname, lname, 
            'Portal' AS sourceTable, 
            dateAdded
      FROM    portalUsers
      WHERE   unApproved = 1
   UNION
      SELECT  fname, lname, 
            'Intranet' AS sourceTable, 
            dateAdded
      FROM   intranetUsers
      WHERE   Approved = 0
   ORDER BY dateAdded      
</cfquery>
You could then select directly from this view using TOP 1 and get the most recent record.
The Fix: An "In-line" View?
 Still another way is to use an "inline view" and select from a subselect using top 1. That would look something like this:
 	
<cfquery name="myquery" datasource="mydsn">
   SELECT TOP 1 *
   FROM 
   ( SELECT    fname, lname, 
            'Portal' AS sourceTable, 
            dateAdded
      FROM    portalUsers
      WHERE   unApproved = 1
   UNION
      SELECT  fname, lname, 
            'Intranet' AS sourceTable, 
            dateAdded
      FROM   intranetUsers
      WHERE   Approved = 0
      ) AS inLineView
   ORDER BY dateAdded      
</cfquery>
Ahah!  This creates a dataset and then allows you to pull out only the first record. Make sure and test for performance to see if one or the other of these methods works better. NOTE: The subselect alias is required ("AS inLineView"). If you do not alias the subselect the query will error out - but there will be little indication in the error why it's throwing an exception.  
 
								
							    
								    
								    	
								    
									
									
								
							 
						
Related Blog Entries