Using Declare in SQL Server view - sql-server

I have a query like this:
DECLARE #year_start INT
DECLARE #year_end INT
SET #year_start = 2005
SET #year_end = 2014
; WITH p_year AS
(
SELECT p_year = #year_start
UNION ALL
SELECT p_year = p_year + 1
FROM p_year
WHERE p_year < #year_end
),
Interval AS
(---
),
CTE AS
(---
),
CTE_1 AS
(---
)
SELECT something
FROM CTE_1
WHERE Rank <= 3
ORDER BY something
I tried it using creating table valued function but can't get that how to manipulate with variables in the table valued function declaration.
Whereas I tried creating table valued function as:
CREATE FUNCTION P_Count()
RETURNS TABLE
AS
DECLARE ...
...
I want to make it a view but Declare statement not allows me. How can I make it a view?

your create function script misses a BEGIN:
CREATE FUNCTION P_Count()
RETURNS #tableName TABLE (structure here)
AS
BEGIN
DECLARE...
...
RETURN;
END;
here is the syntax reference on msdn

Maliks, I suppose, some were confused with your SQL. It was not immediately apparent that you use a recursive CTE to generate years in a range. There are lots of awesome ways to generate a sequence of integers (or numbers, in general) that can be combined with a CTE to emulate variables in a view. If numbers are generated way too often, consider building a separate (and indexed) table for them.
Here is what I think you were looking for. I omit the CTEs, rank and grouping you have not defined in your question. You can modify it as you wish:
WITH params AS (SELECT BegYr = 2005, EndYr=2014),
nums AS (SELECT DISTINCT n = number FROM master..spt_values WHERE number>=0)
SELECT Yr=BegYr + n FROM nums, params
WHERE n <= EndYr - BegYr
More generally, here is a sample view query that uses CTE to nicely emulate internal variable construction. You can test-run it in your version of SQL Server.
CREATE VIEW vwImportant_Users AS
WITH params AS (
SELECT
varType='%Admin%',
varMinStatus=1)
SELECT status, name
FROM sys.sysusers, params
WHERE status > varMinStatus OR name LIKE varType
SELECT * FROM vwImportant_Users
yielding output:
status name
12 dbo
0 db_accessadmin
0 db_securityadmin
0 db_ddladmin
also via JOIN
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers INNER JOIN params ON 1=1
WHERE status > varMinStatus OR name LIKE varType
also via CROSS APPLY
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers CROSS APPLY params
WHERE status > varMinStatus OR name LIKE varType

Related

Return specific number of rows in result set from Stored Procedure

When we make a stored procedure call we pass input parameter of how many rows we want to get from result. Also, we want specific columns returned which is obtained through join operation on tables.
My doubt is can we return the result as table but if in that approach how to limit result rows to specific count which is passed as input parameter.
I also searched and found about using Fetch next rows only but can we use that without offset logic.
Can somebody suggest me if there is any better approach than above mentioned?
Here is an example of how you could use TOP.
create or alter procedure TopTest
(
#RowCount int
) as
select top (#RowCount) *
from sys.columns c
order by c.name
And here is how you could do this using OFFSET/FETCH
create or alter procedure TopTestOffset
(
#RowCount int
) as
select *
from sys.columns c
order by c.name
offset 0 rows
fetch first (#RowCount) rows only
TOP and OFFSET are easier to use if you need to get first n rows. If you need a range of rows (i.e. for paging), you can use CTE
with vw as (
SELECT ROW_NUMBER() OVER (ORDER BY column1) AS RowNumber,
columnlist
from YourTable
) select * from vw
where RowNumber between 1 and #NumberOfRows

Scalar function with WHILE loop to inline function

Hi I have a view which is used in lots of search queries in my application.
The issue is application queries which use this view is is running very slow.I am investigating this and i found out a particular portion on the view definition which is making it slow.
create view Demoview AS
Select
p.Id as Id,
----------,
STUFF((SELECT ',' + [dbo].[OnlyAlphaNum](colDesc)
FROM dbo.ContactInfoDetails cd
WHERE pp.FormId = f.Id AND ppc.PageId = pp.Id
FOR XML PATH('')), 1, 1, '') AS PhoneNumber,
p.FirstName as Fname,
From
---
This is one of the column in the view.
The scalar function [OnlyAlphaNum] is making it slow,as it stops parallel execution of the query.
The function is as below;
CREATE FUNCTION [dbo].[OnlyAlphaNum]
(
#String VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
WITH SCHEMABINDING
AS
BEGIN
WHILE PATINDEX('%[^A-Z0-9]%', #String) > 0
SET #String = STUFF(#String, PATINDEX('%[^A-Z0-9]%', #String), 1, '')
RETURN #String
END
How can i convert it into an inline function.?
I tried with CASE ,but not successful.I have read that CTE is a good option.
Any idea how to tackle this problem.?
I already did this; you can read more about it here.
The function:
CREATE FUNCTION dbo.alphaNumericOnly8K(#pString varchar(8000))
RETURNS TABLE WITH SCHEMABINDING AS RETURN
/****************************************************************************************
Purpose:
Given a varchar(8000) string or smaller, this function strips all but the alphanumeric
characters that exist in #pString.
Compatibility:
SQL Server 2008+, Azure SQL Database, Azure SQL Data Warehouse & Parallel Data Warehouse
Parameters:
#pString = varchar(8000); Input string to be cleaned
Returns:
AlphaNumericOnly - varchar(8000)
Syntax:
--===== Autonomous
SELECT ca.AlphaNumericOnly
FROM dbo.AlphaNumericOnly(#pString) ca;
--===== CROSS APPLY example
SELECT ca.AlphaNumericOnly
FROM dbo.SomeTable st
CROSS APPLY dbo.AlphaNumericOnly(st.SomeVarcharCol) ca;
Programmer's Notes:
1. Based on Jeff Moden/Eirikur Eiriksson's DigitsOnlyEE function. For more details see:
http://www.sqlservercentral.com/Forums/Topic1585850-391-2.aspx#bm1629360
2. This is an iTVF (Inline Table Valued Function) that performs the same task as a
scalar user defined function (UDF) accept that it requires the APPLY table operator.
Note the usage examples below and see this article for more details:
http://www.sqlservercentral.com/articles/T-SQL/91724/
The function will be slightly more complicated to use than a scalar UDF but will yeild
much better performance. For example - unlike a scalar UDF, this function does not
restrict the query optimizer's ability generate a parallel query plan. Initial testing
showed that the function generally gets a
3. AlphaNumericOnly runs 2-4 times faster when using make_parallel() (provided that you
have two or more logical CPU's and MAXDOP is not set to 1 on your SQL Instance).
4. This is an iTVF (Inline Table Valued Function) that will be used as an iSF (Inline
Scalar Function) in that it returns a single value in the returned table and should
normally be used in the FROM clause as with any other iTVF.
5. CHECKSUM returns an INT and will return the exact number given if given an INT to
begin with. It's also faster than a CAST or CONVERT and is used as a performance
enhancer by changing the bigint of ROW_NUMBER() to a more appropriately sized INT.
6. Another performance enhancement is using a WHERE clause calculation to prevent
the relatively expensive XML PATH concatentation of empty strings normally
determined by a CASE statement in the XML "loop".
7. Note that AlphaNumericOnly returns an nvarchar(max) value. If you are returning small
numbers consider casting or converting yout values to a numeric data type if you are
inserting the return value into a new table or using it for joins or comparison
purposes.
8. AlphaNumericOnly is deterministic; for more about deterministic and nondeterministic
functions see https://msdn.microsoft.com/en-us/library/ms178091.aspx
Usage Examples:
--===== 1. Basic use against a literal
SELECT ao.AlphaNumericOnly
FROM samd.alphaNumericOnly8K('xxx123abc999!!!') ao;
--===== 2. Against a table
DECLARE #sampleTxt TABLE (txtID int identity, txt varchar(100));
INSERT #sampleTxt(txt) VALUES ('!!!A555A!!!'),(NULL),('AAA.999');
SELECT txtID, OldTxt = txt, AlphaNumericOnly
FROM #sampleTxt st
CROSS APPLY samd.alphaNumericOnly8K(st.txt);
---------------------------------------------------------------------------------------
Revision History:
Rev 00 - 20150526 - Inital Creation - Alan Burstein
Rev 00 - 20150526 - 3rd line in WHERE clause to correct something that was missed
- Eirikur Eiriksson
Rev 01 - 20180624 - ADDED ORDER BY N; now performing CHECKSUM conversion to INT inside
the final cte (digitsonly) so that ORDER BY N does not get sorted.
****************************************************************************************/
WITH
E1(N) AS
(
SELECT N
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))x(N)
),
iTally(N) AS
(
SELECT TOP (LEN(ISNULL(#pString,CHAR(32)))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM E1 a CROSS JOIN E1 b CROSS JOIN E1 c CROSS JOIN E1 d
)
SELECT AlphaNumericOnly =
(
SELECT SUBSTRING(#pString,CHECKSUM(N),1)
FROM iTally
WHERE
((ASCII(SUBSTRING(#pString,CHECKSUM(N),1)) - 48) & 0x7FFF) < 10
OR ((ASCII(SUBSTRING(#pString,CHECKSUM(N),1)) - 65) & 0x7FFF) < 26
OR ((ASCII(SUBSTRING(#pString,CHECKSUM(N),1)) - 97) & 0x7FFF) < 26
ORDER BY N
FOR XML PATH('')
);
Note the examples in the code comments:
--===== 1. Basic use against a literal
SELECT ao.AlphaNumericOnly
FROM samd.alphaNumericOnly8K('xxx123abc999!!!') ao;
--===== 2. Against a table
DECLARE #sampleTxt TABLE (txtID int identity, txt varchar(100));
INSERT #sampleTxt(txt) VALUES ('!!!A555A!!!'),(NULL),('AAA.999');
SELECT txtID, OldTxt = txt, AlphaNumericOnly
FROM #sampleTxt st
CROSS APPLY samd.alphaNumericOnly8K(st.txt);
Returns:
AlphaNumericOnly
-------------------
xxx123abc999
txtID OldTxt AlphaNumericOnly
----------- ------------- -----------------
1 !!!A555A!!! A555A
2 NULL NULL
3 AAA.999 AAA999
It's the fastest of it's kind. It runs extra fast with a parallel execution plan. To force a parallel execution plan, grab a copy of make_parallel by Adam Machanic. Then you would run it like this:
--===== 1. Basic use against a literal
SELECT ao.AlphaNumericOnly
FROM dbo.alphaNumericOnly8K('xxx123abc999!!!') ao
CROSS APPLY dbo.make_parallel();
--===== 2. Against a table
DECLARE #sampleTxt TABLE (txtID int identity, txt varchar(100));
INSERT #sampleTxt(txt) VALUES ('!!!A555A!!!'),(NULL),('AAA.999');
SELECT txtID, OldTxt = txt, AlphaNumericOnly
FROM #sampleTxt st
CROSS APPLY dbo.alphaNumericOnly8K(st.txt)
CROSS APPLY dbo.make_parallel();
Surely there is scope to improve this. test it out.
;WITH CTE AS (
SELECT (CASE WHEN PATINDEX('%[^A-Z0-9]%', D.Name) > 0
THEN STUFF(D.Name, PATINDEX('%[^A-Z0-9]%', D.Name), 1, '')
ELSE D.NAME
END ) NameString
FROM #dept D
UNION ALL
SELECT STUFF(C.NameString, PATINDEX('%[^A-Z0-9]%', C.NameString), 1, '')
FROM CTE C
WHERE PATINDEX('%[^A-Z0-9]%', C.NameString) > 0
)
Select STUFF((SELECT ',' + E.NameString from CTE E
WHERE PATINDEX('%[^A-Z0-9]%', E.NameString) = 0
FOR XML PATH('')), 1, 1, '') AS NAME

Calling User-Defined Function in select statement returning xml data

I created a user-defined function in SQL Server 2012 that returns XML. I would like to call the function in a SELECT statement. Is this possible?
When I try doing it, I get the error:
The FOR XML clause is not allowed in a ASSIGNMENT statement.
I want the SELECT statement to return a set of these named methods that have dependencies of other named methods within their logic.
In the main CTE, I get the latest versions of methods that have dependencies. The UDF goes thru the logic of each method and returns any methods called within it. So, I want to call the UDF in the SELECT statement and return XML of the dependent method names.
The function works and returns XML data. This is the function:
ALTER FUNCTION [dbo].[GetCalledMLMs]
(
-- Add the parameters for the function here
#MLM_Txt nvarchar(MAX)
)
RETURNS XML
AS
BEGIN
-- Declare the return variable here
DECLARE #CalledMLMs XML
Declare #MLMTbl table (pos int, endpos int, CalledMLM nvarchar(200))
--Logic to get the data...
Select #CalledMLMs = CalledMLM from #MLMTbl FOR XML PATH
-- Return the result of the function
RETURN #CalledMLMs
END
This is the CTE that calls the UDF.
;with cte as
(
select distinct Name, max(ID) as LatestVersion
from MLM_T
where Logic like '%:= MLM %' and Logic not like '%standard_libs := mlm%'
group by Name
)
select MLM2.Name, LatestVersion,
dbo.GetCalledMLMs(MLM2.Logic) as CalledMLMs
from cte join MLM_T MLM2 on cte.Name = MLM2.Name
and cte.LatestVersion = MLM2.ID
and MLM2.Active = 1 and MLM2.Status in (3, 4)
When running this query I get the error that XML is not allowed to be used in assignment statement.
Is there any way to call a function in the SELECT statment that returns an XML data type?
If you want to set a variable to a value you have to use SET and a scalar value on the right side.
The syntax SELECT #SomeVariable=SomeColumn FROM SomeTable is not possible with FOR XML (and rather dangerous anyway...), because the XML is not a column of the SELECT but something after the process of selecting.
Your problem is situated here:
Select #CalledMLMs = CalledMLM from #MLMTbl FOR XML PATH
Try to change this to
SET #CalledMLMs = (SELECT CalledMLM FROM #MLMTbl FRO XML PATH);
I solved the problem by changing the function to return a table, not XML.
So it looks like this:
FUNCTION [dbo].[GetCalledMLMsTbl]
(
-- Add the parameters for the function here
#MLM_Txt nvarchar(MAX)
)
--RETURNS XML
RETURNS #MLMTbl TABLE
(
pos int,
endpos int,
CalledMLM nvarchar(200)
)
AS
BEGIN
--logic here
insert into #MLMTbl (pos, endpos, CalledMLM) Values (#startpos, #endpos, #MLM_name)
RETURN
END
Then I called the function in the 'from' clause in the select
;with cte as
(
select distinct Name, max(ID) as LatestVersion
from CV3MLM
where Logic like '%:= MLM %' and Logic not like '%standard_libs := mlm%'
--and Name not like '%V61_CCC'
group by Name
)
select MLM2.Name, LatestVersion, C.CalledMLM
from cte join MLM_tbl MLM2 on cte.Name = MLM2.Name and cte.LatestVersion = MLM2.ID
and MLM2.Active = 1 and MLM2.Status in (3, 4)
cross apply dbo.GetCalledMLMsTbl(MLM2.Logic) C
order by MLM2.Name, LatestVersion

Optimizing sql server scalar-valued function

Here is my question,
I have a view calling another view. And that second view has a scalar function which obviously runs for each row of the table. For only 322 rows, it takes around 30 seconds. When I take out the calculated field, it takes 1 second.
I appreciate if you guys give me an idea if I can optimize the function or if there is any other way to increase the performance?
Here is the function:
ALTER FUNCTION [dbo].[fnCabinetLoad] (
#site nvarchar(15),
#cabrow nvarchar(50),
#cabinet nvarchar(50))
RETURNS float
AS BEGIN
-- Declare the return variable here
DECLARE #ResultVar float
-- Add the T-SQL statements to compute the return value here
SELECT #ResultVar = SUM(d.Value)
FROM
(
SELECT dt.*,
ROW_NUMBER()
OVER (PARTITION BY dt.tagname ORDER BY dt.timestamp DESC) 'RowNum'
FROM vDataLog dt
WHERE dt.Timestamp BETWEEN dateadd(minute,-15,getdate()) AND GetDate()
) d
INNER JOIN [SKY_EGX_CONFIG].[dbo].[vPanelSchedule] AS p
ON p.rpp = left(d.TagName,3) + substring(d.TagName,5,5)
+ substring(d.TagName,11,8)
AND right(p.pole,2) = substring(d.TagName,23,2)
AND p.site = #site
AND p.EqpRowNumber = #cabrow
AND p.EqpCabinetName= #cabinet
WHERE d.RowNum = 1
AND Right(d.TagName, 6) = 'kW Avg'
RETURN #ResultVar
END
Scalar-valued functions have atrocious performance. Your function looks like an excellent candidate for an inline table-valued function that you can CROSS APPLY:
CREATE FUNCTION [dbo].[fnCabinetLoad]
(
#site nvarchar(15),
#cabrow nvarchar(50),
#cabinet nvarchar(50)
)
RETURNS TABLE
AS RETURN
SELECT SUM(d.Value) AS [TotalLoad]
FROM
(
SELECT dt.*, ROW_NUMBER() OVER (PARTITION BY dt.tagname ORDER BY dt.timestamp DESC) 'RowNum'
FROM vDataLog dt
WHERE dt.Timestamp BETWEEN dateadd(minute,-15,getdate()) AND GetDate()) d INNER JOIN [SKY_EGX_CONFIG].[dbo].[vPanelSchedule] AS p
ON p.rpp = left(d.TagName,3) + substring(d.TagName,5,5) + substring(d.TagName,11,8)
AND right(p.pole,2) = substring(d.TagName,23,2)
AND p.site = #site
AND p.EqpRowNumber = #cabrow
AND p.EqpCabinetName= #cabinet
WHERE d.RowNum = 1
AND Right(d.TagName, 6) = 'kW Avg'
In your view:
SELECT ..., cabinetLoad.TotalLoad
FROM ... CROSS APPLY dbo.fnCabinetLoad(.., .., ..) AS cabinetLoad
My understanding is the returned result set is 322 rows, but if the vDataLog table is significantly larger, I would run that subquery first and dump that result set into a table variable. Then, you can use that table variable instead of a nested query.
Otherwise, as it stands now, I think the joins are being done on all rows of the nested query and then you're stripping them off with the where clause to get the rows you want.
You really don't need a function and get rid of nested view(very poor performant)! Encapsulate the entire logic in a stored proc to get the desired result, so that instead of computing everything row by row, it's computed as a set. Instead of view, use the source table to do the computation inside the stored proc.
Apart from that, you are using the functions RIGHT, LEFT AND SUBSTRING inside your code. Never have them in WHERE OR JOIN. Try to compute them before hand and dump them into a temp table so that they are computed once. Then index the temp tables on these columns.
Sorry for the theoretical answer, but right now code seems a mess. It needs to go through layers of changes to have decent performance.
Turn the function into a view.
Use it by restraining on the columns site, cabrow and cabinet and Timestamp. When doing that, try storing GetDate() and dateadd(minute,-15,getdate()) on a variable. I think not doing so can prevent you from taking advantage on any index on Timestamp.
SELECT SUM(d.Value) AS [TotalLoad],
dt.Timestamp,
p.site,
p.EqpRowNumber AS cabrow,
p.EqpCabinetName AS cabinet
FROM
( SELECT dt.*,
ROW_NUMBER() OVER (PARTITION BY dt.tagname ORDER BY dt.timestamp DESC)'RowNum'
FROM vDataLog dt) d
INNER JOIN [SKY_EGX_CONFIG].[dbo].[vPanelSchedule] AS p
ON p.rpp = left(d.TagName,3) + substring(d.TagName,5,5) + substring(d.TagName,11,8)
AND right(p.pole,2) = substring(d.TagName,23,2)
WHERE d.RowNum = 1
AND d.TagName LIKE '%kW Avg'

Search for multiple values in xml column in SQL

This is my table
BasketId(int) BasketName(varchar) BasketFruits(xml)
1 Gold <FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID><FID>5</FID><FID>6</FID></FRUITS>
2 Silver <FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID></FRUITS>
3 Bronze <FRUITS><FID>3</FID><FID>4</FID><FID>5</FID></FRUITS>
I need to search for the basket which has FID values 1 and 3
so that in this case i would get Gold and Silver
Although i've reached to the result where i can search for a SINGLE FID value like 1
using this code:
declare #fruitId varchar(10);
set #fruitId=1;
select * from Baskets
WHERE BasketFruits.exist('//FID/text()[contains(.,sql:variable("#fruitId"))]') = 1
HAD it been T-SQL i would have used the IN Clause like this
SELECT * FROM Baskets where FID in (1,3)
Any help/workaround appreciated...
First option would be to add another exist the where clause.
declare #fruitId1 int;
set #fruitId1=1;
declare #fruitId2 int;
set #fruitId2=3;
select *
from #Test
where
BasketFruits.exist('/FRUITS/FID[.=sql:variable("#fruitId1")]')=1 and
BasketFruits.exist('/FRUITS/FID[.=sql:variable("#fruitId2")]')=1
Another version would be to use both variables in the xquery statement, counting the hits.
select *
from #Test
where BasketFruits.value(
'count(distinct-values(/FRUITS/FID[.=(sql:variable("#fruitId1"),sql:variable("#fruitId2"))]))', 'int') = 2
The two queries above will work just fine if you know how many FID parameters you are going to use when you write the query. If you are in a situation where the number of FID's vary you could use something like this instead.
declare #FIDs xml = '<FID>1</FID><FID>3</FID>'
;with cteParam(FID) as
(
select T.N.value('.', 'int')
from #FIDs.nodes('FID') as T(N)
)
select T.BasketName
from #Test as T
cross apply T.BasketFruits.nodes('/FRUITS/FID') as F(FID)
inner join cteParam as p
on F.FID.value('.', 'int') = P.FID
group by T.BasketName
having count(T.BasketName) = (select count(*) from cteParam)
Build the #FIDs variable as an XML to hold the values you want to use in the query.
You can test the last query here: https://data.stackexchange.com/stackoverflow/q/101600/relational-division-with-xquery
It is a bit more involved than I hoped it would be - but this solution works.
Basically, I'm using a CTE (Common Table Expression) which breaks up the table and cross joins all values from the <FID> nodes to the basket names.
From that CTE, I select those baskets that contain both a value of 1 and 3.
DECLARE #Test TABLE (BasketID INT, BasketName VARCHAR(20), BasketFruits XML)
INSERT INTO #TEST
VALUES(1, 'Gold', '<FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID><FID>5</FID><FID>6</FID></FRUITS>'),
(2, 'Silver', '<FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID></FRUITS>'),
(3, 'Bronze', '<FRUITS><FID>3</FID><FID>4</FID><FID>5</FID></FRUITS>')
;WITH IDandFID AS
(
SELECT
t.BasketID,
t.BasketName,
FR.FID.value('(.)[1]', 'int') AS 'FID'
FROM #Test t
CROSS APPLY basketfruits.nodes('/FRUITS/FID') AS FR(FID)
)
SELECT DISTINCT
BasketName
FROM
IDandFID i1
WHERE
EXISTS(SELECT * FROM IDandFID i2 WHERE i1.BasketID = i2.BasketID AND i2.FID = 1)
AND EXISTS(SELECT * FROM IDandFID i3 WHERE i1.BasketID = i3.BasketID AND i3.FID = 3)
Running this query, I do get the expected output of:
BasketName
----------
Gold
Silver
Is this too trivial?
SELECT * FROM Baskets WHERE BasketFruits LIKE '%<FID>1</FID>%' AND BasketFruits LIKE '%<FID>3</FID>%'

Resources