Need some t-sql clarification - sql

This is a follow up to my previous question, in t-sql
SELECT SCOPE_IDENTITY()
returns a BIGINT, I did the following to get it to return an INT:
DECLARE #X INT
INSERT ...
SELECT #X = SCOPE_IDENTITY()
-- if i don't include the line below, it will return a BIGINT
SELECT #X
Why does it return a BIGINT unless I do SELECT #X at the end?
p.s. turns out
SELECT #X = SCOPE_IDENTITY()
doesn't return anything, it just sets #x

The statement
SELECT #X = SCOPE_IDENTITY()
is an assignment statement. As in most programming languages, an assignment statement is executed by first evaluating the right hand side. In this case the right hand side evaluates to a bigint. When the value of #X gets the resulting bigint, there is an implicit type conversion, because #X is a different type (int) than the value it's receiving.
SQL is a typed language, and the type of an expression (such as SCOPE_IDENTITY() here) depends on the expression, not on what happens to the expression's value after evaluation.
Analogy:
DECLARE #i INT;
SET #i = 3.2 + 0.2;
You wouldn't suggest that 3.2 + 0.2 is an integer, would you? It's 3.4, a decimal. Only because of the assignment is there an implicit conversion to INT.
There's no magic in most programming languages.

SELECT SCOPE_IDENTITY()
returns a BIGINT as you have seen
SELECT #X = SCOPE_IDENTITY()
returns BIGINT and casts it into INT variable #X
So you are returning BIGINT SCOPE_IDENTITY and also concurrently casting it to INT and setting that result to #X.
Returning #X returns the INT result.
Just some interesting reading on the subject.
SQL Server 7.0 Books Online also
stated: "It is recommended that SET
#local_variable be used for variable
assignment rather than SELECT
#local_variable."

Related

I'm having trouble with the sql language functions

I have been working in the SQL language for 1 month. That's why I may not understand things. But I always get an error when creating these functions. I wrote the codes down there. The error message is as follows:
Only one expression can be specified in the select list when
the subquery is not introduced with EXISTS.
CREATE FUNCTION deneme(
#ID int
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #value nvarchar(max)
SET #value = (
SELECT * FROM information
WHERE #ID = Person_id
)
RETURN #value
END
You cannot assign all columns values into one variable like that, and since you're passing an ID of the person and you want your function to returns the info of that person
CREATE FUNCTION dbo.deneme
(
#ID int
)
RETURNS NVARCHAR(300)
AS
BEGIN
DECLARE #Value NVARCHAR(300) = N'';
SELECT #Value = CONCAT(I.FirstName, N' ', I.LastName)
FROM Information I
WHERE I.PersonId = #ID;
RETURN #Value;
END
As others have pointed out you are trying to place multiple columns/fields in a single column/field.
#ID is a single column. "Select *" is presumably returning more than a single column or else it wouldn't be much help!
In order to change this and make it work as you are trying here, you would need to concat the columns you are trying to return. This is almost surely not the best way to accomplish this but sometimes concating names (for example) is fine.
The other issue you may be running into is even if you changed this to "Select ID" but still have errors it may be because the query returns more than one row matching that criteria. You can work around this (it is a work around most of the time) by limiting the number of rows returned with "TOP 1". But be careful as this may not return the information you want. You can use an order by statement to help ensure it is the correct information (such as order by Time_entered).
The code below with "TOP 1" and concatenating multiple columns (and casting as the same type) will always work.
Again, these are not Best Practices and shouldn't be used to sanitize data in production... but it does show you why you are getting these errors and how to prevent them.
CREATE FUNCTION deneme(
#ID int
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #value nvarchar(max)
SET #value = (
SELECT TOP 1 cast(First_name as nvarchar) + N' ' + cast(Last_name as nvarchar) FROM information
WHERE #ID = Person_id
Order by Time_entered desc
)
RETURN #value
END

Strange Subtraction Behavior in SQL Server: 1-1 = 2.22044604925031E-16 [duplicate]

In the following query
declare #a float(23)
declare #b float(23)
declare #c float(53)
set #a = 123456789012.1234
set #b = 1234567.12345678
set #c = #a * #b
select #c
select LTRIM(STR((#c),32,12))
declare #x decimal(16,4)
declare #y decimal(16,8)
declare #z decimal (32,12)
set #x = 123456789012.1234
set #y = 1234567.12345678
set #z = #x * #y
select #z
I get answers as
1.52415693411713E+17
152415693411713020.000000000000
152415692881907790.143935926652
From the above answers the third answer is the correct one. Is this the reason why float data type is called Approximate Numeric Data Type
Or am I doing something fundamentally wrong.
BTW this is due to a problem I have with legacy system wherein I have to use float as storage data type, at the same time in there should not be loss of precision while calculation.
Please suggest alternatives, or an explanation.
Float is accurate to 15 significant figures only (in SQL Server).
This is demonstrated by 1.52415693411713 E+17 where 1.52415693411713 (15 digits) is as accurate as you'll get. The final 020... after 152415693411713 with STR is made up is the resolution of floating point
To keep precision, don't use float. It is that simple. CAST to decimal if you want for calculation, but if you CAST back to float you are limited to 15 digits
See "What Every Computer Scientist Should Know About Floating-Point Arithmetic"
The last answer
152415692881907790.143935926652
is providing scale up to 12 decimal places because you have declared #z accordingly.
declare #z decimal (32,12)
The second parameter in this declaration is scale which is set to 12.
More on the this can be found at http://msdn.microsoft.com/en-us/library/ms187746.aspx
Problem not with float Data type.
Problem is with using float(23).
This data type has a capacity of holding 8 significant digits, not 15 as float(53).
And that's exactly how many correct digits you've got in the output.
Calculation of 2 float(23) numbers is done with float(23) precision, and only afterwards converted to float(53), which is absolutely useless and misleading.
Fix the initial declarations of #a and #b and the problem will disappear.
Always check what is the value to have actually assigned to a variable:
declare #a float(23)
declare #b float(23)
declare #c float(23)
set #a = 123456789012.1234
set #b = 1234567.12345678
SET #c = #a * #b
select #a, #b, #c
GO
declare #a float
declare #b float
declare #c float
set #a = 123456789012.1234
set #b = 1234567.12345678
SET #c = #a * #b
select #a, #b, #c
GO
The outputs of the SELECTs:
1.234568E+11 1234567 1.524157E+17
123456789012.123 1234567.12345678 1.52415692881908E+17
P.S. Operations of 2nd level (multiplication, division) and upper on decimal values use float point computing, so they cannot be any more precise than same operations on FLOAT values, assuming the same level of depth used for both types.

How to do math with GUIDs in SQL Server

I need to do some math with SQL Server GUIDs in a trigger and I'm having difficulty figuring out how to convert a uniqueidentifier to a numeric(38,0).
One potential problem: my understand is that both of these datatypes are 16-byte "integers". If I'm wrong here, please correct me.
Otherwise, how would I go about this conversion? I've tried CAST and CONVERT and keep getting Explicit conversion from data type uniqueidentifier to numeric is not allowed. as an error message whenever I try. I'd really like to not have to parse each character and do hex math in a UDF to do this.
Is this possible?
Here's my script to repro this real quick:
DECLARE #guid uniqueidentifier
SET #guid = NEWID()
DECLARE #a numeric(38,0)
SET #a = 2
PRINT CAST(#guid AS numeric(38,0)) -- fails
PRINT #guid / #a -- also fails
Unfortunately, I haven't stumbled on a conversion from a hexadecimal value in a VARCHAR to a NUMERIC short of looping through one digit at a time.
declare #GUID as UniqueIdentifier = NewId()
declare #Binary as VarBinary(64) = #GUID
declare #String as VarChar(64) = Convert( VarChar(64), #Binary, 2 )
select #GUID as 'GUID', #Binary as 'Binary', #String as 'String'

SQL - safely downcast BIGINT to INT

I have a CSV I'm importing into our database. One of the "columns" contains data that should be an INT but some rows have numbers that only fall in the BIGINT range (because they're test data from one of our partners). We store INT internally and have no desire to change.
I want to safely downcast from BIGINT to INT. By safely, I mean no errors should be raised if an arithmetic overflow happens. If the cast/conversion succeeds, I want my script to go on. If it fails, I want it to short-circuit. I can't seem to figure out the proper syntax. This is what I've got:
DECLARE #UserIDBigInt BIGINT = 9723021913; -- actually provided by query param
--Setting within the INT range successfully converts
--SET #UserIDBigInt = 5;
DECLARE #UserID INT = CONVERT(INT, #UserIDBigInt);
--DECLARE #UserID INT = CAST(#UserIDBigInt AS INT);
SELECT #UserIDBigInt
SELECT #UserID
IF #UserID IS NOT NULL BEGIN
SELECT 'Handle it as reliable data'
END
I've thought about comparing #UserIDBigInt to the valid range of an INT (-2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647)), but I really don't like that approach. That's my fallback. I was hoping for some language constructs or built-in functions I could use. If I absolutely have to compare to the valid range, are there at least some built-in constants (like C#'s int.MinValue & int.MaxValue)?
EDIT: Corrected typo.
Add these to your script:
SET ARITHABORT OFF;
SET ARITHIGNORE ON;
This will convert any overflow values to NULL.
More info here: http://msdn.microsoft.com/en-us/library/ms184341.aspx
Cast your bigint to varbinary, then store the lower half to #UserID and check the upper half:
if the upper half is all 0's and the lower half represents a non-negative value, #UserID then contains the correct int value;
if the upper half is all 1's and #UserID is negative, it's all right too;
otherwise there's an arithmetic overflow.
Here's an implementation:
DECLARE #UserIDBigInt BIGINT = 9723021913;
DECLARE #UserID INT, #HighInt INT;
WITH v AS (SELECT CAST(#UserIDBigInt AS varbinary) AS bin)
SELECT
#HighInt = SUBSTRING(bin, 1, 4),
#UserID = SUBSTRING(bin, 5, 4)
FROM v;
IF (#HighInt = 0 AND #UserID >= 0 OR #HighInt = -1 AND #UserID < 0) BEGIN
SELECT 'Handle it as reliable data'
END
I'm not sure this is the best answer but it is one I came up with earlier on my own. It is possible to catch the exception/error and gracefully continue execution.
Example:
DECLARE #UserIDBigInt BIGINT = 9723021913;
DECLARE #UserID INT;
BEGIN TRY
SET #UserID = #UserIDBigInt;
END TRY BEGIN CATCH
END CATCH
IF #UserID IS NULL BEGIN
SELECT 'Handle it as unreliable data'
RETURN
END
SELECT 'Handle it as reliable data'
You could also convert the value to a string, trim it to length and convert to int. not the best way, but a safe easy way for sure

stored procedures and using tsql functions in parameters calls

Environment sql server 2005 sp3
I have a stored proc that takes an INT as input. I want to CAST a CHAR to an INT during the call to the stored proc. it seems I cannot do that. I get a syntax error before #foo. I do not see it can someone help me find it please.
CREATE PROCEDURE testme
#test AS INT
AS
BEGIN
SELECT #TEST
END
DECLARE #foo AS CHAR(6)
set #foo = '11test'
EXEC testMe #test = CAST(Substring(#foo,1,2) as int)
first off all, you can't cast '11test' as an int
second, if the value can be converted to an int, you don't need to cast, an implicit cast will happen
DECLARE #foo AS CHAR(6)
set #foo = '2'
EXEC testMe #test =#foo
If you want to test if it can be converted to an int, grab the IsInt function from here: IsNumeric, IsInt, IsNumber and use that to test before making the proc call
EDIT
here is how you can do it
DECLARE #foo AS CHAR(6)
set #foo = '11test'
SET #foo = CAST(Substring(#foo,1,2) as int)
EXEC testMe #test = #foo
you can't pass functions to procs, this is why GETDATE() doesn't work either, either use an intermediate variable or cast back to the same variable
You cannot convert a char field to int when it contains non-numeric characters.
I would suggest creating a function that loops through the chars and removes any that are non-numeric.