Update field by a function - sql

I have the function which gets ID and returns date from table if it exists or returns current date if isn't:
CREATE FUNCTION [dbo].[CLOSEDATE] (#ID int)
RETURNS datetime
AS
BEGIN
DECLARE #closed int;
DECLARE #result datetime;
SELECT #result = created_on from dbo.statuses_history
WHERE journalized_id = #ID and new_status = 'Закрыто';
IF #result IS NULL
SELECT #result = GETDATE()
RETURN (DATEADD(dd, 0, DATEDIFF(dd, 0, #result)))
END;
The next queries return correct date from table:
select dbo.closedate(4170)
select dbo.closedate(id) from issues where id = 4170
And the next code update the record correctly (values from table):
DECLARE #d AS datetime
select #d = dbo.closedate(4170)
UPDATE issues SET created_on = #d WHERE issues.id = 4170
But I get current date in the field if I update the record:
UPDATE issues
SET created_on = dbo.CloseDate(id)
WHERE issues.id = 4170
It looks like the ID parameter doesn't pass to the function.

Your tests (that I missed on the first reading, sorry) are enough to make me very confused. It seems that your test results should not be possible.
My only suggestion would be to recode the function and see what happens...
CREATE FUNCTION [dbo].[CLOSEDATE] (#ID int)
RETURNS TABLE
AS
RETURN
SELECT
(DATEADD(dd, 0, DATEDIFF(dd, 0, ISNULL(MAX(created_on), GetDate())))) AS close_date
FROM
dbo.statuses_history
WHERE
journalized_id = #ID
AND new_status = 'Закрыто'
And then...
UPDATE
issues
SET
created_on = fn.close_date
FROM
issues
CROSS APPLY
dbo.CLOSEDATE(id) AS fn
WHERE
issues.id = 4170

Cross Apply is what you looking for I think.

Related

Multiple parameters error while creating function in SQL Server

I created a function, now rather passing static value I want to add parameter in the function but after calling function it start throwing an error:
Procedure or function dbo.hello has too many arguments specified.
Function :
Create Function dbo.hello
(#InputstartDate Date, #InputendDate Date)
Returns #attendanceTemp table(STUD_NAME VARCHAR(50),
ATTD_DATE DATE ,
attd_DATEs DATE,
Attendance VARCHAR(20))
As
Begin
Declare #startDate DATE
SET #startDate = #InputstartDate
Declare #endDate Date
SET #endDate = #InputendDate
Declare #dateDifference INT
SET #dateDifference = DATEDIFF(day, #startDate,#endDate) ;
Declare #count INT
SET #count = 0
DECLARE #myTable TABLE (STUD_ID int,
countdd int,
STUD_NAME varchar(50),
AttDate Date
)
While #count <= #dateDifference
Begin
Insert Into #myTable (STUD_ID, countdd, STUD_NAME, AttDate)
Values (1, 123, 'HAIDER', #startDate)
Set #count = #count +1
Set #startDate = DATEADD(day, 1, #startDate)
End
Insert Into #attendanceTemp
Select
tb.STUD_NAME, ATTD_DATE, tb.AttDate,
Case
When att.DETAIL Is Null
Then 'ABSENT'
When att.DETAIL = 'ATTENDACE'
Then 'PRESENT'
End As Attendance
from
#myTable tb
Left Join
ATTENDANCE att on tb.AttDate = att.ATTD_DATE
Where
att.STUD_ID = 1 or att.STUD_ID IS NULL
Return
END
Calling the function:
select *
from dbo.hello('2014-04-01', '2014-04-10');
Error:
Procedure or function dbo.hello has too many arguments specified
Possibly you first created the function with only one parameter.
Then made changes to the 'create function' script, and forgot to deploy?
I would;
1. DROP FUNCTION dbo.hello
2. CREATE FUNCTION dbo.hello, with you script
3. Try executing your function again.
The function seems to work fine (Though I cannot run a full test due to not having table 'ATTENDANCE')

Handle Julian and Real Times in one query

Hi Below is some sample data
DECLARE #Time TABLE (
[Time] VARCHAR(250),
[Count] VARCHAR(250)
)
INSERT INTO #Time ([Time],[Count])
VALUES
('13:52','2'),
('13:53','2'),
('13:54','5'),
('13:55','3'),
('13:56','3'),
('13:57','1'),
('13:58','4'),
('13:59','1'),
('130','72'),
('1300','61'),
('1301','40'),
('1302','51'),
('1303','53'),
('1304','59'),
('1305','62'),
('1306','36'),
('1307','56'),
('1308','52')
Select * from #Time
Is there a way to handle both data types so that it outputs as a real time
I use a function that partly works but am getting and error each time:
Msg 8116, Level 16, State 1, Line 1
Argument data type varchar is invalid for argument 2 of dateadd function.
The function I use is as follows and wondered if this can be adopted/changed to account for the poor data eg both types of Times (Julian/Regular).
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
-- Add the parameters for the function here
#JulianTime INT
)
RETURNS DATETIME
AS
BEGIN
-- Declare the return variable here
DECLARE #Result DATETIME
-- Add the T-SQL statements to compute the return value here
IF #JulianTime >= 0 AND #JulianTime < 1440 -- this ensures that the result will be in the range of a datetime data type
SET #Result = DATEADD(MINUTE, #JulianTime, CAST('00:00' AS TIME))
ELSE
SET #Result = CAST('00:00' AS TIME)
-- Return the result of the function
RETURN #Result
END
GO
ADDITION:
The COMPLETE datetime function is here:
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianDateTimeToSQLDateTime] (
-- Add the parameters for the function here
#JulianDate INT,
#JulianTime INT = 0
)
RETURNS DATETIME
AS
BEGIN
-- Declare the return variable here
DECLARE #Result DATETIME
-- Add the T-SQL statements to compute the return value here
IF #JulianDate > 640333 -- this ensures that the result will be in the range of a datetime data type
BEGIN
SET #Result = DATEADD(DAY, (#JulianDate-429), CAST('Jan 1 0001' AS DATETIME2))
IF #JulianTime < 1440 AND #JulianTime >= 0 -- this ensures that the time is between 00:00 and 23:59
SET #Result = DATEADD(MINUTE, #JulianTime, #Result)
END
ELSE
SET #Result = 'Jan 1 1753'
-- Return the result of the function
RETURN #Result
END
GO
I am not sure what exactly are you trying to do from your give information. But I tried to modify the function to return time when I pass it JulianTime as a varchar. Here is the code -- (try to make changes accordingly though)
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
-- Add the parameters for the function here
#JulianTime VARCHAR(255)
)
RETURNS TIME(0)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result TIME(0), #HOUR VARCHAR(2), #MINUTES VARCHAR(2)
-- DECLARE #JulianTime VARCHAR(255)
-- SET #JulianTime = '13:02'
SET #HOUR = SUBSTRING(#JulianTime, 1, 2)
IF(LEN(#JulianTime) = 4)
SET #MINUTES = SUBSTRING(#JulianTime, 3, 2)
IF(LEN(#JulianTime) = 5)
SET #MINUTES = SUBSTRING(#JulianTime, 4, 2)
SET #Result = CONCAT(#HOUR, ':', #MINUTES)
-- PRINT #RESULT
-- Return the result of the function
RETURN #Result
END
GO
I've updated the FUNCTION.
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
#timeString varchar(250)
)
RETURNS TIME
AS
BEGIN
DECLARE #Result TIME ;
/* Ensure that we're working with a fixed date */
DECLARE #epoch datetime = '19000101' ;
/* Check the string for ':' */
IF CHARINDEX(':',#timeString) > 0
/* Can #timeString be parsed as a valid time? */
SET #Result = TRY_PARSE(#timeString AS time) ; /* Returns NULL if bad time string. */
ELSE
/* No ':' so check if valid julian time. */
IF TRY_CONVERT(int,#timeString) IS NOT NULL AND CONVERT(int,#timeString) BETWEEN 0 AND 1439
SET #Result = CAST( DATEADD(minute, CONVERT(int,#timeString), #epoch) AS time) ;
ELSE
SET #Result = NULL ;
RETURN #Result ;
END
http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=1ca82cd76b2932593262601b1742f602
This will only work if you're using something greater than SQL 2012+ because of TRY_PARSE and TRY_CONVERT. If you're lower than 2012, you can modify that bit to do what those functions essentially do.
Select
CASE WHEN CHARINDEX(':',[Time]) > 0 THEN CAST([Time] AS TIME) ELSE DATEADD(MINUTE, TRY_CAST([Time] AS INT), CAST('00:00' AS TIME)) END AS [RealTime],
*
from #Time
This appears to do the trick, thanks for the valuable input
For SQL 2008, use this function:
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
#timeString varchar(250)
)
RETURNS TIME
AS
BEGIN
DECLARE #Result time ;
/* Ensure that we're working with a fixed date */
DECLARE #epoch datetime = '19000101' ;
/* Check the string for ':' */
IF CHARINDEX(':',#timeString) > 0
IF ISDATE(#timeString) = 1
/* Is #timeString a valid time object? */
SET #Result = CAST(#timeString AS time) ;
ELSE
SET #Result = NULL ;
ELSE
/* No ':' so check if valid julian time. */
IF ISNUMERIC(#timeString) = 1 AND CONVERT(int,#timeString) BETWEEN 0 AND 1439
SET #Result = CAST( DATEADD(minute, CONVERT(int,#timeString), #epoch) AS time) ;
ELSE
SET #Result = NULL ;
RETURN #Result ;
END

SQL Function help- Daylight Savings calculation

All,
I wrote a function that essentially takes a timestamp and a shopper ID, and based on that shoppers zipcode work out the UTC offset and correct the time.
The problem I am having is: it's sloooow!
Can anyone see an easy way to speed it up ?
CREATE FUNCTION TimeModifier
(
-- Add the parameters for the function here
#InputDate datetime,#shopperid int
)
RETURNS datetime
AS
BEGIN
--Declare #shopperid int
--set #shopperid=25
-- Declare the return variable here
-- Declare the return variable here
DECLARE #Result datetime
Declare #zip nvarchar(10)
Declare #TimeofYear int
declare #Year int
declare #Winter int
declare #Summer int
select #year=datepart(yyyy,#InputDate)
--If 0 then its outside of the summer hours.
SELECT #timeofyear=count(*) FROM [d].[dbo].[DST-Dates] where #inputdate>=startdate and #inputdate<=enddate
select #zip=zip from d..shopper where shopperid=#shopperid
--Gets the UTC offset for winter and summer
select #winter=winter,#summer=summer FROM [MMD_Feed].[dbo].[ZipCodeZones] where zip=#zip
if(#TimeofYear=0)--IE is it Winter
set #Result=DATEADD(HH,#winter, #Inputdate)
else--Use summer offset
set #Result=DATEADD(HH,#summer, #Inputdate)
--select #Result
-- Return the result of the function
RETURN #Result
END
GO
Thanks
~J
Here is a total shot in the dark for converting this to an iTVF.
CREATE FUNCTION TimeModifier
(
-- Add the parameters for the function here
#InputDate datetime
, #shopperid int
)
RETURNS TABLE WITH SCHEMABINDING
AS RETURN
select DATEADD(Hour, case when t.TimeOfYear = 0 then z.winter else z.summer end, #InputDate) as MyResult
from d.dbo.shopper s
join [MMD_Feed].[dbo].[ZipCodeZones] z on s.zip = z.zip
cross apply
(
SELECT count(*) as TimeOfYear
FROM [d].[dbo].[DST-Dates]
where #inputdate >= startdate
and #inputdate <= enddate
) t
where s.shopperid = #shopperid

SQL Stored Procedure set variables using SELECT

I have a stored procedure in SQL Server 2005 with multiple variables and I want to set the values of these variables using a select statement. All three variables come from a same table and there should be a way to set them using one select statement instead of the way I currently have as shown below. Please help me to figure it out.
DECLARE #currentTerm nvarchar(max)
DECLARE #termID int
DECLARE #endDate datetime
SET #currentTerm =
(
Select CurrentTerm from table1 where IsCurrent = 1
)
SET #termID =
(
Select TermID from table1 where IsCurrent = 1
)
SET #endDate =
(
Select EndDate from table1 where IsCurrent = 1
)
select #currentTerm = CurrentTerm, #termID = TermID, #endDate = EndDate
from table1
where IsCurrent = 1
One advantage your current approach does have is that it will raise an error if multiple rows are returned by the predicate. To reproduce that you can use.
SELECT #currentTerm = currentterm,
#termID = termid,
#endDate = enddate
FROM table1
WHERE iscurrent = 1
IF( ##ROWCOUNT <> 1 )
BEGIN
RAISERROR ('Unexpected number of matching rows',
16,
1)
RETURN
END

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.