views:

157

answers:

5

In Microsoft SQL Server 2005 or above, I would like to get the first row, and if there is no matching row, then return a row with default values.

SELECT TOP 1 ID,Name
FROM TableName
UNION ALL 
SELECT 0,''
ORDER BY ID DESC

This works, except that it returns two rows if there is data in the table, and 1 row if not. I'd like it to always return 1 row. I think it has something to do with EXISTS, but I'm not sure. It would be something like:

SELECT TOP 1 * FROM Contact 
WHERE EXISTS(select * from contact)

But if not EXISTS, then SELECT 0,''

+1  A: 
IF EXISTS ( SELECT TOP 1 ID, Name FROM TableName )
 BEGIN
     SELECT TOP 1 ID, Name FROM TableName
 END
ELSE
 BEGIN
 --exists returned no rows
 --send a default row
 SELECT 0, ''
 END
JonH
It isn't very efficient to select the data two times, once in the IF and again for the result set. Also, a line comment in TSQL is _--_, and a multi line comment is _/*_ followed by _*/_ not a single quote _'_, like in VB.
KM
Sorry was doing vb.net as I was helping I definately do realize that though --comment and /*comment*/ will edit.
JonH
@KM: He isn't really "selecting the data two times." An EXISTS query works different from a regular query. While I agree it's less efficient to do it this way, the cost for the EXISTS here is on the order of a read or three, no matter how full the table is.
Emtucifor
For clarity please edit your post to say IF EXISTS (SELECT 1 FROM TableName) instead of duplicating the query (which makes it possibly confusing to people, especially with the meaningless TOP in there).
Emtucifor
+4  A: 

Use:

  SELECT TOP 1
         x.id,
         x.name
    FROM (SELECT t.id,
                 t.name
            FROM TABLENAME t
          UNION ALL
          SELECT 0, 
                 '') x
ORDER BY id DESC

Using a CTE equivalent:

WITH query AS (
    SELECT t.id,
           t.name
      FROM TABLENAME t
    UNION ALL
    SELECT 0, 
           '')
  SELECT TOP 1
         x.id,
         x.name
    FROM query x
ORDER BY x.id DESC
OMG Ponies
+1  A: 

put the top oustide of the UNION query

SELECT TOP 1 * FROM(
SELECT  ID,Name
FROM TableName
UNION ALL 
SELECT 0,''
) z
ORDER BY ID DESC
SQLMenace
+2  A: 
CREATE TABLE #sample(id INT, data VARCHAR(10))

SELECT TOP 1 id, data INTO #temp FROM #sample
IF @@ROWCOUNT = 0 INSERT INTO #temp VALUES (null, null)
SELECT * FROM #temp
Rubens Farias
this will work if you insert (0,'') as the user specifies and not (NULL, NULL). However there is a lot of work/database processing needed to create and insert into a temp table, the method of putting the TOP 1 into outside of a derived table with a UNION, is MUCH more efficient. It would be terrible to have this in production where it is used with a high frequency.
KM
you're right @KM, ty
Rubens Farias
Using a temp table costs about 200 reads more than other methods. It's good for people to know it's possible, but I think using a temp table should be last on anyone's list.
Emtucifor
+4  A: 

What happens when the table is very full and you might want to specify which row of your top 1 to get, such as the first name? OMG Ponies' query will return the wrong answer in that case if you just change the ORDER BY clause. His query also costs about 8% more CPU than this modification (though it has equal reads)

SELECT TOP 1 *
FROM (
   SELECT TOP 1 ID,Name
   FROM TableName
   ORDER BY Name
   UNION ALL
   SELECT 0,''
) X
ORDER BY ID DESC

The difference is that the inner query has a TOP 1 also, and which TOP 1 can be specified there (as shown).

Just for fun, this is another way to do it which performs very closely to the above query (-15ms to +30ms). While it's more complicated than necessary for such a simple query, it demonstrates a technique that I don't see other SQL folks using very often.

SELECT
   ID = Coalesce(T.ID, 0),
   Name = Coalesce(T.Name, '')
FROM
   (SELECT 1) X (Num)
   LEFT JOIN (
      SELECT TOP 1 ID, Name
      FROM TableName
      ORDER BY ID DESC
   ) T ON 1 = 1 -- effective cross join but does not limit rows in the first table
Emtucifor