SQL function, extract numbers before - - sql-server-2005

i'm playing around with building a sql function that will extract numbers from a title, which is what the following code below does. Although, i want to modify this function to parse numbers into sections. For example:
Current Data in title field:
QW 1 RT 309-23-1
QW 1 RT 29-1
QW 1 RT 750-1
QW RT 750-1
Temp tables created once function is ran on title field:
column 1 Column 2 Column 3 Column 4
1 309 23 1
1 29 1 Null
1 750 1 Null
Null 750 1 Null
create function [dbo].[ExtractNumbers](#Numbers nvarchar(2000))
returns nvarchar(2000)
as
BEGIN
declare #NonNumericIndex int
set #NonNumericIndex = PATINDEX('%[^0-9]%',#Numbers)
WHILE #NonNumericIndex > 0
begin
SET #Numbers = REPLACE(#Numbers,SUBSTRING(#Numbers,#NonNumericIndex,1),'')
SET #NonNumericIndex = PATINDEX('%[^0-9]%',#Numbers)
SET
end
return #Numbers
END

Here's one way.
Although actually at the end I realised the format was more fixed than I had originally realised so you may be better off just using the various string manipulation functions to calculate the columns directly.
WITH TestTable AS
(
SELECT 'QW 1 RT 309-23-1' AS title UNION ALL
SELECT 'QW 1 RT 29-1' UNION ALL
SELECT 'QW 1 RT 750-1' UNION ALL
SELECT 'QW RT 750-1'
)
SELECT title, [1] AS [Column 1], [2] AS [Column 2],[3] AS [Column 3],[4] AS [Column 4]
FROM TestTable CROSS APPLY dbo.GetNumbers(title)
PIVOT
(MAX(num) FOR idx IN ([1], [2],[3],[4])
) AS PivotTable;
Uses the following TVF
CREATE FUNCTION GetNumbers
(
#Numbers NVARCHAR(2000)
)
RETURNS #Results TABLE
(
idx INT IDENTITY(1,1),
num INT
)
AS
BEGIN
DECLARE #NonNumericIndex INT, #NumericIndex INT
SET #NumericIndex = PATINDEX('%[0-9]%',#Numbers)
IF (#NumericIndex > 4) --First Column not there
INSERT INTO #Results VALUES (NULL)
WHILE #NumericIndex > 0
BEGIN
SET #Numbers = RIGHT(#Numbers,LEN(#Numbers)-#NumericIndex+1)
SET #NonNumericIndex = PATINDEX('%[^0-9]%',#Numbers)
IF(#NonNumericIndex = 0)
BEGIN
INSERT
INTO #Results VALUES (#Numbers)
RETURN
END
ELSE
INSERT
INTO #Results VALUES
(LEFT(#Numbers,#NonNumericIndex-1))
SET #Numbers = RIGHT(#Numbers,LEN(#Numbers)-#NonNumericIndex+1)
SET #NumericIndex = PATINDEX('%[0-9]%',#Numbers)
END
RETURN
END

Related

How to get records that between m records back and n records forward from a reference row - Non-sequential data

My scenario is as follows:
I have a reference record, say, ProductId = 1
The records each have a non-unique ItemTypeId
I would like to fetch records that exists between the following points
START POINT being 2 records BACKWARDS of type ItemTypeId = 1, from record of ProductId =1
END POINT being 3 records FORWARDS of type ItemTypeId = 1, from record of ProductId = 1
The query should get ALL data between the two points, inclusively
Here's a picture that illustrates this better than my words:
How would I structure my query to do this?
Any better way to do it without temp tables?
Thank-you!
Note that for this to work at all, you need that record ID to be an actual column in the table. Rows have no inherent order in a table.
With that in place, you can use LAG and LEAD to get what you want:
CREATE TABLE #t
(
RecordId INT IDENTITY(1,1),
ProductId INT,
ItemType INT
);
INSERT INTO #t(ProductId, ItemType)
VALUES
(5,1),(3,1),(7,3),(6,1),(2,7),
(1,1),(7,3),(8,1),(10,3),(9,5),
(11,1),(19,1),(17,4),(13,3);
WITH c1 AS
(
SELECT ProductId,
RecordId,
LAG(RecordId,2) OVER (ORDER BY RecordId) AS Back2,
LEAD(RecordId,3) OVER (ORDER BY RecordId) AS Forward3
FROM #t
WHERE ItemType = (SELECT ItemType FROM #t WHERE ProductId = 1)
),c2 AS
(
SELECT c1.Back2, c1.Forward3 FROM c1
WHERE c1.ProductId = 1
)
SELECT #t.*
FROM #t
INNER JOIN c2 ON #t.RecordId BETWEEN c2.Back2 AND c2.Forward3;
If you wanna do without using temp tables as you ask, the following solution work.
But it is not very nice i agree.
Well this is what i done :
CREATE DATABASE TEST;
USE TEST
CREATE TABLE PRODUCT
(
ProductId INT,
ItemType INT
)
INSERT INTO PRODUCT
VALUES
(5,1),
(3,1),
(7,3),
(6,1),
(2,7),
(1,1),
(7,3),
(8,1),
(10,3),
(9,5),
(11,1),
(19,1),
(17,4),
(13,3)
DECLARE product_cursor CURSOR FOR
SELECT * FROM PRODUCT;
OPEN product_cursor
DECLARE
#ProductId INT,
#ItemId INT,
#END_FETCH INT,
#countFrom INT,
#countTo INT
DECLARE #TableResult TABLE
(
RProductId INT,
RItemId INT
)
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
SET #END_FETCH = 0
SET #countFrom = 0
SET #countTo = 0
WHILE ##FETCH_STATUS = 0 AND #END_FETCH = 0
BEGIN
IF #ItemId = 1 AND (#countFrom = 0 AND #countTo = 0)
BEGIN
SET #countFrom = 3
SET #countTo = 3
END
ELSE
BEGIN
IF #countFrom > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countFrom -= 1
--SELECT 'CountFrom : ', #countFrom
END
END
ELSE
BEGIN
IF #countTo > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countTo -= 1
--SELECT 'CountTO : ', #countTo
END
END
ELSE
BEGIN
SET #END_FETCH = 1
END
END
END
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
END
CLOSE product_cursor
DEALLOCATE product_cursor
SELECT * FROM #TableResult
And this is the result i got :
RProductId RItemId
3 1
7 3
6 1
2 7
1 1
7 3
8 1
10 3
9 5
11 1
19 1
But i prefer the solution of #James Casey.
By the way, why won't you use temp table ?

How to get only Capital letters from given value

I have a table it contains ID, Description and code columns. I need to fill code column using description column. Sample Description is "Investigations and Remedial Measures" so my code should be "IRM".
Note: Is there any words like "and/for/to/in" avoid it
This code may help you..
declare #input as varchar(1000) -- Choose the appropriate size
declare #output as varchar(1000) -- Choose the appropriate size
select #input = 'Investigations and Remedial Measures', #output = ''
declare #i int
select #i = 0
while #i < len(#input)
begin
select #i = #i + 1
select #output = #output + case when unicode(substring(#input, #i, 1))between 65
and 90 then substring(#input, #i, 1) else '' end
end
SELECT #output
Personally I would do this with an inline table-valued function
On SQL Server 2017 or better, or Azure SQL Database:
CREATE OR ALTER FUNCTION dbo.ExtractUpperCase(#s nvarchar(4000))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH s(s) AS (SELECT 1 UNION ALL SELECT s+1 FROM s WHERE s < LEN(#s))
SELECT TOP (3) value = STRING_AGG(SUBSTRING(#s,s,1),'')
WITHIN GROUP (ORDER BY s.s)
FROM s WHERE ASCII(SUBSTRING(#s,s,1)) BETWEEN 65 AND 90
);
GO
On SQL Server 2016 or older:
CREATE FUNCTION dbo.ExtractUpperCase(#s nvarchar(4000))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH s(s) AS (SELECT 1 UNION ALL SELECT s+1 FROM s WHERE s < LEN(#s))
SELECT value = (SELECT TOP (3) v = SUBSTRING(#s,s,1) FROM s
WHERE ASCII(SUBSTRING(#s,s,1)) BETWEEN 65 AND 90
ORDER BY s.s FOR XML PATH(''),
TYPE).value(N'./text()[1]',N'nvarchar(4000)')
);
GO
In either case:
CREATE TABLE #x(id int, name nvarchar(4000));
INSERT #x(id, name) VALUES
(1, N'Belo Horizonte Orange'),
(2, N'São Paulo Lala'),
(3, N'Ferraz de Vasconcelos Toranto');
SELECT id, f.value FROM #x AS x
CROSS APPLY dbo.ExtractUpperCase(x.name) AS f
ORDER BY id OPTION (MAXRECURSION 4000);
Results:
id name
---- ----
1 BHO
2 SPL
3 SVT
The OPTION (MAXRECURSION 4000) is only necessary if your strings can be longer than 100 characters.

How to select only armstrong numbers from the list?

I want is to select Armstrong numbers from the list below list I have searched of solution of this question bu unable to find in SQL-Server:
Numbers
121
113
423
153
541
371
I am sure most of you know what's the Armstrong number and how to calculate though I am describing is for the simplicity : sum of the cubes of its digits is equal to the number itself i.e.
1*1*1 + 5*5*5 + 3*3*3 = 153
3*3*3 + 7*7*7 + 1*1*1 = 371
Please help me on this as I am also trying but seeking for quick solution. It will be very helpful to me. Thanks in advance.
Obviously static processing during each query is not correct approach but we can create function like this and
create function dbo.IsArmstrongNumber(#n int)
returns int as
begin
declare #retValue int = 0
declare #sum int = 0
declare #num int = #n
while #num > 0
begin
set #sum += (#num%10) * (#num%10) * (#num%10)
set #num = #num/10
end
IF #sum = #n
set #retValue = 1
return #retValue
end
Pre-processing and selecting in IN clause is better
select * from #Numbers where dbo.IsArmstrongNumber(n) = 1
select 153 x into #temp;
insert #temp values(371);
insert #temp values(541);
with cte as (select x, substring(cast(x as nvarchar(40)) ,1,1) as u, 1 as N FROM #temp
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),3)) = x
drop table #temp;
here is the mark 2 - you can change the #ORDER to explore power of 4,5 etc
declare #order int = 3;
declare #limit int = 50000;
with nos as (select 1 no
union all
select no + 1 from nos where no < #limit),
cte as (select no as x, substring(cast(no as nvarchar(40)) ,1,1) as u, 1 as N FROM nos
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),#order)) = x
option (maxrecursion 0);
This is a quick mod to my sum of digits UDF
Declare #Table table (Numbers int)
Insert into #Table values
(121),
(113),
(423),
(153),
(541),
(371)
Select * from #Table where [dbo].[udf-Stat-Is-Armstrong](Numbers)=1
Returns
Numbers
153
371
The UDF
CREATE Function [dbo].[udf-Stat-Is-Armstrong](#Val bigint)
Returns Bit
As
Begin
Declare #RetVal as bigint
Declare #LenInp as bigint = len(cast(#Val as varchar(25)))
;with i AS (
Select #Val / 10 n, #Val % 10 d
Union ALL
Select n / 10, n % 10
From i
Where n > 0
)
Select #RetVal = IIF(SUM(power(d,#LenInp))=#Val,1,0) FROM i;
Return #RetVal
End
You can use the following to find Armstrong numbers using Sql functions:
WITH Numbers AS(
SELECT 0 AS number UNION ALL SELECT number + 1 FROM Numbers WHERE number < 10000)
SELECT number AS ArmstrongNumber FROM Numbers
WHERE
number = POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),1,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),2,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),3,1),0),3)
OPTION(MAXRECURSION 0)

Parsing Out Data in Varying Lengths to be used in a stored procedure to calculate a field

I have a database field with data like the examples below:
FEE 200 A 16 Y N NYFIRE 32.8 C M0008 Y N
INF 150 A 05 Y Y PFE 35 A 05 Y Y
NYFEE 200 A 16 Y N
I need to parse out all the values with an A before them, or any values that are
preceded with an INF. If they are preceded with an INF I need to use those values to deduct them from a formula in SQL. If the values are not preceded with an INF, but are followed by an A I need to add those values in a formula in a stored procedure. IMPORTANT, what looks like spaces in the examples are CHAR(9) characters. There could be up to 9 series of numbers that have an A before them... the above examples only show 1 or 2. I can extract out the first one using a charindex, but don't know how to get the subsequent A's out. I can't get a patindex to work with a '%[' + CHAR(9) + 'INF' + CHAR(9) + ']%', but if I do patindex on just 'INF' then I can't get just the following value with the 'INF' as part of the string, I don't want the 'INF' part.
I don't know how to parse out the values in a stored procedure, or at all (I've tried a number of things as mentioned above), and have not been able to find anything except how to parse out data on specific patterns. There is not always the same number of spaces for the value preceding the A, it could be 2 or 3 characters.
This will parse out the values. In SQL 2005/2008 you can do
DECLARE #Test table
(id int identity, field varchar(max))
INSERT INTO #Test (field)
Values('FEE 200 A 16 Y N NYFIRE 32.8 C M0008 Y N')
INSERT INTO #Test (field)
Values('INF 150 A 05 Y Y PFE 35 A 05 Y Y')
INSERT INTO #Test (field)
Values('NYFEE 200 A 16 Y N')
Update #Test set Field = REPLACE(Field,' ', CHAR(9))
;with cte
as
(
SELECT
id,
cast(0 as bigint) ind,
cast('' as varchar(max)) foo,
1 anchor
FROM
#Test
UNION ALL SELECT
n.id, CHARINDEX(Char(9),
n.field,
cte.ind+1) ind ,
SUBSTRING(field,cte.ind,CHARINDEX(Char(9),n.field, cte.ind+1) - cte.ind) Foo,
0 anchor
FROM
#Test n
INNER JOIN cte ON n.id = cte.id
WHERE
CHARINDEX(Char(9),n.field, cte.ind+1) <> 0
)
select *
from cte
where
anchor = 0
order by id, ind
You can either set up a loop (Which I don't recommend) or you can do the self joins to figure out what has an FEE and what has an INF and what values you want
select Fees.id, value.*
from cte Fees
INNER JOIN Cte a
ON Fees.id = a.id and a.foo like '%A%'
INNER JOIN Cte value
ON a.id = value.id and a.ind < value.ind
where
fees.anchor = 0
and fees.foo like '%FEE'
order by fees.id, fees.ind
If you're using 2000 you'll need to set up a split function and loop through your data and call it
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[Split]
(
#RowData VARCHAR(8000),
#Delimeter NVARCHAR(2000)
)
RETURNS #RtnValue TABLE
(
ID INT IDENTITY(1,1),
Data VARCHAR(8000)
)
AS
BEGIN
DECLARE #Iterator INT
SET #Iterator = 1
DECLARE #FoundIndex INT
SET #FoundIndex = CHARINDEX(#Delimeter,#RowData)
WHILE (#FoundIndex>0)
BEGIN
INSERT INTO #RtnValue (data)
SELECT
Data = LTRIM(RTRIM(SUBSTRING(#RowData, 1, #FoundIndex - 1)))
SET #RowData = SUBSTRING(#RowData,
#FoundIndex + DATALENGTH(#Delimeter) / 2,
LEN(#RowData))
SET #Iterator = #Iterator + 1
SET #FoundIndex = CHARINDEX(#Delimeter, #RowData)
END
INSERT INTO #RtnValue (Data)
SELECT Data = LTRIM(RTRIM(#RowData))
RETURN
END
Go
The split function is a modified for 2000 version of what is found here by Itai Goldstein
DECLARE #Test table
(id int identity, fieldValue varchar(2000))
INSERT INTO #Test (fieldValue)
Values('FEE 200 A 16 Y N NYFIRE 32.8 C M0008 Y N')
INSERT INTO #Test (fieldValue)
Values('INF 150 A 05 Y Y PFE 35 A 05 Y Y')
INSERT INTO #Test (fieldValue)
Values('NYFEE 200 A 16 Y N')
Update #Test set fieldValue = REPLACE(fieldValue,' ', CHAR(9))
DECLARE #fieldValue varchar(2000)
DECLARE #CurrentID int
SELECT #CurrentID = MIN(id)
FROM
#test
DECLARE #OUTPUT table
(id int , theOrder int, fieldValue varchar(2000))
DECLARE #rowData varchar(8000)
DECLARE #delimiter varchar(1)
SET #delimiter = char(9)
WHILE not #CurrentID IS NULL
BEGIN
SELECT #rowData = FieldValue FROM #Test Where id = #currentID
INSERT INTO #OUTPUT
SELECT
#currentID,
s.ID,
s.data
FROM
dbo.split(#rowdata, #delimiter) s
SELECT #CurrentID = MIN(id)
FROM
#test
WHERE
id > #CurrentID
END
SELECT * FROM #OUTPUT

Getting average from 3 columns in SQL Server

I have a table with 3 columns (smallint) in SQL Server 2005.
Table Ratings
ratin1 smallint,
ratin2 smallint
ratin3 smallint
These columns can have values from 0 to 5.
How can I select the average value of these fields, but only compare fields where the value is greater then 0.
So if the column values are 1, 3 ,5 - the average has to be 3.
if the values are 0, 3, 5 - The average has to be 4.
This is kind of quick and dirty, but it will work...
SELECT (ratin1 + ratin2 + ratin3) /
((CASE WHEN ratin1 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin2 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin3 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin1 = 0 AND ratin2 = 0 AND ratin3 = 0 THEN 1 ELSE 0 END) AS Average
#mwigdahl - this breaks if any of the values are NULL. Use the NVL (value, default) to avoid this:
Sum columns with null values in oracle
Edit: This only works in Oracle. In TSQL, try encapsulating each field with an ISNULL() statement.
There should be an aggregate average function for sql server.
http://msdn.microsoft.com/en-us/library/ms177677.aspx
This is trickier than it looks, but you can do this:
SELECT dbo.MyAvg(ratin1, ratin2, ratin3) from TableRatings
If you create this function first:
CREATE FUNCTION [dbo].[MyAvg]
(
#a int,
#b int,
#c int
)
RETURNS int
AS
BEGIN
DECLARE #result int
DECLARE #divisor int
SELECT #divisor = 3
IF #a = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #b = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #c = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #divisor = 0
SELECT #result = 0
ELSE
SELECT #result = (#a + #b + #c) / #divisor
RETURN #Result
END
select
(
select avg(v)
from (values (Ratin1), (Ratin2), (Ratin3)) as value(v)
) as average
You can use the AVG() function. This will get the average for a column. So, you could nest a SELECT statement with the AVG() methods and then SELECT these values.
Pseudo:
SELECT col1, col2, col3
FROM (
SELECT AVG(col1) AS col1, AVG(col2) AS col2, AVG(col3) AS col3
FROM table
) as tbl
WHERE col1 IN (0, 3, 5)
etc.