Creating a function out of a select statement in SQL - sql

I have built a select statement to pick the date the participant was first recorded in the database. However I want to create a function out of this select statement to use it. I am fairly new to SQL and have never built a function before. My select statement looks like this:
Select DATEDIFF(day, (select min(startdatetime)
from GamePlay
), enddatetime)
from GamePlay
where ParticipantID = '200'
My attempted function looks like this:
CREATE FUNCTION daysPlayed (#ParticipantID int)
RETURNS DateTime
AS
BEGIN
Return DATEDIFF(day, (select min(startdatetime)
from GamePlay
), enddatetime)
from GamePlay
where ParticipantID = #ParticipantID
END
GO

DATEDIFF does not return a datetime. It returns an int.
Your function might look like this:
CREATE FUNCTION daysPlayed (#ParticipantID int)
RETURNS INT
AS
BEGIN
DECLARE #result INT
SELECT #result=DATEDIFF(day, (select min(startdatetime) from GamePlay), enddatetime) from GamePlay where ParticipantID = #ParticipantID
RETURN #result
END
but i don't think it will do what you want it to do.
Also, please note that using functions in selects over a large number of rows is a guarantee performance hit and you might not be able to use some of the indexes you set up in your tables.

I don't know exactly what you want but see this sample might be helpful
CREATE FUNCTION mDay (#Date nchar(10))
RETURNS nchar(2)
AS
BEGIN
RETURN substring(#Date,9,2)
END
SELECT dbo.Courses.MID, dbo.Masters.ID, dbo.Masters.Name,COUNT(CASE dbo.mDay(CDate) WHEN '01' THEN 2 END) AS day1
FROM dbo.Courses INNER JOIN
dbo.Masters ON dbo.Courses.MID = dbo.Masters.MCode
WHERE (dbo.Courses.CLevel = #Kind) AND (dbo.Courses.CDate BETWEEN #Date1 AND #Date2)
GROUP BY dbo.Courses.MID, dbo.Masters.Name, dbo.Masters.Family, dbo.Masters.ID

Related

Why is SQL server inserting 0 values into my table instead of the correct values using function

Hope somebody can help me with this as I'm completely out of ideas as to why it's happening.
I am currently conducting some analysis on Premier League Match Results and as part of this, I have created a Multi-Statement Table UDF.
This function accepts a HomeTeam, AwayTeam and a MatchDate parameter, and then performs a count of each match result that was won, drawn or lost historically between the home and away team prior to the up match date specified.
This function works fine by manually calling it, and returns values such as
Home Away Draw
0 8 4
I wanted to add this information to every match result in my match table, so created a query to move the matches from a staging table, using OUTER APPLY with the function to insert these values.(I also OUTER APPLY another function prior to this which works fine.) and then insert into my MatchData table.
The query works if I just select the values, but if I INSERT INTO my MatchData table, the values are all populating as 0s.
I have tried numerous tests, which confirm that this also happens if I use a SELECT into, unless the table is temporary.
To add, there is no conversion of the values in question at any point, they remain as integers all the way through and the destination column is of type int also.
Hope somebody can give me some ideas on what to try next. Code is below. apologies for anything that isn't well written, as I have muddled with the code a lot up to now trying to get it to insert the right value
Thanks in advance!
Here's the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [Load].[MoveToMatchData]
AS
BEGIN
INSERT INTO Football.MatchData.PremierLeague
SELECT *
FROM
(SELECT
[League], [MatchID], [Season],
[MatchDate], [HomeTeam], [AwayTeam],
[FTHomeGoals], [FTAwayGoals], [FTResult],
[HTHomeGoals], [HTAwayGoals], [HTResult],
[Referee], [HomeShots], [AwayShots],
[HomeShotsOnTarget], [AwayShotsOnTarget],
[HomeFouls], [AwayFouls], [HomeCorners], [AwayCorners],
[HomeYellows], [AwayYellows], [HomeReds], [AwayReds]
FROM
[Football].[Load].[Staging_MatchData] AS a
WHERE
League = 'E0') AS a
OUTER APPLY
(
SELECT * FROM Football.Load.CreateRelativeTable_Prem
(a.MatchDate, a.HomeTeam, a.AwayTeam, a.Season, A.League)
) as b
OUTER APPLY
Here's the UDF
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [Load].[GetH2HRecords]
(#HomeTeam varchar(50), #AwayTeam varchar(50), #MatchDate date)
RETURNS #H2H TABLE
(
Home int,
Away int,
Draw int
)
AS
BEGIN
DECLARE #FromDate date
SET #FromDate = DATEADD(yyyy,-10,#MatchDate)
INSERT INTO #H2H
SELECT
a.[Number of Matches] as HomeHTH, b.[Number of Matches] as AwayHTH, c.[Number of Matches] as DrawHTH
FROM
(
SELECT
COUNT(MatchID) as [Number of Matchesh]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'H'
) as a
OUTER APPLY
(
SELECT
COUNT(MatchID) as [Number of Matchesa]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'A'
) as b
OUTER APPLY
(
SELECT
COUNT(MatchID) as [Number of Matchesd]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'D'
) as c
RETURN
END
(
SELECT * FROM Football.Load.GetH2HRecords
(a.HomeTeam, a.AwayTeam, a.MatchDate)
) as c
END
This is only potentially an answer, but... why is your Function query so complex? This does the same thing in only one SELECT statement:
CREATE FUNCTION [Load].[GetH2HRecords]
(#HomeTeam varchar(50), #AwayTeam varchar(50), #MatchDate date)
RETURNS #H2H TABLE
(
Home int,
Away int,
Draw int
)
AS
BEGIN
DECLARE #FromDate date
SET #FromDate = DATEADD(yyyy,-10,#MatchDate)
INSERT INTO #H2H
SELECT
SUM(
CASE
WHEN FTResult = 'H'
Then 1
ELSE 0
END
) as HomeHTH,
SUM(
CASE
WHEN FTResult = 'A'
Then 1
ELSE 0
END
) as AwayHTH,
SUM(
CASE
WHEN FTResult = 'D'
Then 1
ELSE 0
END
) as DrawHTH
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
RETURN
END

SQL where clause not getting filtered

I have the following query, but it is not giving any regard to the in the p.created_by =#searchBy where clause, how to correct it so that the results would be filtered according #searchBy too.
ALTER PROC [dbo].[Rptcashcollectionouter] #branchId INT,
#searchBy INT,
#strDate DATETIME=NULL,
#endDate DATETIME=NULL
AS
BEGIN
SELECT DISTINCT p.created_on AS paid_date
FROM reading re
JOIN billing_gen bg ON re.id = bg.reading_id
JOIN customer_registration cr ON bg.account_number = cr.account_number
JOIN payment p ON bg.bill_number = p.bill_number
JOIN customer_category cc ON cr.customer_category_id = cc.id
WHERE p.created_by = #searchBy
AND ( ( #strDate IS NULL )
OR Cast(Floor(Cast(p.created_on AS FLOAT)) AS DATETIME) >=
Cast(Floor(Cast(#strDate AS FLOAT)) AS DATETIME) )
AND ( ( #endDate IS NULL )
OR Cast(Floor(Cast(p.created_on AS FLOAT)) AS DATETIME) <=
Cast(Floor(Cast(#endDate AS FLOAT)) AS DATETIME) )
AND cr.branch_id = #branchId
ORDER BY p.created_on ASC;
END;
Check the value inside your procedure as below.
SELECT #branchId, #searchBy, #strDate,#endDate
And, then try to run the SQL manually with the same value. Also, make sure you have data in your table for your criteria.
Also, what exactly you are trying here ?
Cast(Floor(Cast(p.created_on AS FLOAT)) AS DATETIME)
While executing procedure, make sure you are passing properly value.
Print out all values that are coming (Just for testing).
#searchBy INT is of integer type. But i think "p.created_by =#searchBy" is a type of datetime or date , so it may also conflicts here, or display wrong result. In below line. p.created_by is treating as a datetime or date and #searchby in integer.
WHERE p.created_by = #searchBy

SQL server udf not working

I'm now all day on a fairly simple udf. It's below. When I paste the select statement into a query, it runs as expected... when I execute the entire function, I get "0" every time. As you know there aren't a ton of debugging options, so it's hard to see what value are/ aren't being set as it executes. The basic purpose of it is to make sure stock data exists in a daily pricing table. So I can check by how many days' data I'm checking for, the ticker, and the latest trading date to check. A subquery gets me the correct trading dates, and I use "IN" to pull data out of the pricing and vol table... if the count of what comes back is less than the number of days I'm checking, no good. If it does, we're in business. Any help would be great, I'm a newb that is punting at this point:
ALTER FUNCTION dbo.PricingVolDataAvailableToDateProvided
(#Ticker char,
#StartDate DATE,
#NumberOfDaysBack int)
RETURNS bit
AS
BEGIN
DECLARE #Result bit
DECLARE #RecordCount int
SET #RecordCount = (
SELECT COUNT(TradeDate) AS Expr1
FROM (SELECT TOP (100) PERCENT TradeDate
FROM tblDailyPricingAndVol
WHERE ( Symbol = #Ticker )
AND ( TradeDate IN (SELECT TOP (#NumberOfDaysBack)
CAST(TradingDate AS DATE) AS Expr1
FROM tblTradingDays
WHERE ( TradingDate <= #StartDate )
ORDER BY TradingDate DESC) )
ORDER BY TradeDate DESC) AS TempTable )
IF #RecordCount = #NumberOfDaysBack
SET #Result = 1
ELSE
SET #Result = 0
RETURN #Result
END
#Ticker char seems suspect.
If you don't declare a length in the parameter definition it defaults to char(1) so quite likely your passed in tickers are being silently truncated - hence no matches.
SELECT TOP (100) PERCENT TradeDate ... ORDER BY TradeDate DESC
in the derived table is pointless but won't affect the result.

SQL UDF Group By Parameter Issue

I'm having some issues with a group by clause in SQL. I have the following basic function:
CREATE FUNCTION dbo.fn_GetWinsYear (#Year int)
RETURNS int
AS
BEGIN
declare #W int
select #W = count(1)
from tblGames
where WinLossForfeit = 'W' and datepart(yyyy,Date) = #Year
return #W
END
I'm trying to run the following basic query:
select dbo.fn_GetWinsYear(datepart(yyyy,date))
from tblGames
group by datepart(yyyy,date)
However, I'm encountering the following error message: Column 'tblGames.Date' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Any ideas why this is occurring? FYI, I know I can remove the function and combine into one call but I'd like to keep the function in place if possible.
I think you should be calling your function like this.
select dbo.fn_GetWinsYear(datepart(yyyy,getdate()))
OR
select dbo.fn_GetWinsYear('2010')
Essentially you are just passing a year to your function and the function is returning the number of wins for that year.
If you don't know the year, your function could look something like this...
CREATE FUNCTION dbo.fn_GetWinsYear ()
RETURNS #tblResults TABLE
( W INT, Y INT )
AS
BEGIN
INSERT #tblResults
SELECT count(1), datepart(yyyy,[Date])
FROM tblGames
WHERE WinLossForfeit = 'W'
GROUP BY datepart(yyyy,[Date])
RETURN
END
SELECT * FROM dbo.fn_GetWinsYear()

Best way to calculate Max/Min of N columns in SQL Server

Ok, firstly I've seen this thread. But none of the solutions are very satisfactory. The nominated answer looks like NULLs would break it, and the highest-rated answer looks nasty to maintain.
So I was wondering about something like the following :
CREATE FUNCTION GetMaxDates
(
#dte1 datetime,
#dte2 datetime,
#dte3 datetime,
#dte4 datetime,
#dte5 datetime
)
RETURNS datetime
AS
BEGIN
RETURN (SELECT Max(TheDate)
FROM
(
SELECT #dte1 AS TheDate
UNION ALL
SELECT #dte2 AS TheDate
UNION ALL
SELECT #dte3 AS TheDate
UNION ALL
SELECT #dte4 AS TheDate
UNION ALL
SELECT #dte5 AS TheDate) AS Dates
)
END
GO
Main problems I see are that if there are only 3 fields to compare, you'd still have to specify NULL for the other 2, and if you wanted to extend it to six comparisons it would break existing use. If it was a parameterized stored procedure you could specify a default for each parameter, and adding new parameters wouldn't break existing references. The same method could also obviously be extended to other datatypes or stuff like Min or Avg. Is there some major drawback to this that I'm not spotting? Note that this function works whether some, all or none of the values passed to it are nulls or duplicates.
You can solve null issue with ISNULL function:
SELECT ISNULL(#dte1,0) AS TheDate
UNION ALL
SELECT ISNULL(#dte2,0) AS TheDate
UNION ALL
SELECT ISNULL(#dte3,0) AS TheDate
UNION ALL
SELECT ISNULL(#dte4,0) AS TheDate
UNION ALL
SELECT ISNULL(#dte5,0) AS TheDate) AS Dates
But it will only work with MAX functions.
Here is another suggestion: http://www.sommarskog.se/arrays-in-sql-2005.html
They suggest comma delimited values in a form of string.
The function takes as many parameters as you wish and looks like this:
CREATE FUNCTION GetMaxDate
(
#p_dates VARCHAR(MAX)
)
RETURNS DATETIME
AS
BEGIN
DECLARE #pos INT, #nextpos INT, #date_tmp DATETIME, #max_date DATETIME, #valuelen INT
SELECT #pos = 0, #nextpos = 1
SELECT #max_date = CONVERT(DATETIME,0)
WHILE #nextpos > 0
BEGIN
SELECT #nextpos = charindex(',', #p_dates, #pos + 1)
SELECT #valuelen = CASE WHEN #nextpos > 0
THEN #nextpos
ELSE len(#p_dates) + 1
END - #pos - 1
SELECT #date_tmp = CONVERT(DATETIME, substring(#p_dates, #pos + 1, #valuelen))
IF #date_tmp > #max_date
SET #max_date = #date_tmp
SELECT #pos = #nextpos
END
RETURN #max_date
END
And calling:
DECLARE #dt1 DATETIME
DECLARE #dt2 DATETIME
DECLARE #dt3 DATETIME
DECLARE #dt_string VARCHAR(MAX)
SET #dt1 = DATEADD(HOUR,3,GETDATE())
SET #dt2 = DATEADD(HOUR,-3,GETDATE())
SET #dt3 = DATEADD(HOUR,5,GETDATE())
SET #dt_string = CONVERT(VARCHAR(50),#dt1,21)+','+CONVERT(VARCHAR(50),#dt2,21)+','+CONVERT(VARCHAR(50),#dt3,21)
SELECT dbo.GetMaxDate(#dt_string)
Why not just:
SELECT Max(TheDate)
FROM
(
SELECT #dte1 AS TheDate WHERE #dte1 IS NOT NULL
UNION ALL
SELECT #dte2 AS TheDate WHERE #dte2 IS NOT NULL
UNION ALL
SELECT #dte3 AS TheDate WHERE #dte3 IS NOT NULL
UNION ALL
SELECT #dte4 AS TheDate WHERE #dte4 IS NOT NULL
UNION ALL
SELECT #dte5 AS TheDate WHERE #dte5 IS NOT NULL) AS Dates
That shoud take care of the null problem without introducing any new values
I would pass the Dates in XML (you could use varchar/etc, and convert to the xml datatype too):
DECLARE #output DateTime
DECLARE #test XML
SET #test = '<VALUES><VALUE>1</VALUE><VALUE>2</VALUE></VALUES>'
DECLARE #docHandle int
EXEC sp_xml_preparedocument #docHandle OUTPUT, #doc
SET #output = SELECT MAX(TheDate)
FROM (SELECT t.value('./VALUE[1]','DateTime') AS 'TheDate'
FROM OPENXML(#docHandle, '//VALUES', 1) t)
EXEC sp_xml_removedocument #docHandle
RETURN #output
That would address the issue of handling as many possibilities, and I wouldn't bother putting nulls in the xml.
I'd use a separate parameter to specify the datetype rather than customize the xml & supporting code every time, but you might need to use dynamic SQL for it to work.
A better option is to restructure the data to support column based min/max/avg as this is what SQL is best at.
In SQL Server 2005 you can use the UNPIVOT operator to perform the transformation.
Not always appropriate for every problem, but can make things easier if you can use it.
See:
http://msdn.microsoft.com/en-us/library/ms177410.aspx
http://blogs.msdn.com/craigfr/archive/2007/07/17/the-unpivot-operator.aspx
If you have to do it over one row only, it doesn't matter how you will do it (everything would be fast enough).
For selecting Min/Max/Avg value of several columns PER ROW, solution with UNPIVOT should be much faster than UDF
an other possibility is to create a custom table type, like this:
CREATE TYPE [Maps].[TblListInt] AS TABLE( [ID] [INT] NOT NULL )
then,
CREATE FUNCTION dbo.GetMax(#ids maps.TblListInt READONLY) RETURNS INT
BEGIN
RETURN (select max(id) from #ids)
END
Of course, you can swap "int" with your required type.