Summarizing some of the main points from our [chat room discussion][1]:

---

Generally speaking, SQL Server caches a **single plan for each statement**. That plan must be **valid for all possible future parameter values**.

It is not possible to cache a *seek* plan for your query, because that plan would not be valid if, for example, *@productid* is null.

In some future release, SQL Server might support a single plan that dynamically chooses between a scan and a seek, depending on runtime parameter values, but that is not something we have today.

###General problem class

Your query is an example of a pattern variously referred to as a "catch all" or "dynamic search" query. There are various solutions, each with their own advantages and disadvantages. In modern versions of SQL Server (2008+), the main options are:

* `IF` blocks
* `OPTION (RECOMPILE)`
* Dynamic SQL using `sp_executesql`

The most comprehensive work on the topic is probably by Erland Sommarskog, which is included in the references at the end of this answer. There is no getting away from the complexities involved, so it is necessary to invest some time in trying each option out to understand the trade-offs in each case.

###`IF` blocks

To illustrate an `IF` block solution for the specific case in the question:

    IF @productid IS NOT NULL AND @priceid IS NOT NULL
    BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid
        AND T.priceID = @priceid;
    END;
    ELSE IF @productid IS NOT NULL
    BEGIN
        SELECT 
            T.productID,
            T.priceID
        FROM dbo.Transactions AS T
        WHERE
            T.productID = @productid;
    END;
    ELSE IF @priceid IS NOT NULL
    BEGIN
        SELECT 
            T.productID,
            T.priceID
        FROM dbo.Transactions AS T
        WHERE
            T.priceID = @priceid;
    END;
    ELSE
    BEGIN
        SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T;
    END;

This contains a separate statement for the four possible null-or-not-null cases for each of the two parameters (or local variables). There is a potential problem there with parameter sniffing, which might require an `OPTIMIZE FOR` hint on each query. Please see the references section to explore these types of subtleties.

### Recompile

As noted above an in the question, you could also add an `OPTION (RECOMPILE)` hint to get a fresh plan (seek or scan) on each invocation. Given the relatively slow frequency of calls in your case (once every ten seconds on average, with a sub-millisecond compilation time) it seems likely this option will be suitable for you:

    SELECT
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        (T.productID = @productid OR @productid IS NULL)
        AND (T.priceID = @priceid OR @priceid IS NULL)
    OPTION (RECOMPILE);

---

It is also possible to combine features from the above options in creative ways, to make the most of the advantages of each method, while minimizing the downsides. There really is no shortcut to understanding this stuff in detail, then making an informed choice backed by realistic testing.

###Further reading

* [Parameter Sniffing, Embedding, and the `RECOMPILE` Options][2]
* [Dynamic Search Conditions in T-SQL][3] by Erland Sommarskog
* [High Performance Procedures][4] by Kimberly Tripp


  [1]: http://chat.stackexchange.com/rooms/36347/discussion-between-ypercube-and-royi-namir
  [2]: http://sqlperformance.com/2013/08/t-sql-queries/parameter-sniffing-embedding-and-the-recompile-options
  [3]: http://sommarskog.se/dyn-search.html
  [4]: http://www.sqlskills.com/blogs/kimberly/high-performance-procedures/