It's funny how sometimes the simplest-looking code can lead us down a rabbit hole, isn't it? I was recently digging into the syntax of SQL's CASE statement, specifically how it interacts with functions like RAND(), and it brought up a rather common point of confusion: why, oh why, does it sometimes return NULL?
Imagine you're trying to assign a random city name from a list based on a generated random number. You might write something like this:
SELECT CASE ROUND(((50 - 1 - 1) * RAND() + 1), 0)
WHEN 1 THEN 'New York'
WHEN 2 THEN 'Los Angeles'
-- ... and so on for 50 cities
END
Seems straightforward, right? You're generating a number between 1 and 50, and then mapping that number to a city. But then, you run it, and sometimes, instead of a city, you get NULL. It’s a bit like asking a friend for a recommendation and getting a shrug.
So, what's going on under the hood? It turns out that the CASE statement, especially when dealing with functions that can be evaluated multiple times within a single statement (like RAND() in some SQL dialects, particularly those influenced by Sybase), can behave in a way that's not immediately intuitive. The core issue is that the RAND() function might be evaluated independently for each WHEN clause, rather than just once for the entire CASE expression. This means the number generated for the first WHEN might be different from the number generated for the second WHEN, and so on. If none of these independently generated random numbers happen to match any of the WHEN conditions, and there's no ELSE clause, the result defaults to NULL.
It's a bit like checking the weather forecast for tomorrow, then checking it again an hour later and getting a slightly different prediction – except here, it's happening within the same query execution.
Finding a Workaround
Fortunately, the SQL community has explored this, and there are elegant solutions. One common approach is to ensure the random number is generated only once before it's used in the CASE statement. This can be achieved by:
-
Using a Variable: Assign the random number to a variable first, and then use that variable in your
CASEstatement. This guarantees the number is generated once and then reused.DECLARE @RandomNum INT; SET @RandomNum = CAST((50 * RAND() + 1) AS INT); SELECT CASE @RandomNum WHEN 1 THEN 'New York' WHEN 2 THEN 'Los Angeles' -- ... ELSE 'Unknown' END; -
Using a Common Table Expression (CTE) or Subquery: Similar to using a variable, you can generate the random number in a CTE or a derived table (subquery) and then select from it, feeding the single generated value into your
CASEstatement.WITH RandomValue AS ( SELECT ROUND(((50 - 1 - 1) * RAND() + 1), 0) AS Rn ) SELECT CASE Rn WHEN 1 THEN 'New York' WHEN 2 THEN 'Los Angeles' -- ... END FROM RandomValue; -
Seeding
RAND(): For more controlled randomness, especially when you need consistent results for testing or specific scenarios, you can seed theRAND()function. Using something likeNEWID()as a seed can help ensure a unique random number for each row if you're updating or inserting data.SELECT CASE Rn WHEN 1 THEN '1' WHEN 2 THEN '2' END FROM ( SELECT ROUND(((3 - 1 - 1) * RAND(CAST(NEWID() AS VARBINARY)) + 1), 0) AS Rn ) AS aView;
It's a good reminder that even in structured languages like SQL, the devil can be in the details, especially when functions that produce variable output are involved. Understanding how the CASE statement evaluates its conditions is key to avoiding those unexpected NULLs and getting the results you expect.
