Convert char to smallint SQL - sql-server

I have column in SQL Server table called Book_pagenumber declared as smallint. One of page contained the number and letter like 50a when I tried to use insert statement as shown below
insert into [Books] (book_id, book_author, Book_pagenumber)
values (50, 'Test', cast('50a' as smallint))
I get an error message
Conversion failed when converting the varchar value to data type smallint
I also used
INSERT INTO [Books] (book_id, book_author, Book_pagenumber)
VALUES (50, 'Test', CAST(CAST('50a' AS VARCHAR(3)) AS SMALLINT)
I got the data inserted but just number 50 without the letter.
Please I need advice how to convert char to smallint without changing the column data type

One option could be
Example
Declare #YourTable table (Book_pagenumber varchar(50))
Insert Into #YourTable values
('50a'),
('123b'),
('175')
Select *
,AsInt = try_convert(int,left(Book_pagenumber,patindex('%[^0-9]%',Book_pagenumber+'a')-1))
From #YourTable
Returns
Book_pagenumber AsInt
50a 50
123b 123
175 175

You can create a function that will clean your string from characters, other than numeric:
-- Create Function that will keep only numeric values in #String
CREATE FUNCTION dbo.udf_NumericOnly (#String NVARCHAR(64))
RETURNS SMALLINT
AS
BEGIN
DECLARE
#Result VARCHAR(5) = N''
, #Position INT = 1
, #Length INT;
SELECT #Length = LEN(#String);
WHILE (#Position <= #Length)
BEGIN
SELECT #Result += CASE WHEN SUBSTRING(#String, #Position, 1) BETWEEN CHAR(48) AND CHAR(57) THEN SUBSTRING(#String, #Position, 1) ELSE N'' END;
SELECT #Position += 1;
END
RETURN #Result;
END
GO
-- Test the Function
INSERT Books (book_id, book_authoer, Book_pagenumber)
SELECT
50
, 'Test'
, dbo.udf_NumericOnly('a4s22df5');

Related

Need to calculate the sum of second using a function from a string variable using SQL

There is a column named as duration in a table called Adventurous.The column has values as below.In the column Suffix of 'H' is hours,Suffix of 'M' is minutes and Suffix of 'S' is seconds.How can we select the hours, minutes and seconds and convert all into seconds i.e sum of all the hours minutes and seconds in the form of seconds.
Duration
--------
PT10M13S
PT13M22S
PT1H2M18S
PT11S
i tried using substring and charindex as below and tried to create a function but i am getting error:
Declare #Duration varchar(30) ='PT16H13M42S', #Dur varchar(10)
Declare #hours int
declare #mins int
declare #secs int
declare #len int
select #len = len(substring (#Duration, 3, len(#Duration))), #Dur=substring (#Duration, 3, len(#Duration))
select #hours = charindex('H', #Dur)
select substring(#Dur, 1, #hours-1)
select #Duration=substring (#Dur, #hours+1, len(#Dur))
select #mins = charindex('M', #Duration)
select substring(#Duration, 1, #mins-1)
select #Dur=substring (#Duration, #mins+1, len(#Duration))
select #secs= charindex('S', #Dur)
select substring(#Dur, 1, #Secs-1)
select #len, #Dur, #Duration
example PT1H2M18S= 1*3600+2*60+18=3738
Try this:
Declare #t table (duration varchar(50))
insert into #t values ('PT1H2M18S')
select
convert(int,substring(duration,CHARINDEX('PT',duration)+2,(CHARINDEX('H',duration)-CHARINDEX('PT',duration))-2))*3600 +
convert(int,substring(duration,CHARINDEX('H',duration)+1,(CHARINDEX('M',duration)-CHARINDEX('H',duration))-1))*60 +
convert(int,substring(duration,CHARINDEX('M',duration)+1,(CHARINDEX('S',duration)-CHARINDEX('M',duration))-1))
from #t
Another possible approach is to transform Duration text input into a valid T-SQL expression ('PT1H2M18S' will be transformed into '1*3600+2*60+18*1+0'). After that, consider next two options:
Generate and execute a dynamic statement, which will evaluate each expression or
Define a function to make the calculations
Input:
CREATE TABLE #Data (
Duration varchar(50)
)
INSERT INTO #Data
(Duration)
VALUES
('PT10M13S'),
('PT13M22S'),
('PT1H2M18S'),
('PT100H'),
('PT11S')
Dynamic statement:
DECLARE #stm nvarchar(max)
SET #stm = N''
SELECT #stm = #stm +
CONCAT(
'UNION ALL SELECT ''',
Duration,
''' AS [Duration], ',
REPLACE(REPLACE(REPLACE(REPLACE(Duration, 'H', '*3600+'), 'M', '*60+'), 'S', '*1+'), 'PT', ''),
'0 AS [Seconds] '
)
FROM #Data
SET #stm = STUFF(#stm, 1, 10, N'')
EXEC (#stm)
User-defined function:
CREATE FUNCTION [udfCalculateHMS] (#expression varchar(100))
RETURNS int
AS
BEGIN
DECLARE #result int
DECLARE #s varchar(100)
--
SET #result = 0
WHILE (CHARINDEX('+', #expression) > 0) BEGIN
SET #s = SUBSTRING(#expression, 1, CHARINDEX('+', #expression) - 1)
SET #expression = STUFF(#expression, 1, CHARINDEX('+', #expression), '')
SET #result = #result +
CONVERT(int, SUBSTRING(#s, 1, CHARINDEX('*', #s) - 1)) *
CONVERT(int, STUFF(#s, 1, CHARINDEX('*', #s), ''))
END
-- Return value
RETURN #result
END
SELECT
Duration,
dbo.udfCalculateHMS(CONCAT(REPLACE(REPLACE(REPLACE(REPLACE(Duration, 'H', '*3600+'), 'M', '*60+'), 'S', '*1+'), 'PT', ''), '0')) AS Seconds
FROM #Data
Output:
Duration Seconds
PT10M13S 613
PT13M22S 802
PT1H2M18S 3738
PT100H 360000
PT11S 11
This is how I would move across the string the pull out the correct integer values. The number of characters to offset may change depending on if you can have varying numbers of characters per hour, minute and second. But the principle should get you going.
Declare #Duration varchar(30) ='PT16H13M42S'
select * from
(values(substring(#Duration,CHARINDEX('PT',#duration)+2,(CHARINDEX('H',#Duration)-CHARINDEX('PT',#Duration))-2),
substring(#Duration,CHARINDEX('H',#duration)+1,(CHARINDEX('M',#Duration)-CHARINDEX('H',#Duration))-1),
substring(#Duration,CHARINDEX('M',#duration)+1,(CHARINDEX('S',#Duration)-CHARINDEX('M',#Duration))-1))) duration ([Hours], [Minutes], [Seconds]);
Throwing in an answer using Tally Table and mostly reliable ISNUMERIC SQL function
This should be good for small datasets. I also assume that you have valid numbers i.e. hour part are not >24, minute part or seconds part are not >60
create table #t(duration nvarchar(max));
insert into #t values
('PT10M13S')
,('PT13M22S')
,('PT1H2M18S')
,('PT11S')
select
totalseconds= sum(m.factor* case when ISNUMERIC(substring(duration, r-2,2))=1 then substring(duration, r-2,2) else substring(duration, r-1,1) end ),
duration from #t
cross join
(
select r=row_number() over (order by (select NULL))-1
from sys.objects s1 cross join sys.objects s2
)t
join
(values('S',1),('M',60),('H',3600)) m(part,factor)
on r<=len(duration) and substring(duration, r,1) =m.part
group by duration
drop table #t
PS: See this SO link which suggests that scalar UDF are faster than ISNUMERIC
Fastest way to check if a character is a digit?

Cannot convert from nvarchar to int when there are variables in where clause

select #now_total = COUNT(*)
from CountTable
where quotename(CHAR(65 + #i - 1)) > 0 ;
I wrote this in SQL Server in order to get the number of rows where the value in column 'A', B' ... 'Z' is greater than 0 respectively. But SQL Server tells me
cannot convert from nvarchar to int
I tried to change the quotename to just [A]>0, that's ok. But I have to calculate for i=1...26. What's the correct statement for this?
Thank you!
This is because you cannot specify the columns dynamically using plain SQL. What happens here is that quotename(CHAR(65)) gets converted to [A]. But instead of using the column A, it's being treated as a literal. Hence, the error:
Conversion failed when converting the nvarchar value '[A]' to data
type int.
You should use dynamic sql:
CREATE TABLE tbl(
ID INT IDENTITY(1, 1),
A INT,
B INT
)
INSERT INTO tbl(A, B) VALUES
(1, 1), (1, 2), (2, 2), (2, 3);
DECLARE #now_total INT
DECLARE #i INT = 1
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'SELECT #now_total = COUNT(*) FROM tbl WHERE ' + QUOTENAME(CHAR(65 + #i - 1) ) + ' > 0'
EXEC sp_executesql #sql, N'#now_total INT OUT', #now_total OUT
SELECT #now_total
DROP TABLE tbl
You can't specify a column name dynamically in the middle of a SQL statement like that.
You can construct a string containing a SQL statement and then execute it:
declare #now_total int
declare #sql nvarchar(max)
set #sql =
'select #now_total = count(*) from CountTable where ' + quotename(char(64+#i)) + ' > 0;'
exec sp_executesql #sql, N'#now_total int output', #now_total output;
As you can see, that's not a lot of fun.
It would be better to change your table structure from
create table CountTable (A int, B int, C int, ...)
insert CountTable values (1, 34, 0, ..)
To something like
create table CountTable (CountId int, ValueId char(1), Count int)
insert CountTable values (1, 'A', 1)
insert CountTable values (1, 'B', 34)
insert CountTable values (1, 'C', 0)
...
Then your query becomes
select count(distinct CountId) from CountTable where ValueId=char(64+#i) and Count>0
And you don't have to use dynamic SQL.

T- SQL Capture data Overflow errors

Aim - To capture data overflow errors from source to destination tbls.
I've have a source table that is being dumped with data and column data type is varchar
While the destination table has specific column type like decimal (12, 5), or Int etc...
Is the anyway I can find all the rows that don't fit the spec and flag it so that they are not part of insert and hence not cause the script to fail..
MS-SQL 2008 R2
Yes. You can do it by checking whether the value in the column of source table is a number before inserting to the destination table using ISNUMERIC in SQL Server.
SOURCE TABLE
CREATE TABLE #SOURCE(SCOL VARCHAR(300))
INSERT INTO #SOURCE
SELECT 'ABC'
UNION ALL
SELECT '121'
UNION ALL
SELECT '-145.78'
UNION ALL
SELECT '200,000'
UNION ALL
SELECT N'£100.20'
UNION ALL
SELECT '0E0'
UNION ALL
SELECT N'₤'
DESTINATION TABLE
CREATE TABLE #DESTINATION(DCOL NUMERIC(5,2))
-- Only the numeric values will be inserted and is Type-safe
INSERT INTO #DESTINATION
SELECT SCOL
FROM #SOURCE
WHERE ISNUMERIC(SCOL)=1
SELECT * FROM #DESTINATION
EDIT :
If ISNUMERIC doesn't satisfy the values in your column, create the below function and check the value in your column in WHERE clause. I got the function from here.
CREATE FUNCTION dbo.isReallyNumeric
(
#num VARCHAR(64)
)
RETURNS BIT
BEGIN
IF LEFT(#num, 1) = '-'
SET #num = SUBSTRING(#num, 2, LEN(#num))
DECLARE #pos TINYINT
SET #pos = 1 + LEN(#num) - CHARINDEX('.', REVERSE(#num))
RETURN CASE
WHEN PATINDEX('%[^0-9.-]%', #num) = 0
AND #num NOT IN ('.', '-', '+', '^')
AND LEN(#num)>0
AND #num NOT LIKE '%-%'
AND
(
((#pos = LEN(#num)+1)
OR #pos = CHARINDEX('.', #num))
)
THEN
1
ELSE
0
END
END
GO
And in the query to select and insert, use the above function
INSERT INTO #DESTINATION
SELECT SCOL
FROM #SOURCE
WHERE DBO.isReallyNumeric(SCOL)=1

SQL Server OUTPUT clause

I am a little stuck with why I can not seem to get the 'new identity' of the inserted row with the statement below. SCOPE_IDENTITY() just returns null.
declare #WorkRequestQueueID int
declare #LastException nvarchar(MAX)
set #WorkRequestQueueID = 1
set #LastException = 'test'
set nocount off
DELETE dbo.WorkRequestQueue
OUTPUT
DELETED.MessageEnvelope,
DELETED.Attempts,
#LastException,
GetUtcdate(), -- WorkItemPoisened datetime
DELETED.WorkItemReceived_UTC
INTO dbo.FaildMessages
FROM dbo.WorkRequestQueue
WHERE
WorkRequestQueue.ID = #WorkRequestQueueID
IF ##ROWCOUNT = 0
RAISERROR ('Record not found', 16, 1)
SELECT Cast(SCOPE_IDENTITY() as int)
Any assistance would be most appreciated.
For now I use a workaround this like so.
declare #WorkRequestQueueID int
declare #LastException nvarchar(MAX)
set #WorkRequestQueueID = 7
set #LastException = 'test'
set nocount on
set xact_abort on
DECLARE #Failed TABLE
(
MessageEnvelope xml,
Attempts smallint,
LastException nvarchar(max),
WorkItemPoisened_UTC datetime,
WorkItemReceived_UTC datetime
)
BEGIN TRAN
DELETE dbo.WorkRequestQueue
OUTPUT
DELETED.MessageEnvelope,
DELETED.Attempts,
#LastException,
GetUtcdate(), -- WorkItemPoisened datetime
DELETED.WorkItemReceived_UTC
INTO
#Failed
FROM
dbo.WorkRequestQueue
WHERE
WorkRequestQueue.ID = #WorkRequestQueueID
IF ##ROWCOUNT = 0 BEGIN
RAISERROR ('Record not found', 16, 1)
Rollback
END ELSE BEGIN
insert into dbo.FaildMessages select * from #Failed
COMMIT TRAN
SELECT Cast(SCOPE_IDENTITY() as int)
END
EDITED FEB'2013
#MartinSmith alerts us that this bug don't want be fixed by Microsoft.
"Posted by Microsoft on 2/27/2013 at 2:18 PM Hello Martin, We
investigated the issue and found that changing the behavior is not an
easy thing to do. It would basically require redefining some of the
behavior when both INSERT & OUTPUT INTO target has identity columns.
Given the nature of the problem & the uncommon scenario, we have
decided not to fix the issue. -- Umachandar, SQL Programmability
Team"
EDITED OCT'2012
This is caused by a bug:
Testing bug:
Quoting OUTPUT Clause doc:
##IDENTITY, SCOPE_IDENTITY, and IDENT_CURRENT return identity values
generated only by the nested DML statement, and not those generated by
the outer INSERT statement.
After test it It seems that scope_identity() only works if outer operation is an insert in a table with identity columns:
Test 1: Delete
create table #t ( a char(1) );
create table #d ( a char(1), i int identity );
insert into #t
values ('a'),('b'),('c');
delete #t
output deleted.a into #d;
select SCOPE_IDENTITY(), * from #d;
a i
---- - -
null a 1
null b 2
null c 3
Test 2: Inserting in outer table with identity
create table #t ( a char(1), i int identity );
create table #d ( a char(1), i int identity );
insert into #t
values ('x'),('x'),('x');
insert into #t
output inserted.a into #d
values ('a'),('b');
select scope_identity(), * from #d;
a i
- - -
2 a 1
2 b 2
Test 3: Inserting in outer table without identity
create table #t ( a char(1) );
create table #d ( a char(1), i int identity );
insert into #t
values ('x'),('x'),('x');
insert into #t
output inserted.a into #d
values ('a'),('b');
select scope_identity(), * from #d;
a i
---- - -
null a 1
null b 2
You might try to use a table variable for your output clause, thus allowing you to explicitly insert into FaildMessages:
declare #WorkRequestQueueID int
declare #LastException nvarchar(MAX)
set #WorkRequestQueueID = 1
set #LastException = 'test'
set nocount off
-- Declare a table variable to capture output
DECLARE #output TABLE (
MessageEnvelope VARCHAR(50), -- Guessing at datatypes
Attempts INT, -- Guessing at datatypes
WorkItemReceived_UTC DATETIME -- Guessing at datatypes
)
-- Run the deletion with output
DELETE dbo.WorkRequestQueue
OUTPUT
DELETED.MessageEnvelope,
DELETED.Attempts,
DELETED.WorkItemReceived_UTC
-- Use the table var
INTO #output
FROM dbo.WorkRequestQueue
WHERE
WorkRequestQueue.ID = #WorkRequestQueueID
-- Explicitly insert
INSERT
INTO dbo.FaildMessages
SELECT
MessageEnvelope,
Attempts,
#LastException,
GetUtcdate(), -- WorkItemPoisened datetime
WorkItemReceived_UTC
FROM #output
IF ##ROWCOUNT = 0
RAISERROR ('Record not found', 16, 1)
SELECT Cast(SCOPE_IDENTITY() as int)

SQL Server: Inserting a list of ints into a temporary table

I have a list of IDs in a text file like this:
24641985 ,
24641980 ,
24641979 ,
24641978 ,
24641976 ,
24641974 ,
...
...
24641972 ,
24641971 ,
24641970 ,
24641968 ,
24641965)
There's tens of thousands of them.
Now I need to know which ids are in this list, that do not correspond to an ID in my table.
I guess I should put them into a temporary table, then say something like:
select theId
from #tempIdCollection
where theId not in (select customerId from customers)
Problem is I don't know how to get them into the temp table!
Can anyone help? This doesn't have to be efficient. I've just got to run it once. Any solution suggestions welcome!
Thanks in advance!
-Ev
I'd use a table variable. You declare it just like a regular variable.
declare #tempThings table (items int)
insert #tempThings values (1)
Have a "permanent temp" table, also known as an "inbox" table. Just a simple tabled named something like "temp_bunchOfKeys".
Your basic sequence is:
1) Truncate temp_bunchOfKeys
2) BCP the text file into temp_bunchOfKeys
3) Your sql is then:
select theId
from Temp_BunchOfKeys
where theId not in (select customerId from customers)
I had the same problem but with strings instead of integers, and solved it by using a split function (see code below) that returns a table variable with the list content. Modify the function to suit your purpose.
Example of how to call the function
create table #t (Id int, Value varchar(64))
insert into #t (Id, Value)
select Id, Item
from dbo.fnSplit('24641978, 24641976, ... 24641972, 24641971', ',')
/*Do your own stuff*/
drop table #t
Function
if object_id(N'dbo.fnSplit', N'TF') is not null
drop function dbo.fnSplit
GO
create function dbo.fnSplit(#string varchar(max), #delimiter char(1))
returns #temptable table (Id int, Item varchar(8000))
as
begin
-- NB! len() does a rtrim() (ex. len('2 ') = 1)
if ( len( #string ) < 1 or #string is null ) return
declare #idx int
declare #slice varchar(8000)
declare #stringLength int
declare #counter int ; set #counter = 1
set #idx = charindex( #delimiter, #string )
while #idx!= 0
begin
set #slice = ltrim( rtrim( left(#string, #idx - 1)))
set #slice = replace( replace(#slice, char(10), ''), char(13), '')
insert into #temptable(Id, Item) values(#counter, #slice)
-- To handle trailing blanks use datalength()
set #stringLength = datalength(#string)
set #string = right( #string, (#stringLength - #idx) )
set #idx = charindex( #delimiter, #string )
set #counter = #counter + 1
end
-- What's left after the last delimiter
set #slice = ltrim(rtrim(#string))
set #slice = replace( replace(#slice, char(10), ''), char(13), '')
insert into #temptable(Id, Item) values(#counter, #slice)
return
end
GO
You can copy paste all those ids from text file to a excel file. Then use import from excel feature in the Sql server to create a table out of that excel file. Quite simple really. Let me know if you need more specific instructions.

Resources