Using SQL Server 2016 and latest SSMS, I'm trying to take a csv string and split it and then insert into table-value parameter variable and I'm getting error squiggles under the string_split function.
Is what I'm doing even possible?
DECLARE #tvParam TABLE (FirstName nvarchar(50))
DECLARE #s VARCHAR(MAX) = '0152,1731'
INSERT INTO #tvParam
SELECT Value
FROM STRING_SPLIT(#s, ',')
Not sure what build of SSMS you're using but that is just IntelliSense being not so intelligent. In anticipation of these changes to that function, IntelliSense was updated to expect 3 parameters unconditionally. It doesn't understand (a) that the 3rd parameter is optional, and (b) where it's even supported. Current/future builds of SSMS should get better at this over time.
Here is SSMS 18.11.1:
Thankfully, you can ignore the message, which is just a parse-time warning. The code runs successfully.
Related
I have an unusual situation - first time!
I'm trying to write a pre-deployment script for an SSDT Project in Visual Studio, which will prepare a load of data for migration. There's a user-defined table-valued function (TF) in the new version of the database called [MySchema].[MyFunc] that will really help with the migration script... but it doesn't exist yet in the old database that will be upgraded and migrated. There are many other objects in the new version, but I just need to use this one to help with the migration. The function doesn't have any dependencies on any other new (or existing) objects, it's totally self-contained.
I'm hoping to compile [MySchema].[MyFunc] as part of the pre-deployment, so that I can use it. The function lives in .\MySchema\Functions\MyFunc.sql
I've attempted the following...
ATTEMPT 1
This fails with Incorrect syntax near CREATE (Note: CREATE is the first line of file .\MySchema\Functions\MyFunc.sql):
IF object_id('[MySchema].[MyFunc]', 'TF') IS NULL
BEGIN
:r .\MySchema\Functions\MyFunc.sql
END
ATTEMPT 2
This fails with Incorrect syntax near 'GO' and Incorrect syntax near ':' Expecting CONVERSATION:
GO
IF object_id('[MySchema].[MyFunc]', 'TF') IS NULL
BEGIN
:r .\MySchema\Functions\MyFunc.sql
GO
END
ATTEMPT 3
Copy and paste the entire CREATE FUNCTION statement into my pre-deployment script:
CREATE FUNCTION [MySchema].[MyFunc]
(
#p1 VARCHAR(255)
)
RETURNS #returntable TABLE
(
some_col NVARCHAR(MAX)
)
AS
BEGIN
-- some function code here
RETURN
END
But this fails with CREATE FUNCTION must be the only statement in the batch. I've tried inserting GO before & after this, but I get similar results to ATTEMPT 2. I've also tried with ; instead of GO.
ATTEMPT 4
I tried to use an iTVF, but it didn't help
ATTEMPT 5
I considered, for a few brief moments, taking the code out of my function and just using it without a function... but I need to use that code around 20 times in the migration script. So I dismissed this option. It will produce a different result every time (due to changing parameters), so I can't just put it in a CTE or similar and re-use it each time.
PLEASE NOTE
Answers should be SSDT project specific. This isn't code being run in SSMS. It's in Visual Studio and will be run as part of a Publish process. If you're not sure what SSDT or a pre-deployment script is, please don't answer :-) Any answers not based on SSDT are irrelevant to this scenario. Thanks!
If you're targeting SQL Server 2016 or later two possible solutions come to mind:
in a seperate batch before including your function code try:
DROP FUNCTION IF EXISTS [MySchema].[MyFunc];
revise the content of the MyFunc.sql file such that it starts with:
CREATE OR ALTER FUNCTION [MySchema].[MyFunc]
...
References:
DROP FUNCTION (Transact-SQL)
CREATE FUNCTION (Transact-SQL)
You can't have GO inside BEGIN/END. I don't see why Attempt 3 doesn't work, most probably because of other code you have in the same script. Normally you can create objects in the pre-script. Depending on the version of SQL Server you can either use IF EXISTS or change the code to DROP the function if it exists and then always CREATE it instead of the opposite approach you are trying to do.
Another way is to simply put data population logic to the pre-script itself. You can do something like:
IF NOT EXISTS (SELECT * FROM SomeTable)
BEGIN
INSERT INTO SomeTable ...
END
In such case you'll be able to re-run this script without duplicating the data.
I'm trying to test a SQL query in SQL Server Management Studio that normally requires a multivalue parameter from the SSRS report its a part of.
I'm not sure to how hard code a multi value parameter in management studio. The report was created by a vendor, I'm just trying to make it runnable for testing outside of SSRS.
For example the parameter in SSRS is a collection of numbers that the user selects - ie "3100, 3102, 3105" would be the selections for the multivalue parameter called #object_code
I've got something like this - but it's not working.
Declare #Object_Code varchar(100)
Set #object_Code = ('3100','3102','3105')
....really long vendor written query I don't thoroughly understand...
IN(#object_code)
You have to use String-Split function to separate comma separated values.
For example-
Declare #Object_Code varchar(100)
Set #Object_Code = '3100,3102,3105'
....really long vendor written query I dont thoroughly understand...
--T.object_code IN (#object_code)
Inner Join dbo.Split(#Object_Code, ',') as S On S.data = T.object_code
Search your database first for any string-split function.
If you want to create string-split function then follow this -
T-SQL split string
If you use SQL Server 2016 you might want to check out the function STRING_SPLIT.
If you use a lower version of SQL Server and you can't or don't want to create a separate function, the following could be an alternative:
declare #object_code varchar(100);
set #object_code = '3100,3102,3105';
select
ltrim(rtrim(x.par.value('.[1]','varchar(max)'))) as object_code
from (
select convert(xml,'<params><param>' + replace(#object_code,',', '</param><param>') + '</param></params>') as c
) tbl
cross apply
c.nodes('/params/param') x(par);
Everybody seems to be getting hung up on splitting a string that doesn't have to be a string. We're just trouble shooting a query here and need a way to feed it values. It's not important how SSRS does it, just that we can reproduce the result.
Declare #Object_Code table (params varchar(20));
INSERT #object_Code
VALUES ('3100'),('3102'),('3105')
....really long vendor written query I don't thoroughly understand...
IN (SELECT params FROM #object_code)
Then spend some quality time getting to know the query.
SQL Server has Deferred Name Resolution feature, read here for details:
https://msdn.microsoft.com/en-us/library/ms190686(v=sql.105).aspx
In that page, all it's talking is stored procedure so it seems Deferred Name Resolution only works for stored procedures and not for functions and I did some testing.
create or alter function f2(#i int)
returns table
as
return (select fff from xxx)
go
Note the table xxx does not exist. When I execute the above CREATE statement, I got the following message:
Msg 208, Level 16, State 1, Procedure f2, Line 4 [Batch Start Line 22]
Invalid object name 'xxx'.
It seems that SQL Server instantly found the non-existent table xxx and it proved Deferred Name Resolution doesn't work for functions. However when I slightly change it as follows:
create or alter function f1(#i int)
returns int
as
begin
declare #x int;
select #x = fff from xxx;
return #x
end
go
I can successfully execute it:
Commands completed successfully.
When executing the following statement:
select dbo.f1(3)
I got this error:
Msg 208, Level 16, State 1, Line 34
Invalid object name 'xxx'.
So here it seems the resolution of the table xxx was deferred. The most important differences between these two cases is the return type. However I can't explain when Deferred Name Resolution will work for functions and when not. Can anyone help me to understand this? Thanks in advance.
It feels like you were looking for understanding of why your particular example didn't work. Quassnoi's answer was correct but didn't offer a reason so I went searching and found this MSDN Social answer by Erland Sommarskog. The interesting part:
However, it does not extend to views and inline-table functions. For
stored procedures and scalar functions, all SQL Server stores in the
database is the text of the module. But for views and inline-table
functions (which are parameterised view by another name) SQL Server
stores metadata about the columns etc. And that is not possible if the
table is missing.
Hope that helps with understanding why :-)
EDIT:
I did take some time to confirm Quassnoi's comment that sys.columns as well as several other tables did contain some metadata about the inline function so I am unsure if there is other metadata not written. However I thought I would add a few other notes I was able to find that may help explain in conjunction.
First a quote from Wayne Sheffield's blog:
In the MTVF, you see only an operation called “Table Valued Function”. Everything that it is doing is essentially a black box – something is happening, and data gets returned. For MTVFs, SQL can’t “see” what it is that the MTVF is doing since it is being run in a separate context. What this means is that SQL has to run the MTVF as it is written, without being able to make any optimizations in the query plan to optimize it.
Then from the SQL Server 2016 Exam 70-761 by Itzik Ben-Gan (Skill 3.1):
The reason that it's called an inline function is because SQL Server inlines, or expands, the inner query definition, and constructs an internal query directly against the underlying tables.
So it seems the inline function essentially returns a query and is able to optimize it with the outer query, not allowing the black-box approach and thus not allowing deferred name resolution.
What you have in your first example is an inline function (it does not have BEGIN/END).
Inline functions can only be table-valued.
If you used a multi-statement table-valued function for you first example, like this:
CREATE OR ALTER FUNCTION
fn_test(#a INT)
RETURNS #ret TABLE
(
a INT
)
AS
BEGIN
INSERT
INTO #ret
SELECT a
FROM xxx
RETURN
END
, it would compile alright and fail at runtime (if xxx would not exist), same as a stored procedure or a scalar UDF would.
So yes, DNR does work for all multi-statement functions (those with BEGIN/END), regardless of their return type.
So I have a stored procedure in SQL Server. I've simplified its code (for this question) to just this:
CREATE PROCEDURE dbo.DimensionLookup as
BEGIN
select DimensionID, DimensionField from DimensionTable
inner join Reference on Reference.ID = DimensionTable.ReferenceID
END
In SSIS on SQL Server 2012, I have a Lookup component with the following source command:
EXECUTE dbo.DimensionLookup WITH RESULT SETS (
(DimensionID int, DimensionField nvarchar(700) )
)
When I run this procedure in Preview mode in BIDS, it returns the two columns correctly. When I run the package in BIDS, it runs correctly.
But when I deploy it out to the SSIS catalog (the same server the database is on), point it to the same data sources, etc. - it fails with the message:
EXECUTE statement failed because its WITH RESULT SETS clause specified 2 column(s) for result set number 1, but the statement sent
3 column(s) at run time.
Steps Tried So Far:
Adding a third column to the result set - I get a different error, VS_NEEDSNEWMETADATA - which makes sense, kind of proof there's no third column.
SQL Profiler - I see this:
exec sp_prepare #p1 output,NULL,N'EXECUTE dbo.DimensionLookup WITH RESULT SETS ((
DimensionID int, DimensionField nvarchar(700)))',1
SET FMTONLY ON exec sp_execute 1 SET FMTONLY OFF
So it's trying to use FMTONLY to get the result set data ... needless to say, running SET FMTONLY ON and then running the command in SSMS myself yields .. just the two columns.
SET NOTCOUNT ON - Nothing changed.
So, two other interesting things:
I deployed it out to my local SQL 2012 install and it worked fine, same connections, etc. So it may be a server / database configuration. Not sure what if anything it is, I didn't install the dev server and my own install was pretty much click through vanilla.
Perhaps the most interesting thing. If I remove the join from the procedure's statement so it just becomes
select DimensionID, DimensionField from DimensionTable
It goes back to just sending 2 columns in the result set! So adding a join, without adding any additional output columns, ups the result set to 3 columns. Even if I add 6 more joins, just 3 columns. So one guess is its some sort of metadata column that only gets activated when there's a join.
Anyway, as you can imagine, it's driving me kind of mad. I have a workaround to load the data into a temp table and just return that, but why won't this work? What extra column is being sent back? Why only when I add a join?
Gah!
So all credit to billinkc: The reason is because of a patch.
In Version 11.0.2100.60, SSIS Lookup SQL command metadata is gathered using the old SET FMTONLY method. Unfortunately, this doesn't work in 2012, as the Books Online entry on SET FMTONLY helpfully notes:
Do not use this feature. This feature has been replaced by sp_describe_first_result_set.
Too bad they didn't follow their own advice!
This has been patched as of version 11.0.2218.0. Metadata is correctly gathered using the sp_describe_first_result_set system stored procedure.
This can happen if the specified WITH results set in SSIS identifies that there are more columns than being returned by the stored proc being called. Check your stored proc and ensure that you have the correct number of output columns as the WITH results set.
So here is a bit of a strange one...
I have a stored proc that takes 40 seconds to run.
I copy the contents of the stored proc to a new query window, change the 2 input parameters (which are both dates) so they are declared and set, and run the query. It is basically instant, sub 1 second execution.
The only difference is the stored proc takes the 2 dates as parameters.
Anyone got any idea what can make that happen?
(I am running SQL Server 2005)
Recompilation sniffs the parameters, so it can make no difference.
This article explains my statement...
...parameter values are sniffed during
compilation or recompilation...
You need to mask the parameters:
ALTER PROCEDURE [uspFoo]
#Date1 datetime,
#Date2 datetime
AS
BEGIN
DECLARE #IDate1 datetime, #IDate2 datetime;
SELECT #IDate1 = #Date1, #IDate2 = #Date2;
-- Stuff here that depends on #IDate1 and #IDate2
END
As gbn says, this is a parameter sniffing issue. An alternative way to his suggestion is to include the following line at the end of your query:
OPTION (RECOMPILE)
Run the profiler and capture the query plans for the execution. Check to see what the differences are - you may be able to tune the query or force a particular plan.
I had a similar issue and found it was caused because I was setting up the parameter as a DATE but the column it was being compared against in the WHERE clause was in DATETIME so it was like SSMS was converting the data type for every row it was comparing. Changing the parameter data type to DATETIME or masking the parameter as mentioned above resolves the issue.
had a same issue, I tried to resolve Parameter sniffing and also used recompile options but didn't work for me.. then came to know that adding ASSUME_MIN_SELECTIVITY_FOR_FILTER_ESTIMATES in the update query can resolve my problem..