SQL query problem - sql-server

I'm new to StackOverflow, and new to SQL Server, I'd like you to help me with some troublesome query.
This is my database structure(It's half spanish, hope doesn't matter)
Database
My problem is that I don't now how to make a query that states which team is local and which is visitor(using table TMatch, knowing that the stadium belongs to only one team)
This is as far as I can get
Select P.NroMatch, (select * from fnTeam (P.TeamA)) as TeamA,(select * from fnTeam (P.TeamB)) as TeamB,
(select * from fnEstadium (P.CodEstadium)) as Estadium, (cast(P.GolesTeamA as varchar)) + '-' + (cast(P.GolesTeamA as varchar)) as Score,
P.Fecha
from TMatch P
Using this functions:
If object_id ('fnTeam','fn')is not null
drop function fnTeam
go
create function fnTeam(#CodTeam varchar(5))
returns table
return(Select Name from TTeam where CodTeam = #CodTeam)
go
select * from fnTeam ('Eq001')
go
----****
If object_id ('fnEstadium','fn')is not null
drop function fnEstadium
go
create function fnEstadium(#CodEstadium varchar(5))
returns table
return(Select Name from TEstadium where CodEstadium = #CodEstadium)
go
I hope I'd explained myself well, and I thank you help in advance
EDIT:
Thanks for the help, this is what I've been looking for
Select P.NroMatch,
CASE
WHEN Ts.CodTeam= Ta.CodTeamTHEN Ta.Name
ELSE Tb.Name
END
As TeamLocal,
CASE
WHEN Ts.CodTeam<> Ta.CodTeamTHEN Ta.Name
ELSE Tb.Name
END
As TeamVisitante,
Ts.Name as Estadium,
(cast(P.GolesTeamA as varchar)) + '-' + (cast(P.GolesTeamB as varchar)) as Score,
P.Fecha
from
TMatch P
join TTeamTa ON Ta.CodTeam= P.TeamA
join TTeamTb ON Tb.CodTeam= P.TeamB
join TEstadium Ts ON Ts.CodEstadium = P.CodEstadium

You do not need to use 'lookup' functions for this (and shouldn't), joins are a better approach:
Select
P.NroMatch,
Ta.Name as TeamA,
Tb.Name as TeamB,
Ts.Name as Estadium,
cast(P.GolesTeamA as varchar)) + '-' + (cast(P.GolesTeamA as varchar) as Score,
P.Fecha,
CASE
WHEN Ts.CodTeam = Ta.Name THEN Ta.Name
ELSE Tb.Name
END As HomeTeam,
CASE
WHEN Ts.CodTeam <> Ta.Name THEN Ta.Name
ELSE Tb.Name
END As VistorTeam
from
TMatch P
join TTeam Ta ON Ta.CodTeam = P.TeamA
join TTeam Tb ON Tb.CodTeam = P.TeamB
join TEstadium Ts ON Ts.CodEstadium = P.CodEstadium
If you are new to SQL, it might be useful searching SO for some resources such as these:
SQL Tutorial
SQL Tutorial

Related

Convert money datatype to 3 decimal places

I am trying to show my PT.TotalAmount(money) field in decimal format upto 3 decimal places. I tried using
CAST(ROUND(123.4567, 3) AS MONEY)
But I get error:
An object or column name is missing or empty. For SELECT INTO
statements, verify each column has a name. For other statements, look
for empty alias names. Aliases defined as "" or [] are not allowed.
Change the alias to a valid name.
Here is my SP:
Select PT.[ID] 'TransactionID', PT.BatchNumber, PT.SequenceNumber, PT.TransactionDate,
PT.TerminalID ,CAST(ROUND(PT.TotalAmount, 2) AS MONEY), PT.TransactionTypeID, TT.TransactionType,
PT.PAN 'EmbossLine',PT.PreBalanceAmount, PT.PostBalanceAmount, RefTxnID,
SettlementDate,PaidCash, CreditAmount, DiscountAmount,
RefPAN, PT.Remarks, ' ' + CashierCard as 'SupervisorCard',St.StoreID
into #Temp
from POS_Transactions PT inner join TransactionType TT on TT.TransactionTypeID = PT.TransactionTypeID
inner join Staff St on St.CardNumber=PT.CashierCard
where
PT.[ID] not in (Select distinct isnull(TransactionID,0) from Testcards)
and (PT.TransactionDate >= #DateFrom)
and (PT.TransactionDate < #DateTo)
and (PT.TransactionTypeID = #TransactionTypeID or #TransactionTypeID = -999)
select T.*, ' '+ C.EmbossLine as 'EmbossLine', ' '+ C.EmbossLine as 'EmbossLine1',
isnull(C.FirstName,'') +' '+ isnull(C.LastName,'') 'EmbossName',C.FirstName,C.LastName,City.CityName,Country.CountryName,Country.CurrencyName, PM.MerchantID , PM.MerchantName1, C.AccountNumber, C.VehicleNumber
from #Temp T
inner join Card C on C.EmbossLine= T.EmbossLine
inner join Terminal on Terminal.TerminalID = T.TerminalID
inner join Merchant PM on PM.MerchantID = Terminal.MerchantID
inner join City on City.CityID = PM.CityID
inner join Country on Country.CountryID = PM.CountryID
where C.Status <>'E3'
and C.CardID not in (Select distinct isnull(CardID,0) from Testcards)
and (PM.MerchantID =#MerchantID or #MerchantID='-999')
and (C.EmbossLine like '%'+#EmbossLine+'%' or #EmbossLine like '-999')
and (C.FirstName like '%'+#FirstName+'%' or #FirstName like '-999')
and (C.LastName like '%'+#LastName+'%' or #LastName like '-999')
and (PM.CountryID = #CountryID or #CountryID ='-999')
and(PM.CityID = #CityID or #CityID ='-999')
order by T.TransactionDate, MerchantName1, T.BatchNumber, T.SequenceNumber
drop table #Temp
Why am I getting this error? Whats wrong in conversion? When I simply write PT.TotalAmount in my Select statement, the query is completed successfully
Just add any alias name for CAST(ROUND(PT.TotalAmount, 2) AS MONEY) as roundedamont(whatever column name you want) in your sql query.
Select * into statement require each column have distinct name
Select PT.[ID] 'TransactionID', PT.BatchNumber, PT.SequenceNumber, PT.TransactionDate,
PT.TerminalID ,CAST(ROUND(PT.TotalAmount, 2) AS MONEY)<----Give Alias HERE

SQL Server 2012 - Manipulate string data for stored procedure

Can you give me some pointers (or point in the right direction on what search terms for google)? In a stored procedure I have a parameter #TAG (string). I receive '(14038314,14040071)' (for example) from another application that cannot be altered. In the stored procedure, I need to split apart '(14038314,14040071)' to put quotes around each string value, rebuild it, strip out the outer quotes,strip out the parens and pass it to #TAG in the query below so that it looks like the line commented out below?
SELECT
V.NAME AS VARIETY, TAGID
FROM
mfinv.dbo.onhand h
INNER JOIN
mfinv.dbo.onhand_tags t on h.onhand_id = t.onhand_id
INNER JOIN
mfinv.dbo.onhand_tag_details d on t.onhand_tag_id = d.onhand_tag_id
INNER JOIN
mfinv.dbo.FM_IC_PS_VARIETY V ON V.VARIETYIDX = d.VARIETYIDX
LEFT JOIN
mfinv.dbo.FM_IC_TAG TG ON TG.TAGIDX = t.TAGIDX
WHERE
h.onhand_id = (SELECT onhand_id FROM mfinv.dbo.onhand
WHERE onhand_id = IDENT_CURRENT('mfinv.dbo.onhand'))
AND TG.ID IN (#TAG)
--AND TG.ID IN ('14038314','14040071')
You can Use Dynamic SQL Like This
DECLARE #TAG Nvarchar(MAX)='14038314,14040071'
set #TAG=''''+REPLACE(#TAG,',',''',''')+''''
--select #TAG
DECLARE #SQL NVARCHAR(MAX)=N'
Select V.NAME AS VARIETY, TAGID
FROM mfinv.dbo.onhand h
INNER JOIN mfinv.dbo.onhand_tags t on h.onhand_id = t.onhand_id
INNER JOIN mfinv.dbo.onhand_tag_details d on t.onhand_tag_id = d.onhand_tag_id
INNER JOIN mfinv.dbo.FM_IC_PS_VARIETY V ON V.VARIETYIDX = d.VARIETYIDX
LEFT JOIN mfinv.dbo.FM_IC_TAG TG ON TG.TAGIDX = t.TAGIDX
WHERE h.onhand_id = (SELECT onhand_id FROM mfinv.dbo.onhand
WHERE onhand_id = IDENT_CURRENT (''mfinv.dbo.onhand''))
AND TG.ID IN ('+#TAG+')'
PRINT #SQL
EXEC (#SQL)
Here's what I did. Thank you all for responding. Thanks to dasblinkenlight for answering "How to replace first and last character of column in sql server?". Thanks to SQLMenace for answering "How Do I Split a Delimited String in SQL Server Without Creating a Function?".
Here's how I removed parenthesis:
#Tag nvarchar(256)
SET #Tag = SUBSTRING(#Tag, 2, LEN(#Tag)-2)
Here's how I split and rebuilt #Tag:
AND TG.ID in
(
SELECT SUBSTRING(',' + #Tag + ',', Number + 1,
CHARINDEX(',', ',' + #Tag + ',', Number + 1) - Number -1)AS VALUE
FROM master..spt_values
WHERE Type = 'P'
AND Number <= LEN(',' + #Tag + ',') - 1
AND SUBSTRING(',' + #Tag + ',', Number, 1) = ','
)

Forcing SQL Server to pre-cache entire database into memory

We have a client site with a 50Gb SQL 2012 database on a server with 100+ Gb of RAM.
As the application is used, SQL server does a great job of caching the db into memory but the performance increase from the caching occurs the SECOND time a query is run, not the first.
To try to maximize cache hits the first time queries are run, we wrote a proc that iterates through every index of every table within the entire DB, running this:
SELECT * INTO #Cache
FROM ' + #tablename + ' WITH (INDEX (' + #indexname + '))'
In an attempt to force a big, ugly, contrived read for as much data as possible.
We have it scheduled to run every 15 minutes, and it does a great job in general.
Without debating other bottlenecks, hardware specs, query plans, or query optimization, does anybody have any better ideas about how to accomplish this same task?
UPDATE
Thanks for the suggestions. Removed the "INTO #Cache". Tested & it didn't make a difference on filling the buffer.
Added: Instead of Select *, I'm selecting ONLY the keys from the Index. This (obviously) is more to-the-point and is much faster.
Added: Read & Cache Constraint Indexes also.
Here's the current code: (hope it's useful for somebody else)
CREATE VIEW _IndexView
as
-- Easy way to access sysobject and sysindex data
SELECT
so.name as tablename,
si.name as indexname,
CASE si.indid WHEN 1 THEN 1 ELSE 0 END as isClustered,
CASE WHEN (si.status & 2)<>0 then 1 else 0 end as isUnique,
dbo._GetIndexKeys(so.name, si.indid) as Keys,
CONVERT(bit,CASE WHEN EXISTS (SELECT * FROM sysconstraints sc WHERE object_name(sc.constid) = si.name) THEN 1 ELSE 0 END) as IsConstraintIndex
FROM sysobjects so
INNER JOIN sysindexes si ON so.id = si.id
WHERE (so.xtype = 'U')--User Table
AND ((si.status & 64) = 0) --Not statistics index
AND ( (si.indid = 0) AND (so.name <> si.name) --not a default clustered index
OR
(si.indid > 0)
)
AND si.indid <> 255 --is not a system index placeholder
UNION
SELECT
so.name as tablename,
si.name as indexname,
CASE si.indid WHEN 1 THEN 1 ELSE 0 END as isClustered,
CASE WHEN (si.status & 2)<>0 then 1 else 0 end as isUnique,
dbo._GetIndexKeys(so.name, si.indid) as Keys,
CONVERT(bit,0) as IsConstraintIndex
FROM sysobjects so
INNER JOIN sysindexes si ON so.id = si.id
WHERE (so.xtype = 'V')--View
AND ((si.status & 64) = 0) --Not statistics index
GO
CREATE PROCEDURE _CacheTableToSQLMemory
#tablename varchar(100)
AS
BEGIN
DECLARE #indexname varchar(100)
DECLARE #xtype varchar(10)
DECLARE #SQL varchar(MAX)
DECLARE #keys varchar(1000)
DECLARE #cur CURSOR
SET #cur = CURSOR FOR
SELECT v.IndexName, so.xtype, v.keys
FROM _IndexView v
INNER JOIN sysobjects so ON so.name = v.tablename
WHERE tablename = #tablename
PRINT 'Caching Table ' + #Tablename
OPEN #cur
FETCH NEXT FROM #cur INTO #indexname, #xtype, #keys
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT ' Index ' + #indexname
--BEGIN TRAN
IF #xtype = 'V'
SET #SQL = 'SELECT ' + #keys + ' FROM ' + #tablename + ' WITH (noexpand, INDEX (' + #indexname + '))' --
ELSE
SET #SQL = 'SELECT ' + #keys + ' FROM ' + #tablename + ' WITH (INDEX (' + #indexname + '))' --
EXEC(#SQL)
--ROLLBACK TRAN
FETCH NEXT FROM #cur INTO #indexname, #xtype, #keys
END
CLOSE #cur
DEALLOCATE #cur
END
GO
First of all, there is a setting called "Minumum Server Memory" that looks tempting. Ignore it. From MSDN:
The amount of memory acquired by the Database Engine is entirely dependent on the workload placed on the instance. A SQL Server instance that is not processing many requests may never reach min server memory.
This tells us that setting a larger minimum memory won't force or encourage any pre-caching. You may have other reasons to set this, but pre-filling the buffer pool isn't one of them.
So what can you do to pre-load data? It's easy. Just set up an agent job to do a select * from every table. You can schedule it to "Start automatically when Sql Agent Starts". In other words, what you're already doing is pretty close to the standard way to handle this.
However, I do need to suggest three changes:
Don't try to use a temporary table. Just select from the table. You don't need to do anything with the results to get Sql Server to load your buffer pool: all you need to do is the select. A temporary table could force sql server to copy the data from the buffer pool after loading... you'd end up (briefly) storing things twice.
Don't run this every 15 minutes. Just run it once at startup, and then leave it alone. Once allocated, it takes a lot to get Sql Server to release memory. It's just not needed to re-run this over and over.
Don't try to hint an index. Hints are just that: hints. Sql Server is free to ignore those hints, and it will do so for queries that have no clear use for the index. The best way to make sure the index is pre-loaded is to construct a query that obviously uses that index. One specific suggestion here is to order the results in the same order as the index. This will often help Sql Server use that index, because then it can "walk the index" to produce the results.
This is not an answer, but to supplement Joel Coehoorn's answer, you can look at the table data in the cache using this statement. Use this to determine whether all the pages are staying in the cache as you'd expect:
USE DBMaint
GO
SELECT COUNT(1) AS cached_pages_count, SUM(s.used_page_count)/COUNT(1) AS total_page_count,
name AS BaseTableName, IndexName,
IndexTypeDesc
FROM sys.dm_os_buffer_descriptors AS bd
INNER JOIN
(
SELECT s_obj.name, s_obj.index_id,
s_obj.allocation_unit_id, s_obj.OBJECT_ID,
i.name IndexName, i.type_desc IndexTypeDesc
FROM
(
SELECT OBJECT_NAME(OBJECT_ID) AS name,
index_id ,allocation_unit_id, OBJECT_ID
FROM sys.allocation_units AS au
INNER JOIN sys.partitions AS p
ON au.container_id = p.hobt_id
AND (au.type = 1 OR au.type = 3)
UNION ALL
SELECT OBJECT_NAME(OBJECT_ID) AS name,
index_id, allocation_unit_id, OBJECT_ID
FROM sys.allocation_units AS au
INNER JOIN sys.partitions AS p
ON au.container_id = p.partition_id
AND au.type = 2
) AS s_obj
LEFT JOIN sys.indexes i ON i.index_id = s_obj.index_id
AND i.OBJECT_ID = s_obj.OBJECT_ID ) AS obj
ON bd.allocation_unit_id = obj.allocation_unit_id
INNER JOIN sys.dm_db_partition_stats s ON s.index_id = obj.index_id AND s.object_id = obj.object_ID
WHERE database_id = DB_ID()
GROUP BY name, obj.index_id, IndexName, IndexTypeDesc
ORDER BY obj.name;
GO
Use this to replace function dbo._GetIndexKeys
(SELECT STRING_AGG(COL_NAME(ic.object_id,ic.column_id), ',') FROM sys.index_columns ic WHERE ic.object_id = so.id AND ic.index_id = si.indid) AS Keys,
--dbo._GetIndexKeys(so.name, si.indid) as Keys,

Repeating same SQL

I am writing a CASE statement in my SQL Server Stored Procedure. There I am repeeating a same long SQL statements every time for 11 CASEs. Should I put the SQL Statement in CASE condition in a another Stored Procedure? What could be the best approach?
CASE (SELECT ..................)
THEN 'SELCET a.field'
ELSE
''SELECT vlaues'
END
as 'Coulmn1 '
CASE (SELECT ..................)
THEN 'SELCET vaues'
ELSE
''SELECT vlaues'
END
as 'Coulmn1 '
You might want to do something like
SELECT
CASE (your select statement which retuns one value)
WHEN 'option1' THEN 'value2return1'
WHEN 'option2' THEN 'value2return2'
WHEN 'option3' THEN 'value2return3'
...
ELSE 'defaultValue'
END
Use common table expressions link.
WITH CTE (ColA)
AS
(
Select ColA From Table
)
SELECT ColA
FROM CTE
Use a CTE. In the future it would help if you gave us a slightly better example of your code however.
WITH RepeateSelect AS (SELECT ..................)
.....
.....
CASE (SELECT * FROM RepeateSelect)
THEN 'SELCET a.field'
ELSE
'SELECT vlaues'
END
as 'Coulmn1 '
CASE (SELECT * FROM RepeateSelect)
THEN 'SELCET vaues'
ELSE
''SELECT vlaues'
END
as 'Coulmn1 '
Try using the repeated query once thusly:
select A.C1, X.C2, X.C3
from TableA as A inner join
( select B.AlmondJoy,
case when B.YN > 7 then 'Yes' else 'No' end as C2,
case when C.UD in ( -1, 1 ) then 'Up' else 'Down' end as C3
from TableB as B inner join
TableC as C on C.Id = B.Id
) as X on X.AlmondJoy = A.Zagnut

Transpose table and save as View

I have a dynamic pivot/unpivot script that transposes a table. This is dynamic enough to return certain columns that I want and using dynamic columns.
What I am looking for is rather to convert this into either a UDF or a VIEW so that I can join it to other tables.
Please help.
ALTER PROC [dbo].[uspGetUserByValues]
(
#Select NVARCHAR(4000) = '*',
#Where NVARCHAR(4000) = NULL,
#OrderBy NVARCHAR(4000) = NULL
)
AS
BEGIN
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',P.' + QUOTENAME(PropertyDescription)
from System_Properties
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ', M.Email, C.Company_Name, C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles,0) SMSProfiles, U.UserID
from
(
select PropertyDescription, UP.UserID, PropertyValue
from User_Properties UP
JOIN System_Properties SP ON UP.PropertyID = SP.PropertyID
JOIN aspnet_Membership M ON UP.UserID = M.UserID
) X
pivot
(
min(PropertyValue)
for PropertyDescription in (' + REPLACE(#cols,'P.','') + ')
) P
JOIN aspnet_Membership M ON P.UserID = M.UserID
JOIN aspnet_Users U on P.UserID = U.UserID
JOIN Companies C ON C.Company_ID = P.Company_ID
LEFT JOIN (SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile GROUP BY UserID ) SMS ON SMS.UserID = P.UserID
'
SET #query = 'SELECT ' + #Select + ' FROM ('+ #query +') A'
IF ISNULL(#Where,'NULL') != 'NULL'
BEGIN
SET #query = #query + ' WHERE ' + #Where
END
IF ISNULL(#OrderBy,'NULL') != 'NULL'
BEGIN
SET #query = #query + ' ORDER BY ' + #OrderBy
END
execute(#query)
--PRINT(#query)
END
OH wow I made it.
I know this is with "known" column names but actually I didn't have to know them.
Firstly, this is the query I used to create the View. I will need to drop the view at least every I add a new Property or I can actually write a job that checks if all the properties from System_Properties are represented in the view, if not then drop the view and run this code.
CREATE PROC [dbo].[uspCreateViewUsers]
AS
BEGIN
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',P.' + QUOTENAME(PropertyDescription)
from System_Properties
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'CREATE VIEW vwUsers AS SELECT ' + #cols + ', M.Email, C.Company_Name, C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles,0) SMSProfiles, U.UserID
from
(
select PropertyDescription, UP.UserID, PropertyValue
from User_Properties UP
JOIN System_Properties SP ON UP.PropertyID = SP.PropertyID
JOIN aspnet_Membership M ON UP.UserID = M.UserID
) X
pivot
(
min(PropertyValue)
for PropertyDescription in (' + REPLACE(#cols,'P.','') + ')
) P
JOIN aspnet_Membership M ON P.UserID = M.UserID
JOIN aspnet_Users U on P.UserID = U.UserID
JOIN Companies C ON C.Company_ID = P.Company_ID
LEFT JOIN (SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile GROUP BY UserID ) SMS ON SMS.UserID = P.UserID
'
execute(#query)
END
Them the View, which can't be represented graphically by table joins looks like this:
SELECT P.[Company_ID], P.[Created_Date], P.[Created_User], P.[Cust_ID], P.[FirstName], P.[IPCheck], P.[JobTitle], P.[LastLogin], P.[LastModified_Date], P.[LastModified_User],
P.[LastName], P.[Newsletter_OptIn], P.[Password_Change], P.[SupAdmin], P.[SysAccess], P.[SysAdmin], P.[User_Cat_1], P.[User_Cat_10], P.[User_Cat_2],
P.[User_Cat_3], P.[User_Cat_4], P.[User_Cat_5], P.[User_Cat_6], P.[User_Cat_7], P.[User_Cat_8], P.[User_Cat_9], P.[UserClient_ID], M.Email, C.Company_Name,
C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles, 0) SMSProfiles, U.UserID
FROM (SELECT PropertyDescription, UP.UserID, PropertyValue
FROM User_Properties UP JOIN
System_Properties SP ON UP.PropertyID = SP.PropertyID JOIN
aspnet_Membership M ON UP.UserID = M.UserID) X PIVOT (min(PropertyValue) FOR PropertyDescription IN ([Company_ID], [Created_Date], [Created_User],
[Cust_ID], [FirstName], [IPCheck], [JobTitle], [LastLogin], [LastModified_Date], [LastModified_User], [LastName], [Newsletter_OptIn], [Password_Change], [SupAdmin],
[SysAccess], [SysAdmin], [User_Cat_1], [User_Cat_10], [User_Cat_2], [User_Cat_3], [User_Cat_4], [User_Cat_5], [User_Cat_6], [User_Cat_7], [User_Cat_8],
[User_Cat_9], [UserClient_ID])) P JOIN
aspnet_Membership M ON P.UserID = M.UserID JOIN
aspnet_Users U ON P.UserID = U.UserID JOIN
Companies C ON C.Company_ID = P.Company_ID LEFT JOIN
(SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile
GROUP BY UserID) SMS ON SMS.UserID = P.UserID
This now allows me to query the View as if it was a table.
I hope this helps someone else in the future.
Simply said: you cant
At least you can't do it using conventional TSQL programming. Which means you would have to use some hack. Let me explain.
Closest thing to your SP would be UDF. However, UDFs are rather restricted. One thing UDF expect is for data to stay the same while and after executing it. Of course that this means EXEC() is forbidden in that scope.
Another possibility would be a view. However, you have a number of parameters and view's schema depends on these parameters. Functionality to change view's schema based on input parameters doesn't exist in SQL server.
And now for the hacks.
One hack I can think of is:
create a CLR UDF
create new connection based on context connection (same server, same db)
exec your SP there
return result to your original pipe
But it may or may not work (it's a hack after all).
If the hack doesn't work, you can try playing it by the book. This means creating a CLR UDF, assemble select statement in there and execute it, which means that you will have to throw away your original SP. However, it is not a hack since SQL CLR UDF's are made for such (and other) situations. Only thing you will have to take care about is using SqlMetaData because UDF doesn't have a predefined resultset. See this.
In my previous answer I stated that it could be done using CLR UDF, but that was wrong. One thing I forgot is that Microsoft insists on providing a finite number of columns for UDF. This may not be obvious while developing in .NET - after all, you can return any number of columns to SqlPipe. See this (untested) code...
[SqlFunction(DataAccess = DataAccessKind.Read)]
public static void DynOutFunc(SqlString select, SqlString where, SqlString orderBy)
{
// 1: Create SQL query
string query = "select db_id(), db_name()";
// 2: Find out which colums and their types are part of the output
SqlMetaData[] metaData =
{
new SqlMetaData("ID", System.Data.SqlDbType.Int),
new SqlMetaData("Database", System.Data.SqlDbType.NVarChar, 256)
};
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
connection.Open();
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
using (reader)
{
while (reader.Read())
{
SqlDataRecord record = new SqlDataRecord(metaData);
SqlContext.Pipe.SendResultsStart(record);
for(int i = 0; i < metaData.Length; i++)
{
if(metaData[i].DbType == DbType.String)
record.SetString(i, reader.GetString(i));
else if(metaData[i].DbType == DbType.Int32)
record.SetInt32(i, reader.GetInt32(i));
// else if's should cover all supported data taypes
}
SqlContext.Pipe.SendResultsRow(record);
}
}
SqlContext.Pipe.SendResultsEnd();
}
}
Notice SqlMetaData collection that holds information about columns. What stops you from appending just another column to it?
But(!) when it comes to registering that function in the SQL Server itself, you HAVE to provide arguments, like:
CREATE FUNCTION DynOutFunc
#select [nvarchar](4000),
#where [nvarchar](4000),
#orderBy [nvarchar](4000)
RETURNS TABLE (p1 type1, p2 type2, ... pN typeN)
AS EXTERNAL NAME SqlClrTest.UserDefinedFunctions.DynOutFunc;
It turns out there are no hacks for this I can think of. Or there are just no hacks here at all.

Resources