SELECT #ResultVar = SUM(CASE WHEN Budget_Qr IS NULL THEN 0 ELSE Budget_Qr END)
I want the result to return a Float value.
Not knowing the data type of Budget_Qr makes it a bit tricky to answer. You likely can use the CAST function. Also, do the very simple sanity check to make sure that the variable #ResultVar has been declared with an appropriate data type. Also, in your snippet I do not know if you omitted parts of the SQL statement to focus on the issue or not.
If the column Budget_Qr is not a FLOAT you can do something like:
DECLARE #ResultVar AS FLOAT;
SELECT #ResultVar =
SUM(
CASE WHEN Budget_Qr IS NULL THEN CAST(0 AS FLOAT)
ELSE CAST(Budget_Qr AS FLOAT)
END
)
FROM SomeTable;
Many RDBMS systems have built-in functions to deal with NULL values and setting a default. If you happen to be running SQL Server (and a few others), the following would do the same thing as the above statement:
DECLARE #ResultVar AS FLOAT;
SELECT #ResultVar = SUM(CAST(COALESCE(Budget_Qr, 0) AS FLOAT))
FROM SomeTable;
You can always check the documentation for your specific RDBMS to see what functions are available.
Related
I have written a stored procedure that hashes the value of a certain column. I need to use this HASHBYTES function in a CASE WHEN or IIF statement, like this:
DECLARE #Hash varchar(255) = 'testvalue'
SELECT IIF(1=1, HASHBYTES('SHA1',#Hash), #Hash)
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) END AS Hashcolumn
I can't get my head around why I get different outputs from above queries? it seems that whenever I add an ELSE in the CASE WHEN / IIF statement, it returns a string of weird characters (like ü<þ+OUL'RDOk{\Ìø in above example).
Can anyone tell me why this is happening? I need to use the CASE WHEN or IIF.
Thanks guys
IIF returns the data type with the highest precedence from the types in true_value and false_value. In this case, it's #Hash1 which is varchar(255) so your result is getting cast to varchar(255). See below.
DECLARE #Hash varchar(255) = 'testvalue'
SELECT cast(HASHBYTES('SHA1',#Hash) as varchar(255))
Similarly, CASE works the same way. However, if you don't add an ELSE or another WHEN that would conflict with the data type, it will work. This is because an ELSE NULL is implied. i.e.
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) END
However, if you add another check, then precedence kicks in, and it will be converted.
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) WHEN 1=2 THEN #Hash END AS Hashcolumn
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) ELSE #Hash END AS Hashcolumn
The output of a select query is a virtual table. In a relational db a column of a table is constrained to single data type.. so here what happens is implicit conversion is being done by the server engine inorder to render a sigle type and hence weird characters are returned.
The nature of conversion is as #scsimon says it follows highest precedence order.
The following query should help.
DECLARE #Hash varchar(255) = 'testvalue'
SELECT IIF(1=1, CONVERT(VARCHAR(255),HASHBYTES('SHA1',#Hash),2), #Hash)
SELECT CASE WHEN 1=2 THEN CONVERT(VARCHAR(255),HASHBYTES('SHA1',#Hash),2)
ELSE #Hash END AS Hashcolumn
I just released some code into production that is randomly causing errors. I already fixed the problem by totally changing the way I was doing the query. However, it still bothers me that I don't know what was causing the problem in the first place so was wondering if someone might know the answer. I have the following query inside of a stored procedure. I'm not looking for comments about that's not a good practice to make queries with nested function calls and things like that :-). Just really want to find out why it doesn't work consistently. Randomly the function in the query will return a non-numeric value and cause an error on the join. However, if I immediately rerun the query it works fine.
SELECT cscsf.cloud_server_current_software_firewall_id,
dbo.fn_GetCustomerFriendlyFromRuleName(cscsf.rule_name, np.policy_name) as rule_name,
cscsf.rule_action,
cscsf.rule_direction,
cscsf.source_address,
cscsf.source_mask,
cscsf.destination_address,
cscsf.destination_mask,
cscsf.protocol,
cscsf.port_or_port_range,
cscsf.created_date_utc,
cscsf.created_by
FROM CLOUD_SERVER_CURRENT_SOFTWARE_FIREWALL cscsf
LEFT JOIN CLOUD_SERVER cs
ON cscsf.cloud_server_id = cs.cloud_server_id
LEFT JOIN CLOUD_ACCOUNT cla
ON cs.cloud_account_id = cla.cloud_account_id
LEFT JOIN CONFIGURATION co
ON cla.configuration_id = co.configuration_id
LEFT JOIN DEDICATED_ACCOUNT da
ON co.dedicated_account_id = da.dedicated_account_id
LEFT JOIN CORE_ACCOUNT ca
ON da.core_account_number = ca.core_account_id
LEFT JOIN NETWORK_POLICY np
ON np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name))
WHERE cs.cloud_server_id = #cloud_server_id
AND cs.current_software_firewall_confg_guid = cscsf.config_guid
AND ca.core_account_id IS NOT NULL
ORDER BY cscsf.rule_direction, cscsf.cloud_server_current_software_firewall_id
if you notice the join
ON np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name))
calls a function.
Here is that function:
ALTER FUNCTION [dbo].[fn_GetIDFromRuleName]
(
#rule_name varchar(100)
)
RETURNS varchar(12)
AS
BEGIN
DECLARE #value varchar(12)
SET #value = dbo.fn_SplitGetNthRow(#rule_name, '-', 2)
SET #value = dbo.fn_SplitGetNthRow(#value, '_', 2)
SET #value = dbo.fn_SplitGetNthRow(#value, '-', 1)
RETURN #value
END
Which then calls this function:
ALTER FUNCTION [dbo].[fn_SplitGetNthRow]
(
#sInputList varchar(MAX),
#sDelimiter varchar(10) = ',',
#sRowNumber int = 1
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #value varchar(MAX)
SELECT #value = data_split.item
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as row_num FROM dbo.fn_Split(#sInputList, #sDelimiter)
) AS data_split
WHERE
data_split.row_num = #sRowNumber
IF #value IS NULL
SET #value = ''
RETURN #value
END
which finally calls this function:
ALTER FUNCTION [dbo].[fn_Split] (
#sInputList VARCHAR(MAX),
#sDelimiter VARCHAR(10) = ','
) RETURNS #List TABLE (item VARCHAR(MAX))
BEGIN
DECLARE #sItem VARCHAR(MAX)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT #sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))), #sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
The reason it is "randomly" returning different things has to do with how SQL Server optimizes queries, and where they get short-circuited.
One way to fix the problem is the change the return value of fn_GetIDFromRuleName:
return (case when isnumeric(#value) then #value end)
Or, change the join condition:
on np.network_policy_id = (select case when isnumeric(dbo.fn_GetIDFromRuleName(cscsf.rule_name)) = 1)
then dbo.fn_GetIDFromRuleName(cscsf.rule_name) end)
The underlying problem is order of evaluation. The reason the "case" statement fixes the problem is because it checks for a numeric value before it converts and SQL Server guarantees the order of evaluation in a case statement. As a note, you could still have problems with converting numbers like "6e07" or "1.23" which are numeric, but not integers.
Why does it work sometimes? Well, clearly the query execution plan is changing, either statically or dynamically. The failing case is probably on a row that is excluded by the WHERE condition. Why does it try to do the conversion? The question is where the conversion happens.
WHere the conversion happens depends on the query plan. This may, in turn, depend on when the table cscf in question is read. If it is already in member, then it might be read and attempted to be converted as a first step in the query. Then you would get the error. In another scenario, the another table might be filtererd, and the rows removed before they are converted.
In any case, my advice is:
NEVER have implicit conversion in queries.
Use the case statement for explicit conversions.
Do not rely on WHERE clauses to filter data to make conversions work. Use the case statement.
I'm looking to add some code to my TSQL arsenal to defend against performing aggregations in SQL when the data in a column is null. Ideally there would be a SUM_NN (for sum no null), in sql server which would raiserror if any of the values were null.
Since you can't raiserror from a UDF, the only way I could think of doing it looked like this, though I don't like this solution:
CREATE FUNCTION dbo.NULL_TEST_F(#arg FLOAT)
RETURNS FLOAT
AS
BEGIN
IF(#arg IS NULL)
SELECT 1/0
RETURN #arg
END
Note: I think this is stupid solution, but I've gotten burned way too many times when I'm missing data. Also, we're using SQL Server 2005, but I'm open to 2008 and 2012 solutions. Additionally, I'd like to know how other databases deal with this type of issue.
This was my final solution:
CREATE FUNCTION [dbo].[NullTest_F]
(
#input FLOAT,
#message VARCHAR(100)
)
RETURNS FLOAT
AS
BEGIN
DECLARE #test INT
IF(#input IS NULL)
SELECT #test = CAST(#message AS INT)
RETURN #input
END
I can then embed this with a useful error message when running aggregate functions. Example:
CREATE TABLE Data (
Date DATETIME,
DataPoint FLOAT
)
INSERT INTO Data (Date, DataPoint) VALUES ('2012-03-01', 4)
INSERT INTO Data (Date, DataPoint) VALUES ('2012-03-02', 6)
SELECT SUM(NullTest_F(DataPoint, 'Missing data at' + CONVERT(VARCHAR(10), Data))
FROM Data
Maybe this one will help:
https://stackoverflow.com/a/4681815/1371070
You could create a function like suggested in the answer linked above and call it from your aggregate in case #arg is null.
It's still the same strategy overall but It's a better error to throw than divide-by-zero, I guess.
I want to do a select that do a cast only for a specific ID but it doesn't seems to work.
Example :
SELECT
CASE
WHEN(#ID <> 1) THEN Code
WHEN(#ID = 1) THEN Cast(Code AS int)
END Code FROM ....
Any Idea ?
Why do want to do this? A SQL Server expression has a single fixed type. In other words, a single expression can't be varchar(50) or int depending on how the expression is evaluated. You could cast each case to sql_variant, but that may or may not make sense based on what you're trying to do.
EDIT
If you are executing this query from a stored procedure, you could create an IF..ELSE block to execute a different version of the query based on the value of #ID. For example:
IF (#ID = 1) BEGIN
SELECT Cast(Code AS int) AS Code FROM ...
END
ELSE BEGIN
SELECT Code FROM ...
END
It works for me. Check if the #id is of type int and if all values of column Code can be casted to int.
UPDATE If you have a value that can't be casted to int, your query won't work.
So you can write 2 different queries.
Smth like
IF #id = 1 THEN
SELECT code ...
ELSE
SELECT Cast(Code AS int) as Code
You could have also written:
select case when #ID = 1 then CAST(Code as int) else Code end as Code
from...
By the way, any data containing alphabetic characters won't cast to int.
Perhaps could we better help you if you tell us what you want to achieve, with some sample data provided?
I understand that T-SQL is not object oriented. I need to write a set of functions that mimics method overloading in C#.
Is function overloading supported in T-SQL in any way? If there is a hack to do this, is it recommended?
No, there is no way to do this.
I recommend you revisit the requirement, as "make apples look like oranges" is often difficult to do, and of questionable value.
One thing I have done successfully is to write the function in such a way as to allow it to handle null values, and then call it with nulls in place of the parameters you would like to omit.
Example:
create function ActiveUsers
(
#departmentId int,
#programId int
)
returns int
as
begin
declare #count int
select #count = count(*)
from users
where
departmentId = isnull(#departmentId, departmentId)
and programId = isnull(#programId, programId)
return #count
end
go
Uses:
select ActiveUsers(1,3) -- users in department 1 and program 3
select ActiveUsers(null,3) -- all users in program 3, regardless of department
select ActiveUsers(null,null) -- all users
You could pass in a sql_variant, but it comes with all sorts of hazards around it; you can't really use strong typing like you can with OO languages and overloading.
If you need to find the base type within your function, you can use the SQL_VARIANT_PROPERTY function.
You can pass in an array of values within a single string and parse them out using this techique by Erland Sommarskog.
Create a function with a varchar(max) parameter or several if necessary, then have your parameter values in that string like:
param1;param2;parma3;param4
or
param1:type;param2:type;param3:type
or
calltype|param1;param2;param3
etc, you are only limited by your imagination...
Use the technique from the link to split apart this array and use program logic to use those values as you wish.
One solution would be to utilize the sql_variant data type. This example works as long as you use the same datatype for both values. Returns whatever datatype you send it.
create function dbo.Greater(
#val1 sql_variant
,#val2 sql_variant
) returns sql_variant
as
begin
declare #rV sql_variant
set #rV = case when #val1 >= #val2 then #val1
else #val2 end
return #rV
end
go
A solution I've had some luck with is either creating a number of functions that each takes a different data type - or casting all input to NVARCHAR(MAX).
1. creating a number of functions that each takes a different data type
CREATE FUNCTION [dbo].[FunctionNameDatetime2]
CREATE FUNCTION [dbo].[FunctionNameInt]
CREATE FUNCTION [dbo].[FunctionNameString] --(this is not a typo)
CREATE FUNCTION [dbo].[FunctionNameUniqueidentifier]
...
Problem: duplication of code, and a lot functions
2. Cast all input to NVARCHAR(MAX)
CREATE FUNCTION [dbo].[IntToNvarchar]
(
#Key INT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN ISNULL(CAST(#Key AS NVARCHAR), '');
END
CREATE FUNCTION [dbo].[FunctionName]
(
#Key NVARCHAR(MAX)
)
RETURNS CHAR(32)
AS
BEGIN
DECLARE #something CHAR(32)
do stuff ...
RETURN #something;
END
SELECT [dbo].[FunctionName]([dbo].[IntToNvarchar](25))
Problem: less elegant code than overloading
I overload Functions all the time, but I happen to know that these kind of issues are often highly dependent on platform.
On our DB2 system, I routinely overload like the following:
CREATE Function Schema1.F1 (parm date)
returns date
return date + 1month;
CREATE Function Schema1.F1 (parm timestamp)
returns date
return date(timestamp) + 1month;
This is actually quite useful when you have multiple queries which have similar formating requirements.
The only problem I have found about this so far, is you better be sure that you want the function because the standard drop function "schema"."name" fails because it cannot determine which function to drop. If anyone knows how to drop overloaded sql functions, let me know!