SQLServerCentral Article

Parameterizing Views

,

Everyone knows you can't parameterize a view. If you need parameters, you

should be using a stored procedure, right? Wait, is that right? Technically it is. Views simply do not accept parameters. The problem with

using a stored procedure is that it returns a resultset that you can't join to

directly. You can use the insert/exec syntax to put the results into a table and

work with it from there, or you could use OpenRowset.

That leaves you with the option of filtering against the view using a where

clause. Filtering outside the view, not inside. If you put the filter inside the

view then it applies to all users. I'm more interested in the scenario where the

rows returned from a view vary based on some other piece of information.

Let's say we have a reasonably simple view like this:

create view vCustomer as
select * from dbo.Customer where status = 1

Just for discussion, status of 1 indicates that the row is an active

customer. Let's also include a column called employee which contains the ID (int)

of the salesperson who would receive commission based on sales to that customer.

How would we find all the customers belonging to salesperson = 3?

select * from dbo.vCustomer where employeeid = @EmpID

Easy enough. We tell everyone to base all their reports on the vCustomer view

and to include the employee=@EmpID to get just the data they need. Soon we have

hundreds of reports all based on the view and life is good. Yes, I realize the

view is simple, but it could (and should) encapsulate other joins to make

reporting simple and to centralize code. Plus each report will probably include

main other tables that join to the view as needed for each report.

We have thousands of users and all are happy. Until one day, one manager says

"These reports are great! But what I really need is for each report to include

all the sales people who report to me!". Another manager hears this and says

"Yes! That would be so cool!". Off they go to their manager and so the story

continues until 37 minutes later when the directive arrives from far far up the

chain of command "Change all the reports so that they show employees that report

to that manager". Timeframe? Well, ASAP of course!

Humor aside, let's talk about that. Our new requirement is if the user is

just a salesperson, they see only their customers. If the user is a manager,

they see any customer they "own" plus all that are owned by their team. We

already have the relationships in a table called Employees that looks something

like this:

create table Employees (EmployeeID int, ManagerID int)

A self referencing table in best database fashion. What does that do to our

queries for the reports? We're going to need code that figures out if the user

is a plain salesperson or if they are a manager. If they are a manager, we'll

need a list of all employees that report to them. So, our query should return

one row based on the employeeid, plus potentially more rows if they have anyone

that reports to them. Something like this:

select employeeid from dbo.Employees where EmployeeID = @EmpID or ManagerID = @EmpID

That means we need to change every report to look something like this:

select * from dbo.vCustomer where employeeid in (select distinct employeeid from dbo.Employees
where EmployeeID = @EmpID or ManagerID = @EmpID)

So now we need to change a hundred reports or web pages or stored procedures.

Search and replace? It will work, just takes time to make sure you're not

breaking anything. Plus, you have to test each one to be sure. You do test don't

you?

Now we're back to where we started. It sure would be nice if we could just

modify that view, test it once, have all the reports leverage that change

automatically. Wouldn't it??? Is there a way to do it?

If we could pass in the @EmpID, it would be easy, it would be this:

create view vCustomers @EmpID int as
select * from dbo.vCustomer where employeeid in (select distinct employeeid from dbo.Employees
where EmployeeID = @EmpID or ManagerID = @EmpID)

Not valid syntax, just wishful thinking. Not only are we not able to pass a

parameter in, we can't even use a variable in the view. A view has to consist of

tables/views plus an optional where clause. If we had the parameter in a

table...that get you thinking?

Suppose, just suppose, that we have a standard entry point for all our

reports. One piece of code that we always use to login or open the connection or

run the report. If we could push a row into a table before we used the

view, we could join to it! To make it work, we would first define a table:

Create table LoginInfo (UserName varchar(255), employeeID int)

Then each time we connected, we would do this (doing the delete just to be

thorough):

delete from dbo.LoginInfo where userName=User_Name()
insert into dbo.LoginInfo (UserName, EmployeeID) values (User_Name(), @EmpID)

And we change our view to reference it:

create view vCustomers as
select * from dbo.vCustomer where employeeid in (select distinct employeeid from dbo.Employees
where
EmployeeID in (Select employeeid from dbo.LoginInfo where username=User_Name())
or
ManagerID in (Select employeeid from dbo.LoginInfo where username=User_Name())

Effectively we've parameterized the view. Or hacked a solution depending on your

point of view. It's not a miracle cure. It relies on the existence of a

centralized login/connection process where we can do the insert. If we had to

add that insert to every report we'd probably be better off doing search/replace

or refactoring the view. This also won't work if all the users have the same

user name, an alternative would be to use @@SPID.

The situation to illustrate

the problem/solution is contrived. More thought up front in the view or the base

query against the view probably would have headed this off. My goal here is to

get you thinking about changing the behavior of a view without changing it's

signature/interface, and to get you thinking that a view should really be about

encapsulation and code reduction.

Rate

4.4 (5)

You rated this post out of 5. Change rating

Share

Share

Rate

4.4 (5)

You rated this post out of 5. Change rating