You didn't specify your database product in your question, but I'm going to guess from the @Pararemter naming style that you're using SQL Server.
Except for the unusual requirement of interpreting empty input to mean 'all', this a restatement of the problem of Arrays in SQL, explored throughly by Erland Sommarskog. Read all his articles on the subject for a good analysis of all the techniques you can use.
Here I'll explain how to use a table-valued parameter to solve your problem.
Execute the following scripts all together to set up the test environment in an idempotent way.
Creating a sample solution
First create a new empty test database StackOverFlow13556628
:
USE master;
GO
IF DB_ID('StackOverFlow13556628') IS NOT NULL
BEGIN
ALTER DATABASE StackOverFlow13556628 SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE StackOverFlow13556628;
END;
GO
CREATE DATABASE StackOverFlow13556628;
GO
USE StackOverFlow13556628;
GO
Next, create a user-defined table type PrinciapalList
with one column principal_id
. This type contains the input values with which to query the system table sys.database_principals
.
CREATE TYPE PrincipalList AS TABLE (
principal_id INT NOT NULL PRIMARY KEY
);
GO
After that, create the stored procedure GetPrincipals
which takes a PrincipalList
table-valued parameter as input, and returns a result set from sys.database_principals
.
CREATE PROCEDURE GetPrincipals (
@principal_ids PrincipalList READONLY
)
AS
BEGIN
IF EXISTS(SELECT * FROM @principal_ids)
BEGIN
SELECT *
FROM sys.database_principals
WHERE principal_id IN (
SELECT principal_id
FROM @principal_ids
);
END
ELSE
BEGIN
SELECT *
FROM sys.database_principals;
END;
END;
GO
If the table-valued parameter contains rows, then the procedure returns all the rows in sys.database_principals
that have a matching principal_id
value. If the table-valued parameter is empty, it returns all the rows.
Testing the solution
You can query multiple principals like this:
DECLARE @principals PrincipalList;
INSERT INTO @principals (principal_id) VALUES (1);
INSERT INTO @principals (principal_id) VALUES (2);
INSERT INTO @principals (principal_id) VALUES (3);
EXECUTE GetPrincipals
@principal_ids = @principals;
GO
Result:
principal_id name
1 dbo
2 guest
3 INFORMATION_SCHEMA
You can query a single principal like this:
DECLARE @principals PrincipalList;
INSERT INTO @principals (principal_id) VALUES (1);
EXECUTE GetPrincipals
@principal_ids = @principals;
GO
Result:
principal_id name
1 dbo
You can query all principals like this:
EXECUTE GetPrincipals;
Result:
principal_id name
0 public
1 dbo
2 guest
3 INFORMATION_SCHEMA
4 sys
16384 db_owner
16385 db_accessadmin
16386 db_securityadmin
16387 db_ddladmin
16389 db_backupoperator
16390 db_datareader
16391 db_datawriter
16392 db_denydatareader
16393 db_denydatawriter
Remarks
This solution is inefficient because you always have to read from the table-valued parameter twice. In practice, unless your table-valued parameter has millions of rows, it will probably not be the major bottleneck.
Using an empty table-valued parameter in this way feels unintuitive. A more obvious design might simply be to have two stored procedures - one that returns all the rows, and one that returns only rows with matching ids. It would be up to the calling application to choose which one to call.