Hard code SSRS multi value parameter for testing - sql-server

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.

Related

Pass parameter from Excel to SQL in PowerQuery

I want to set local variables or pass parameters from Excel to SQL. I've found similar questions, but all referred to old versions of Excel and/or the answers showed how to filter or manipulate output from a generic SQL query in the Power Query Editor, rather than pass a parameter or modify the SQL, so that the SQL Server supplies data in the needed form.
I'm building a large Microsoft Excel spreadsheet that depends on ten different SQL queries, all against a common SQL Server database. Excel and SQL Server are installed on my laptop and are current versions (as of 16 Mar 2022). All ten queries share a common date restriction, imposed in the WHERE clauses of the queries. The tables accessed and the form of output are very different, so there is no easy way to combine the ten queries into a single query. The queries contain multiple levels of aggregation (e.g. SUM(...)) so I need to restrict the records accessed prior to aggregation and passing results from the query back to Excel.
As currently written, each query begins by setting two date values in local variables. For example,
DECLARE #BEGIN_DATE AS smalldatetime;
DECLARE #END_DATE AS smalldatetime;
#BEGIN_DATE = CAST('2021-03-01 00:00' AS smalldatetime);
#END_DATE = CAST('2021-03-02 23:59' AS smalldatetime);
Every one of the ten queries includes a line in the WHERE clause similar to
WHERE
PickUpDate BETWEEN #BEGIN_DATE AND #END_DATE
Every query will use the same pair of dates. However, the column filtered (PickUpDate above) changes from one query to the next.
As it is, I have to manually edit each of the ten queries to change the two dates--twenty edits in all. This is time-consuming and error-prone. Ideally, I'd like to set the date range in the spreadsheet, in a pop-up dialog box, or any other convenient way and pass the dates to the SQL queries. Then by selecting Data > Refresh All in Excel, update all my tables at once.
Is this possible, and if so, how does one do it?
The answer from David Browne is generally on-target. But I found some difficulties reading data from an Excel table directly into the SQL, given security restrictions in the latest version of Excel/Power Query. Also, since this was the first time I worked directly in M-code and the advanced editor, it was challenging to fill-in the gaps.
I finally got a nice solution running; here is what worked for me.
First, I stored the parameter values in a two-column table. My table is named "ParameterTable" with column headers named "Parameter_Name" and "Value". The value(s) to pass to SQL Server are stored in the Value column. My table has two rows with row entries labeled "Begin_DateTime" and "End_DateTime".
Secondly I created a callable function named “ftnGetParameter.” Select Data > Get Data > From Other Sources > Blank Query. Then select “Advanced Editor.” Delete any boilerplate added by Excel, and enter and save this function
let theParameter=(TableName,ParameterLabel) =>
let
Source=Excel.CurrentWorkbook(){[Name=TableName]}[Content],
value = Source{[Parameter_Name=ParameterLabel]}[Value]
in
value
in
theParameter
Thirdly, code-up your SQL statement as usual. I was trying to pass dates to SQL, so I initially coded with string literals. Enter the query in the usual way. I used Data > Get Data > From Database > From SQL Server Database. Then pasted in the SQL. The two relevant lines in my query looked like this:
DECLARE #BEGIN_DATE AS SMALLDATETIME='2021-01-01 00:00';
DECLARE #END_DATE AS SMALLDATETIME='2021-12-31 23:59';
You could skip this step, but it allowed me to get complex SQL code entered, formatted, and running before I invoked the function to pass the parameters.
Finally, simply replace the string literals in the SQL with code to call the function. My first few lines of M-code looks like this:
let
Source = Sql.Database("DESKTOP-04P8E8C", "nfbdata",
[Query=
"
DECLARE #BEGIN_DATE AS SMALLDATETIME= '" & ftnGetParameter("ParameterTable","Begin_DateTime") & "';
DECLARE #END_DATE AS SMALLDATETIME='" & ftnGetParameter("ParameterTable","End_DateTime") & "' (… the query continues )
Excel will issue some warnings about running the query and prompt you to edit permissions. Once permission has been granted, the function reads the text from the parameter table and passes it into the SQL.
I found that the function call was not optional. Apparently, importing the code directly into a native call to SQL Server is considered an unacceptable security risk.
Many thanks to Mr. David Browne. His post definitely points in the right direction.
You can reference a table on a sheet from Power Query and integrate values from that table into your other queries. Eg if ParameterTable is a single-row table on some worksheet with a column called "StartDate", something like
let
theDate = Date.From( Record.Field(Table.First(ParameterTable),"StartDate") ),
Source = Sql.Databases("localhost"),
AdventureWorksDW2017 = Source{[Name="AdventureWorksDW2017"]}[Data],
dbo_DimDate = AdventureWorksDW2017{[Schema="dbo",Item="DimDate"]}[Data],
#"Filtered Rows" = Table.SelectRows(dbo_DimDate, each [FullDateAlternateKey] = theDate )
in
#"Filtered Rows"
for M query folding, or
let
theDate = Date.From( Record.Field(Table.First(ParameterTable),"StartDate") ),
sql = "
select *
from dimDate
where FullDateAlternateKey = '" & Text.From(theDate) & "'
",
Source = Sql.Database("localhost", "adventureworksdw2017", [Query=sql])
in
Source
for dynamic SQL.

Is there a way to extract individual values from a varchar column using SQL Server 2016?

I am trying to extract individual dates from a varchar column in a SQL Server 2016 tablet that are stored comma separated and am not sure how to proceed. The data is setup like this:
article Consolidation_Order_Cut_Off_Final_Allocation
------------------ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
011040 01/13/2021,03/10/2021
019099 01/13/2021,01/27/2021,02/24/2021,03/24/2021,04/28/2021,05/26/2021,06/23/2021,07/28/2021
019310 01/27/2021,02/03/2021,03/10/2021,04/14/2021,05/12/2021,06/09/2021,07/14/2021,08/11/2021
059611 01/13/2021
Ideally - I would have each date split out into a new row. I have seen a few similar questions that use very complex functions but those seem to be for SQL Server 2008. I have also found the new function STRING_SPLIT but that would seem to be table valued and thus have to come in the FROM. One thought I had was to declare a variable to hold this column and then use something like select * FROM string_split(#dates,','); however since there is more than one value in that list that returns an error. I am very new to the 2016 version of SQL Server and curious if anyone has ran into a way to solve this.
String_Split() is a table valued function, so you can call it with a CROSS APPLY
Example or dbFiddle
Select A.article
,B.Value
From YourTable A
Cross Apply string_split(Consolidation_Order_Cut_Off_Final_Allocation,',') B

SQL Server - How do i get multiple rows of data into a returned variable

First question here so hoping that someone can help!
Im doing a lot of conversions of Access backends on to SQL server, keeping the front end in Access.
I have come across something that i need a little help with.
In Access, I have a query that is using a user-defined function in order to amalgamate some data from rows in a table into one variable. (By opening a recordset and enumerating through, adding to a variable each time.)
For example:
The query has a field that calls the function like this:
ProductNames: Product(ContractID)
And the VBA function "Product()" searches a table based on the ContractID. Cycles through each row it finds and concatenates the results of one field into one variable, ultimately returned to the query.
Obviously, moving this query to SQL server as a view means that that function will not be found as its in Access.
Can I use a function or stored procedure in order to do the same thing? (I have never used them before)
I must stress that I cannot create, alter or drop tables at run-time due to very strict production environment security.
If someone could give me an example id be really grateful.
So i need to be able to call it from the view as shown above.
Let say the table im looking at for the data is called tbl_Products and it has 2 columns:
| ContractID | Product |
How would that be done?! any help massively appreciated!
Andy
Yes you can most certainly do the same thing and adopt the same approach in SQL like you did in the past with VBA + SQL.
The easy solution would be to link to the view, and then build a local query that adds the additional column. However, often for reasons of performance and simply converting sql from Access to T-SQL, then I often “duplicate” those VBA functions as T-SQL functions.
The beauty of this approach is once you make this function, then this goes a “long” way towards easy converting some of your Access SQL to t-sql and views.
I had a GST calculation function in VBA that you would pass the amount, and a date (because the gst rate changes at a known date (in the past, or future).
So I used this function all over the place in my Access SQL.
When I had to convert to sql server, then I was able to use “views” and pass-though quires from Access and simply use “very” similar sql and include that sql function in the sql just like I did in Access.
You need to create what is called a SQL function. This function is often called a scaler function. This function works just like a function in VBA.
So in t-sql store procedure, or even as a expression in your SQL just like in Access!!!!
In your example, lets assume that you have some contract table, and you want to grab the “status” column (we assume text).
And there could be one, 1 or “several” or none!.
So we will concatenate each of the child records “status” code based on contract id.
You can thus fire up SSMS and in the database simply expand your database in the tree view. Now expand “programmability”. Now expand functions. You see “scaler-valued functions”. These functions are just like VBA functions. Once created, you can use the function in code (t-sql) or in views etc.
At this point, you can now write t-sql code in place of VBA code.
And really, you don’t have to “expand” the tree above – but it will allow you to “find” and “see” and “change” your functions you create. Once created then ANY sql, or code for that database can use the function as a expression just like you did in Access.
This code should do the trick:
CREATE FUNCTION [dbo].[ContractStatus]
(#ContractID int)
RETURNS varchar(255)
AS
BEGIN
-- Declare a cursor (recordset)
DECLARE #tmpStatus varchar(25)
DECLARE #MyResult varchar(255)
set #MyResult = ''
DECLARE rst CURSOR
FOR select Status from tblContracts where ID = #ContractID
OPEN rst
FETCH NEXT FROM rst INTO #tmpStatus
WHILE ##FETCH_STATUS = 0
BEGIN
IF #MyResult <> ''
SET #MyResult = #MyResult + ','
SET #MyResult = #MyResult + #tmpStatus
FETCH NEXT FROM rst INTO #tmpStatus
END
-- Return the result of the function
RETURN #MyResult
END
Now, in sql, you can go:
Select ProjectName, ID, dbo.ProjectStatus([ID]) as MyStatus from tblProjects.

Create SQL user-defined function in ColdFusion with MS SQL Server

I'm doing queries in which I want to extract the left-most n characters from a string that has been stripped of all leading and following spaces. An example is:
Select SUBSTRING(LTRIM(RTRIM(somefield)), 0, #n) AS mydata
FROM sometable
It's the only way I can figure to do it on a SQL Server.
I've never written a UDF before, but I think if I was just working on a SQL Server, I could create a user-defined function such as:
CREATE FUNCTION udfLeftTrimmed
(
#inputString nvarchar(50),
#n int
)
RETURNS nvarchar(#n)
AS
BEGIN
RETURN SUBSTRING(LTRIM(RTRIM(#inputString)), 0, #n);
END
I could then do something like:
Select udfLeftTrimmed(somefield,6) AS mydata
FROM sometable
which is at least a little easier to read and understand.
The question is, how do I create the UDF in ColdFusion? All my searches for SQL user-defined function in ColdFusion just gave me how to create ColdFusion functions.
Since there is nothing special or "dynamic" about your UDF you really don't need to create it in CF. You should just create it using MSSQL Manager. UDFs in SQL are like stored procedures. Once created they are a part of the DB/Schema. so create once, use as many times as you like (as #leigh has mentioned).
Keep in mind that using a SQL udf in SQL usually requires the user prepend as in:
<cfquery...>
Select dbo.udfLeftTrimmed(somefield,6) AS mydata
FROM sometable
</cfquery>
Note the "dbo.udf..." that dbo is important and may be why your subsequent try is failing - besides getting a duplicate UDF error by now. :)
NOTE:
To follow up on your comments and Leighs, you can create your UDF in a DB accessible to your user then access it as dbo.dbname.function ... as inthe following code:
<cfquery...>
Select dbo.myspecialDatabase.udfLeftTrimmed(somefield,6) AS mydata
FROM sometable
</cfquery>
Then you need only create it one time.

Using SQL Server 2008 Geography types with nHibernate's CreateSQLQuery

I am trying to issue a SQL update statement with nHibernate (2.0.1GA) like this:
sqlstring = string.Format("set nocount on;update myusers set geo=geography::Point({0}, {1}, 4326) where userid={2};", mlat, mlong, userid);
_session.CreateSQLQuery(sqlstring).ExecuteUpdate();
However I receive the following error: 'geography#p0' is not a recognized built-in function name.
I thought CreateSQLQuery would just pass the SQL I gave it and execute it...guess not. Any ideas on how I can do that within the context of nHibernate?
I'm pretty sure I can tell you what is happening, but I don't know if there is a fix for it.
I think the problem is that the ':' character is used by NHibernate to create a named parameter. Your expression is getting changed to:
set nocount on;update myusers set geo=geography#p0({0}, {1}, 4326) where userid={2};
And #p0 is going to be a SQL variable. Unfortunately I can't find any documentation for escaping colons so they are not treated as a named parameter.
If an escape character exists (my quick skim of the NHibernate source didn't find one; Named parameters are handled in NHibernate.Engine.Query.ParameterParser if you want to spend a little more time searching), then you could use that.
Other solutions:
Add an escape character to the source. You can then use a modified version of NHibernate. If you do this, you should submit your patch to the team so it can be included in the real thing and you don't have to maintain a modified version of the source (no fun).
Create a user defined function in your DB that returns a geography::Point, then call your function instead of the standard SQL function. This seems like the quickest/easiest way to get up and running, but also feels a bit like a band-aid.
See if there is something in NHibernate Spatial that will let you programmatically add the geography::Point() [or edit the code for that project to add one and submit the patch to that team].
"{whatever} is not a recognized built-in function name" is a SQL Server error message, not sure what Hibernate is doing there but SQL Server is the one complaining about it.
There is an implicit conversion from varchar to Point.
Use NHibernate to set the geographic parameters to their string representation
Define a SQL query template with named paramter loc:
const string Query = #"SELECT {location.*}
FROM {location}
WHERE {location}.STDistance(:loc) is not null
ORDER BY {location}.STDistance(:loc)";
Set the parameter to a string representation of Point:
return session
.CreateSQLQuery(Query)
.AddEntity("location", typeof (Location))
.SetString("loc", "Point (53.39006999999999 -3.0084007)")
.SetMaxResults(1)
.UniqueResult<Location>();
This is for a Select. but I see no reason why it wouldn't work for an Insert or Update.
Following on #Chris's answer, here is a copy and paste solution:
CREATE FUNCTION GetPoint
(
#lat float,
#lng float,
#srid int
)
RETURNS geography
AS
BEGIN
declare #point geography = geography::Point(#lat, #lng, #srid);
RETURN #point
END
GO
The you do
dbo.GetPoint(#Latitude, #Longitude, 4326)
instead of
geography::Point(#Latitude, #Longitude, 4326);
And NH is happy

Resources