T-SQL: ¿Combinaciones de tablas opcionales?

Let's say I have a stored procedure that will do complicated logic when the parameter @ComplicatedSearch = 1. When it is set to 1, I populate the variable table @ValidIds with valid rows that this procedure can return. When it's 0, the logic is bypassed and we don't need to filter out the rows to return.

So, past that logic, I end up with a statement like so:

SELECT
    m.*
    ,a.*
FROM
    MyTable m
    INNER JOIN AdditionalInfoTable a
       ON m.Id = a.MyTableId
WHERE
   (@ComplicatedSearch = 0 OR EXISTS(SELECT * FROM @ValidIds WHERE Id = m.Id))

This works fine; however, I believe it would be more efficient to join MyTable to @ValidIds when applicable opposed to using EXISTS(), especially when MyTable contains a large number of rows.

Is there any way of doing something like what I have below without writing multiple queries? (the actual query is very large, so having multiple versions with and without joins would not be ideal)

SELECT
    m.*
    ,a.*
FROM
    MyTable m
    ONLY DO THIS IF ComplicatedSearch = 1 PLEASE: INNER JOIN @ValidIds v
       ON m.Id = v.Id
    INNER JOIN AdditionalInfoTable a
       ON m.Id = a.MyTableId

preguntado el 08 de noviembre de 11 a las 15:11

Your belief that JOIN sería más eficaz que EXISTS está fuera de lugar. EXISTS is implemented very efficiently in SQL Server as a semi join and can use any of the available join types. -

@MartinSmith: I think what I'm trying to avoid here is the full table scan on MyTable; I was hoping by doing the join it would grab the rows more efficiently -

Not sure exactly what plan you would get with this without setting up a test this end. You could try adding OPTION (RECOMPILE) so it takes account of both the value of @ComplicatedSearch and the table variable cardinality. Also make sure the table variable is declared with Id primary key to get an index created on that column. -

2 Respuestas

Otra opción:

SELECT
   m.*     
  ,a.*
FROM MyTable m    
 INNER JOIN @ValidIds v
  ON m.Id = case
              when @ComplicatedSearch = 1 then v.Id  --  Filter rows
              else m.Id  --  Select all rows
            end
 INNER JOIN AdditionalInfoTable a    
  ON m.Id = a.MyTableId

You'd need to do performance testing to see if it's efficient enough. Some quick tests showed that (on my data) the same query plan was generated regardless of whether the first call was for complex or not complex.

The "bifurcated" approach (separate procedures) should be most efficient. However, having the same code with just a minor modification in two different places can be a major pain to support, particularly when you have to add subsequent changes to all "instances" of that code. And if the overall size of the data (e.g. overall performance) isn't too great, the "one size mostly fits all" approach might be most effective.

respondido 08 nov., 11:20

Perfect! The only thing to note is I will need to insert a dummy row into that table when @ComplicatedSearch is 0 to prevent that inner join from wiping everything out - John

Doh! I missed that! (I just hard-coded some values when testing the generated query plans.) - Philip Kelley

Not sure why this is accepted as it doesn't appear to work. In the case where you join back to the same table, SQL server still has to include rows for @validIds because that table was joined in. The result is a substantially larger amount of rows (and a much more expensive and slower running query) than intended. - user2719430

If you are after efficiency you should note that stored procedures will calculate the query plan on the first run then cache it and use the same one thereafter. In this case it means that it will choose to use @ValidIds depending on the first value of @ComplicatedSearch

Thus I would write the procedure more like

if @ComplicatedSearch = 1
   exec simple_search
else
   exec complex_search

Where simple_search includes your first query and complex_search also joins to @ValidIds

Yest you get the query twice but to get over that I would create a view

create view helper as
begin
SELECT
    m.*
    ,a.*
FROM
    MyTable m
    INNER JOIN AdditionalInfoTable a
       ON m.Id = a.MyTableId
end

and then simple selects from that view and complex joins to @ValidIds

respondido 08 nov., 11:20

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.