Create stored procedure for retrieving data - sql-server

I am very new to creating stored procedures and I need to know if this is even possible. The query returns the results from a start date to say a week before(7 days) and returns a random sample of a percent(in this case .4). I have this query that runs and get the desired results:
select top .4 percent *
from
(
SELECT DISTINCT T.date,T.job,C.creditType
FROM [dbo].[Trip_Credits] as T
INNER JOIN [dbo].[Credits] as C
ON T.credit=C.code
INNER JOIN [dbo].[Credit_Types] CT
ON CT.Code = C.creditType
AND C.creditType =2
where T.postdate >= DateADD(day, -7, getDate())
) pop order by newID()
What I would like to know is if I can create a stored procedure where the user can Input the percentage value(where .4 is), data(where T.postdate is), and length(where -7 is). Keep in mind the only one that is in the table is T.postdate. I saw some examples that read like this:
EXECUTE HumanResources.uspGetEmployeesTest2 #FirstName = N'Pilar', #LastName = N'Ackerman';
And this is where I would want the user to be able to enter the values they want. I know I can do this in C# but I was just wondering if this is possible in sql server Management Studio

Yes you can. It would look something like this:
CREATE PROCEDURE dbo.your_sp_name
(
#percent DECIMAL (1,1),
#date DATETIME,
#DaysToLookBack INT
)
AS
BEGIN
select top(#percent) percent *
from
(
SELECT DISTINCT T.date,T.job,C.creditType
FROM [dbo].[Trip_Credits] as T
INNER JOIN [dbo].[Credits] as C
ON T.credit=C.code
INNER JOIN [dbo].[Credit_Types] CT
ON CT.Code = C.creditType
AND C.creditType =2
where #date >= DateADD(day, #DaysToLookBack, getDate())
) pop order by newID()
END

Related

How do I use ##RowCount in a stored procedure, against rows in another table to work out the percentage?

Firstly, may I state that I'm aware of the ability to, e.g., create a new function, declare variables for rowcount1 and rowcount2, run a stored procedure that returns a subset of rows from a table, then determine the entire rowcount for that same table, assign it to the second variable and then 1 / 2 x 100....
However, is there a cleaner way to do this which doesn't result in numerous running of things like this stored procedure? Something like
select (count(*stored procedure name*) / select count(*) from table) x 100) as Percentage...
Sorry for the crap scenario!
EDIT: Someone has asked for more details. Ultimately, and to cut a very long story short, I wish to know what people would consider the quickest and most processor-concise method there would be to show the percentage of rows that are returned in the stored procedure, from ALL rows available in that table. Does that make more sense?
The code in the stored procedure is below:
SET #SQL = 'SELECT COUNT (DISTINCT c.ElementLabel), r.FirstName, r.LastName, c.LastReview,
CASE
WHEN c.LastReview < DateAdd(month, -1, GetDate()) THEN ''OUT of Date''
WHEN c.LastReview >= DateAdd(month, -1, GetDate()) THEN ''In Date''
WHEN c.LastReview is NULL THEN ''Not Yet Reviewed'' END as [Update Status]
FROM [Residents-'+#home_name+'] r
LEFT JOIN [CarePlans-'+#home_name+'] c ON r.PersonID = c.PersonID
WHERE r.Location = '''+#home_name+'''
AND CarePlanType = 0
GROUP BY r.LastName, r.FirstName, c.LastReview
HAVING COUNT(ELEMENTLABEL) >= 14
Thanks
Ant
I could not tell from your question if you are attempting to get the count and the result set in one query. If it is ok to execute the SP and separately calculate a table count then you could store the results of the stored procedure into a temp table.
CREATE TABLE #Results(ID INT, Value INT)
INSERT #Results EXEC myStoreProc #Parameter1, #Parameter2
SELECT
Result = ((SELECT COUNT(*) FROM #Results) / (select count(*) from table))* 100

Allocate Unique Random case number SQL 2008

I have a list of teams in one table and list of cases in another table. I have to allocate a unique random case number to each one of the members in the team. What is the best way to generate unique random case number for each team member. I have read about NewID() and CRYPT_GEN_RANDOM(4) functions. I tried using them but not getting unique number for each team member. Can some one please help me. Thanks for your time. I am using SQL 2008.
I have a 'Teams' table which has team members, their ids(TM1,TM2 etc.) and their names.
I have another 'Cases' table which has ID numbers like 1,2,3,4 etc. I want to allocate random case to each team member. The desired output should be as below.
Team member Random_case_allocated
TM1 3
TM2 5
TM3 7
TM4 2
TM5 8
TM6 6
I have tried
SELECT TOP 1 id FROM cases
ORDER BY CRYPT_GEN_RANDOM(4)
It is giving the same id for all team members. I want a different case id for each team member. Can someone please help. Thank you.
The TOP(1) ORDER BY NEWID() will not work the way you are trying to get it to work here. The TOP is telling the query engine you are only interested on the first record of the result set. You need to have the NEWID() evaluate for each record. You can force this inside of a window function, such as ROW_NUMBER(). This could optimized I would imagine, however, it was what I could come up with from the top of my head. Please note, this is not nearly a truly random algorithm.
UPDATED With Previous Case Exclusions
DECLARE #User TABLE(UserId INT)
DECLARE #Case TABLE(CaseID INT)
DECLARE #UserCase TABLE (UserID INT, CaseID INT, DateAssigned DATETIME)
DECLARE #CaseCount INT =10
DECLARE #SaveCaseID INT = #CaseCount
DECLARE #UserCount INT = 100
DECLARE #NumberOfUserAllocatedAtStart INT= 85
WHILE(#CaseCount > 0)BEGIN
INSERT #Case VALUES(#CaseCount)
SET #CaseCount = #CaseCount-1
END
DECLARE #RandomCaseID INT
WHILE(#UserCount > 0)BEGIN
INSERT #User VALUES(#UserCount)
SET #UserCount = #UserCount-1
IF(#NumberOfUserAllocatedAtStart > 0 )BEGIN
SET #RandomCaseID = (ABS(CHECKSUM(NewId())) % (#SaveCaseID))+1
INSERT #UserCase SELECT #UserCount,#RandomCaseID,DATEADD(MONTH,-3,GETDATE())
SET #RandomCaseID = (ABS(CHECKSUM(NewId())) % (#SaveCaseID))+1
INSERT #UserCase SELECT #UserCount,#RandomCaseID,DATEADD(MONTH,-5,GETDATE())
SET #RandomCaseID = (ABS(CHECKSUM(NewId())) % (#SaveCaseID))+1
INSERT #UserCase SELECT #UserCount,#RandomCaseID,DATEADD(MONTH,-2,GETDATE())
SET #NumberOfUserAllocatedAtStart=#NumberOfUserAllocatedAtStart-1
END
END
;WITH RowNumberWithNewID AS
(
SELECT
U.UserID, C.CaseID, UserCase_CaseID = UC.CaseID,
RowNumber = ROW_NUMBER() OVER (PARTITION BY U.UserID ORDER BY NEWID())
FROM
#User U
INNER JOIN #Case C ON 1=1
LEFT OUTER JOIN #UserCase UC ON UC.UserID=U.UserID AND UC.CaseID=C.CaseID AND UC.DateAssigned > DATEADD(MONTH, -4, UC.DateAssigned)
WHERE
UC.CaseID IS NULL OR UC.CaseID <> C.CaseID
)
SELECT
UserID,
CaseID,
PreviousCases = STUFF((SELECT ', '+CONVERT(NVARCHAR(10), UC.CaseID) FROM #UserCase UC WHERE UC.UserID=RN.UserID FOR XML PATH('')),1,1,'')
FROM RowNumberWithNewID RN
WHERE
RN.RowNumber=1

Comparison results between two select statements in a stored procedure

I want to start off by saying that I am brand new to Stored Procedures, and am basically teaching myself how to do them. Any suggestions or advice will be greatly appreciated. I would mail you chocolate if I could.
The Gist: My organization's clients take a survey on their initial visit and on each 6th subsequent visits. We need to know if the individual has shown improvement over time. The way we decided to do this is compare the 1st to the most recent. So if they have been to 18 sessions, it would be the 1st and 3rd surveys that are compared (because they would have completed the survey 3 times over 18 sessions).
I have been able to obtain the "first" score and the "recent" score with two complex, multiple layered-nested select statements inside of one stored procedure. The "first" one is a TOP(1) linking on unique id (DOCID) and then ordered by date. The "recent" one is a TOP(1) linking on unique id (DOCID) and then ordered by date descending. This gets me exactly what I need within each statement, but it does not output what I need correctly which is obviously to the ordering in the statements.
The end result will be to create a Crystal Report with it for grant reporting purposes.
Declare
#StartDate Date,
#EndDate Date,
#First_DOCID Int,
#First_Clientkey Int,
#First_Date_Screening Date,
#First_Composite_Score Float,
#First_Depression_Score Float,
#First_Emotional_Score Float,
#First_Relationship_Score Float,
#Recent_DOCID Int,
#Recent_Clientkey Int,
#Recent_Date_Screening Date,
#Recent_Composite_Score Float,
#Recent_Depression_Score Float,
#Recent_Emotional_Score Float,
#Recent_Relationship_Score Float,
#Difference_Composit_Score Float,
#Difference_Depression_Score Float,
#Difference_Emotional_Score Float,
#Difference_Relationship_Score Float
SET #StartDate = '1/1/2016'
SET #EndDate = '6/1/2016'
BEGIN
SELECT #First_DOCID = CB24_1.OP__DOCID, #First_Date_Screening = CB24_1.Date_Screening, #First_Clientkey = CB24_1.ClientKey, #First_Composite_Score = CB24_1.Composite_score, #First_Depression_Score = CB24_1.Depression_Results, #First_Emotional_Score = CB24_1.Emotional_Results, #First_Relationship_Score = CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
...
ORDER BY CB24_2.Date_Screening))
ORDER BY ClientKey DESC
END
BEGIN
SELECT #Recent_DOCID = CB24_1.OP__DOCID, #Recent_Date_Screening = CB24_1.Date_Screening, #Recent_Clientkey = CB24_1.ClientKey, #Recent_Composite_Score = CB24_1.Composite_score, #Recent_Depression_Score = CB24_1.Depression_Results, #Recent_Emotional_Score = CB24_1.Emotional_Results, #Recent_Relationship_Score = CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
...
ORDER BY CB24_2.Date_Screening DESC))
ORDER BY ClientKey
END
SET #Difference_Composit_Score = (#Recent_Composite_Score - #First_Composite_Score)
SET #Difference_Depression_Score = (#Recent_Depression_Score - #First_Depression_Score)
SET #Difference_Emotional_Score = (#Recent_Emotional_Score - #First_Emotional_Score)
SET #Difference_Relationship_Score = (#Recent_Relationship_Score - #First_Relationship_Score)
SELECT
#First_DOCID AS First_Docid,
#First_Clientkey AS First_Clientkey,
#First_Date_Screening AS First_Date_Screening,
#First_Composite_Score AS First_Composite_Score,
#First_Depression_Score AS First_Depression_Score,
#First_Emotional_Score AS First_Emotional_Score,
#First_Relationship_Score AS First_Relationship_Score,
#Recent_DOCID AS Recent_DOCID,
#Recent_Clientkey AS Recent_Clientkey,
#Recent_Date_Screening AS Recent_Date_Screening,
#Recent_Composite_Score AS Recent_Composite_Score,
#Recent_Depression_Score AS Recent_Depression_Score,
#Recent_Emotional_Score AS Recent_Emotional_Score,
#Recent_Relationship_Score AS Recent_Relationship_Score,
#Difference_Composit_Score AS Difference_Composit_Score,
#Difference_Depression_Score AS Difference_Depression_Score,
#Difference_Emotional_Score AS Difference_Emotional_Score,
#Difference_Relationship_Score AS Difference_Relationship_Score
In SQL you don't want unnecessary declared variables.
Here's a contrived but reproducible example which utilizes common table expressions and window functions that should get you in the right direction. I created the stored procedure from the template with the necessary input parameters (which in real life you'd like to avoid).
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE dbo.Client_Improvement_Results
(#StartDate DATETIME, #EndDate DATETIME)
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
-- You would never do this in real-life but for a simple reproducible example...
DECLARE #Survey TABLE
(
Clientkey INT,
Date_Screening DATE,
Composite_Score FLOAT
)
INSERT INTO #Survey
VALUES
(1, '2014-04-01', 42.1),
(1, '2014-04-10', 46.1),
(1, '2014-04-20', 48.1),
(2, '2014-05-10', 40.1),
(2, '2014-05-20', 30.1),
(2, '2014-05-30', 10.1)
;
--Use Common Table Expression & Window Functions to ID first/recent visit by client
WITH CTE AS (
SELECT
S.Clientkey
,S.Composite_Score
,S.Date_Screening
,First_Date_Screening = MIN(S.Date_Screening) OVER(PARTITION BY S.Clientkey)
,Recent_Date_Screening = MAX(S.Date_Screening) OVER(PARTITION BY S.Clientkey)
FROM #Survey AS S
)
--Self join of CTE with proper filters
--applied allows you to return differences in one row
SELECT
f.Clientkey
,f.First_Date_Screening
,f.Recent_Date_Screening
,Difference_Score = r.Composite_Score - f.Composite_Score
FROM
CTE AS f --first
INNER JOIN CTE AS r --recent
ON f.Clientkey = r.Clientkey
WHERE
f.Date_Screening = f.First_Date_Screening
AND r.Date_Screening = r.Recent_Date_Screening
END
GO
Here is the solution I came up with after everyone amazing advice.
I want to go back and replace the TOP(1) with another new thing I learned at some point:
select pc.*
from (select pc.*, row_number() over (partition by Clientkey, ProgramAdmitKey order by Date_Screening) as seqnum
from FD__CNSLG_BASIS24 PC) pc
where seqnum = 1
I will have to play with the above script a bit first, however. It doesn't like to be inserted into the larger script below.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
BEGIN
SET NOCOUNT ON;
Declare
#StartDate Date,
#EndDate Date
SET #StartDate = '1/1/2016'
SET #EndDate = '6/1/2016'
WITH CNSL_Clients AS (
SELECT PC_CNT.Clientkey, PC_Cnt.ProgramAdmitKey, PC_Cnt.OP__DOCID
FROM FD__Primary_Client as PC_Cnt
INNER JOIN VW__Cnsl_Session_Count_IndvFamOnly as cnt
ON PC_Cnt.Clientkey = CNT.Clientkey AND PC_Cnt.ProgramAdmitKey = CNT.ProgramAdmitKey
WHERE ((pc_CNT.StartDate between #StartDate AND #EndDate) OR (pc_CNT.StartDate <= #StartDate AND pc_CNT.ENDDate >= #StartDate) OR (pc_CNT.StartDate <= #StartDate AND pc_CNT.ENDDate is null))
AND CNT.SessionCount>=6
),
FIRST_BASIS AS (
SELECT CB24_1.OP__DOCID, CB24_1.Date_Screening, CB24_1.ClientKey, CB24_1.ProgramAdmitKey, CB24_1.Composite_score, CB24_1.Depression_Results,CB24_1.Emotional_Results, CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
FROM FD__CNSLG_BASIS24 AS CB24_2
Inner JOIN CNSL_Clients
ON CB24_2.ClientKey = CNSL_Clients.ClientKey AND CB24_2.ProgramAdmitKey = CNSL_Clients.ProgramAdmitKey
WHERE (CB24_1.ClientKey = CB24_2.ClientKey) AND (CB24_1.ProgramAdmitKey = CB24_2.ProgramAdmitKey)
ORDER BY CB24_2.Date_Screening))
),
RECENT_BASIS AS (
SELECT CB24_1.OP__DOCID, CB24_1.Date_Screening, CB24_1.ClientKey, CB24_1.ProgramAdmitKey, CB24_1.Composite_score, CB24_1.Depression_Results,CB24_1.Emotional_Results, CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
FROM FD__CNSLG_BASIS24 AS CB24_2
Inner JOIN CNSL_Clients
ON CB24_2.ClientKey = CNSL_Clients.ClientKey AND CB24_2.ProgramAdmitKey = CNSL_Clients.ProgramAdmitKey
WHERE (CB24_1.ClientKey = CB24_2.ClientKey) AND (CB24_1.ProgramAdmitKey = CB24_2.ProgramAdmitKey)
ORDER BY CB24_2.Date_Screening DESC))
)
SELECT F.OP__DOCID AS First_DOCID,R.OP__DOCID as Recent_DOCID,F.ClientKey, F.ProgramAdmitKey, F.Composite_Score AS FComposite_Score, R.Composite_Score as RComposite_Score, Composite_Change = R.Composite_Score - F.Composite_Score, F.Depression_Results AS FDepression_Results, R.Depression_Results AS RDepression_Resluts, Depression_Change = R.Depression_Results - F.Depression_Results, F.Emotional_Results AS FEmotional_Resluts, R.Emotional_Results AS REmotionall_Reslu, Emotional_Change = R.Emotional_Results - F.Emotional_Results, F.Relationships_Results AS FRelationships_Resluts, R.Relationships_Results AS RRelationships_Resluts, Relationship_Change = R.Relationships_Results - F.Relationships_Results
FROM First_basis AS F
FULL Outer JOIN RECENT_BASIS AS R
ON F.ClientKey = R.ClientKey AND F.ProgramAdmitKey = R.ProgramAdmitKey
ORDER BY F.ClientKey
END
GO

SQL Server Function like behavior in a query without creating a function?

I have a situation where I can save a lot of repeated text in a query if I use a function however at this site I do not have rights to create a function on the server.
It there a way to have a function defined in the body of the query and then call it from the query itself?
Hopefully I post the following stripped down code correctly. The real query calls two functions, count by year and revenue by year.
-- Function
alter function PetRockCountByYear
(#Company varchar(50), #DateFrom varchar(20), #DateTo varchar(20))
RETURNS varchar(50)
AS
begin
return (select isnull( SUM( transact.qty ), 0)
from TRANSACT
inner join CATALOG on transact.ITEM_NO = catalog.ITEM_NO
where transact.TRAN_DATE >= #DateFrom
and transact.TRAN_DATE <= #DateTo
and transact.ITEM_NO = 'PetRock'
and #Company = transact.company
and transact.ITEM_NO = catalog.ITEM_NO)
end
-- Simplified Query Below
select distinct
company.account, company.COMPANY,
company.STATUS, company.code,
-- Report counts from 1970 - 2015
(select dbo.PetRockCountByYear(company.COMPANY, '01/01/1970', '12/31/1970') ) as '#1970',
(select dbo.PetRockCountByYear( company.COMPANY , '01/01/1971', '12/31/1971') ) as '#1971'
from
TRANSACT
Inner join
invoices on transact.inv_no = invoices.inv_no
Inner join
COMPANY on invoices.COMPANY = company.company
where
ITEM_NO = 'PetRock'
order by
company.ACCOUNT
There are no temporary functions in SQL-Server T-SQL, you need to have CREATE FUNCTION rights on the database look at: https://msdn.microsoft.com/en-us/library/ms178569.aspx#Anchor_2 And https://msdn.microsoft.com/en-us/library/ms191320.aspx#Anchor_0

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'

Resources