Question

I have a TVP myTVP implemented as select val.* from vw_MyView val. The other day, I modified vw_MyView, replacing:
select Foo,Bar from DB with
select Foo, Bash, Bar from DB.

This had a strange side effect: Calling select Bar from myTVP() returned a column named Bar populated with the contents of Bash (despite the fact that Bar and Bash don't even have the same type).

Anonymized plan demonstrating the problem: https://www.brentozar.com/pastetheplan/?id=ryJLJmqdl

Note that Object2 and Object1 are identical, but the output list of the left-most Nested Loop is completely different between the two plans.

Notes:

  • There are no plan hints in use. No trace flags are set.
  • CheckDB found no errors.
  • Using option(recompile) did not fix the issue.
  • Running right-click alter on the TVP with no changes fixed the issue.
  • Running right-click alter on the TVP with a name change fixed the issue, but the new TVP was identical to the previous other than the name.
  • Using SQL 2014 Standard Edition.

Questions:

  1. Is it normal for adding a new column to a view to change which column of the view is used in by other references to that view?
  2. Under what circumstances will adding a column to a view cause such behavior?
Was it helpful?

Solution

Yes, this is expected. Functions and views that use SELECT * end up storing part of the metadata themselves relating to the underlying columns, so it is very easy for them to get confused. I talk about this problem in the following post from 2009:

And also in my recent GroupBy presentation:

How do you avoid this problem?

  1. Don't use SELECT * in objects.
  2. Make your functions and views WITH SCHEMABINDING, which means you can't modify the underlying objects without also modifying the objects that reference them (this also has the side effect of prevent * so win-win).
  3. In this specific case, you can fix the object so that Bar returns the correct data by using:

    EXEC sys.sp_refreshsqlmodule @name = N'dbo.myTVP';
    

Incidentally, the documentation for sys.sp_refreshsqlmodule describes this exact scenario:

Updates the metadata for the specified non-schema-bound stored procedure, user-defined function, view, DML trigger, database-level DDL trigger, or server-level DDL trigger in the current database. Persistent metadata for these objects, such as data types of parameters, can become outdated because of changes to their underlying objects.

As an aside, you should never use FROM myTVP() but instead always specify the schema, e.g. FROM dbo.myTVP(). I talk about that in an old post, too.

OTHER TIPS

This is caused by using Select * in the TVF. It also happens to views that use select * when columns are inserted to the underlying table between existing columns, or when columns are deleted.

This to me is the number one reason (among a few more big ones) not to use Select * anywhere except perhaps in an outer query that wraps a fully explicit subquery for example.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top