Sql-server – Effective dates, complex requirements

sql serversql server 2014

I have a table with, say, preferred colors.
For every person, we store the favorite color and the effective date. If the person changes preference, we store the new one with a newer effective date.
Example:

name   color   eff_date
John   Green   2014-03-07
Luis   Red     2014-03-07
Luis   Yellow  2015-05-02
Nina   Blue    2015-07-06

If we want to take the current preferred color of Luis:

SELECT color
  FROM fav_colors
 WHERE name='Luis'
   AND eff_date=( SELECT max(eff_date) FROM fav_colors WHERE name='Luis' )

Now we decide that persons can change their favorite color anytime, but the change will only be effective from 1st January. So even if Luis entered a record that says that his favorite color is Yellow, the query must return the old value (Red) until 1st January 2016.

SELECT color
  FROM fav_colors
 WHERE name='Luis'
   AND eff_date=( SELECT max(eff_date) FROM fav_colors WHERE name='Luis' and eff_date < '1 jan 2015' )

This query seems ok, but will not return any record for Nina. The rules say that a newcomer can choose the favorite color immediately, without waiting for next 1st January.
In other words, "if possible, take a record from 1st January or earlier; if not possible, any record; take the most recent".
This is what I was able to write:

SELECT color
  FROM fav_colors
 WHERE name='Luis'
   AND eff_date= ISNULL(
       ( SELECT max(eff_date) FROM fav_colors WHERE name='Luis' and eff_date < '1 jan 2015' ),
       ( SELECT max(eff_date) FROM fav_colors WHERE name='Luis' )
       )

Is there a more elegant, or more efficient, way to achieve this result?

I have no control on the tables structure, they are from PeopleSoft.

If Nina made two selections this year I want to show the most recent one.

Best Answer

You can simplify the first and second query with TOP / ORDER BY:

SELECT TOP (1) color
  FROM fav_colors
 WHERE name='Luis'
   AND eff_date < '2015-01-01'
ORDER BY eff_date DESC ;

It won't however return anything for Nina. You could use another subquery and then combine them with either UNION ALL and one more TOP or with COALESCE() (or ISNULL() as in your query):

SELECT color = COALESCE(
    ( SELECT TOP (1) color
        FROM fav_colors
       WHERE name='Luis'
         AND eff_date < '2015-01-01'
      ORDER BY eff_date DESC
    ),
    ( SELECT TOP (1) color
        FROM fav_colors
       WHERE name='Luis'
         AND eff_date >= '2015-01-01'
      ORDER BY eff_date ASC
    ) ) ;

If you want to do this check for more than one (or all of the) persons on the table, you can use OUTER APPLY, as @RobFarley's explains in his answer.)