Extract Min Date from a string with several dates using SQL Server - sql-server

I am trying to extract the min date from a varchar string.
The data in the field looks like this
QTY DIFFERENCE - PO LINE 6. 147 ON PO / 192 ON INVOICE
5/18/2016 4:18:52 PM by ROOFING\ebuchanan
ANDREW SANTORI ISSUED THIS PO, PLEASE SEND TO HIS QUE
5/21/2016 9:48:42 AM by ROOFING\knaylor
RE-ROUTED TO ATS
Using this code
SELECT
UISeq,
LEFT(SUBSTRING(Notes, PATINDEX('%[0-9/]%', Notes), 8000),
PATINDEX('%[^0-9/]%', SUBSTRING(Notes, PATINDEX('%[0-9/]%', Notes), 8000) + 'X') -1) as 'MaxDate'
FROM
bAPUI
WHERE
Notes IS NOT NULL
ORDER BY
UISeq
I get this result from the record above
6
I also get
01/01/2000
On other fields
How do I correct the code to only return the Min date within each record field?
UISeq MinDate
2 3
3 5
13 4/1/2016
15 1
17
18 4/15/2016
19 3
20 4/15/2016
40 05/22/16
43 05/22/16
54 5/18/16
John's post is beyond my current ability
I have created the function, here is the code to extract the data
Declare #Str varchar(max);
Select #Str as Notes, Min(Key_Value)
from bAPUI, [dbo].[SA-udf-Str-Parse](replace(#Str,char(13),' '),' ')
Where Key_Value like '%/%'
and len(Key_Value)>=10
What I am not understanding is how to get the bAPUI.Notes table/field into the select statement.

The following uses a string parser udf. Perhaps in your data, or even just in the example, there were chr(13)'s, so I had to perform a replace(), there could be other extended characters that may need to be trapped.
Declare #Str varchar(max)
Set #Str='QTY DIFFERENCE - PO LINE 6. 147 ON PO / 192 ON INVOICE
5/18/2016 4:18:52 PM by ROOFING\ebuchanan
ANDREW SANTORI ISSUED THIS PO, PLEASE SEND TO HIS QUE
5/21/2016 9:48:42 AM by ROOFING\knaylor
RE-ROUTED TO ATS'
Select * from [dbo].[udf-Str-Parse](replace(#Str,char(13),' '),' ')
Where Key_Value like '%/%'
and len(Key_Value)>=10
Returns
Key_PS Key_Value
13 5/18/2016
28 5/21/2016
While with a quick change
Select Min(Key_Value) from [dbo].[udf-Str-Parse](replace(#Str,char(13),' '),' ')
Where Key_Value like '%/%'
and len(Key_Value)>=10
Returns
5/18/2016
There are millions of variations but here is mine.
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
-- Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1) NOT NULL , Key_Value varchar(500))
As
Begin
Declare #intPos int,#SubStr varchar(500)
Set #IntPos = CharIndex(#delimeter, #String)
Set #String = Replace(#String,#delimeter+#delimeter,#delimeter)
While #IntPos > 0
Begin
Set #SubStr = Substring(#String, 0, #IntPos)
Insert into #ReturnTable (Key_Value) values (#SubStr)
Set #String = Replace(#String, #SubStr + #delimeter, '')
Set #IntPos = CharIndex(#delimeter, #String)
End
Insert into #ReturnTable (Key_Value) values (#String)
Return
End
So to apply to your data
Select UISeq,
,MinDate=(Select Min(Key_Value) from [dbo].[udf-Str-Parse](replace(Notes,char(13),' '),' ') Where Key_Value like '%/%' and len(Key_Value)>=10)
FROM bAPUI
WHERE Notes IS NOT NULL
ORDER BYUISeq
I have no idea how this will perform on a large dataset

Super quick draft - Use CHARINDEX And LEFT to retrieve all characters up to the first space, then convert that text to a DATE, then use MIN to select the EARLIEST date.
select #str as string
,left(#str,CHARINDEX(' ',#str)) -- Get the position of the first space, then select all characters up to the space
,MIN(convert(date,left(#str,CHARINDEX(' ',#str)))) -- Convert the selected characters to a date and then use MIN to select earliest date

Related

Can We evaluate a mathematical Expression to column values obtained by replacing strings with mathematical symbols?

I am making a new column which will became a mathematical expression after replacing the Days/Hours/Minutes with asterisk(*) and Plus(+).
I am using below sql code to make a mathematical expression column :
SELECT replace(replace(replace(VAL,'days','*24 +'),'Hours','*'),'Minutes','/60') as Col from #t)
Example the string ('5 days 12 minutes')will became 5 *24 + 12 /60 after replacing, Now is there any solution that I can evaluate this expression i.e will get result 120 .
One way is to use sp_executesql
DECLARE #testString nvarchar(256);
DECLARE #replacement nvarchar(256);
SET #testString = '5 days 12 minutes';
SET #replacement = (SELECT replace(replace(replace(#testString,'days','*24 +'),'Hours','*'),'Minutes','/60'));
DECLARE #statement nvarchar(256);
SET #statement = N'SELECT ' + #replacement;
EXECUTE sp_executesql #statement;
If you want a set-based approach (usable in VIEWs and functions or directly within a query) you might try something like this:
DECLARE #mockup TABLE(ID INT IDENTITY, SomeString VARCHAR(100));
INSERT INTO #mockup VALUES('5 days 12 minutes')
,('3 days 2 hours 3 minutes');
WITH Splitted AS
(
SELECT m.ID
,m.SomeString
,CAST('<x>' + REPLACE(SomeString,' ','</x><x>') + '</x>' AS XML) AS Casted
FROM #mockup AS m
)
,oddNumbers AS (SELECT Nr FROM (VALUES(1),(3),(5),(7),(9),(11)) AS A(Nr))
,Params AS
(
SELECT s.ID
,s.SomeString
,s.Casted.value('(/x[sql:column("n.Nr")])[1]','decimal(10,4)') AS TheVal
,s.Casted.value('(/x[sql:column("n.Nr")+1])[1]','nvarchar(max)') AS TheUnit
FROM Splitted AS s
CROSS JOIN oddNumbers AS n
WHERE s.Casted.value('(/x[sql:column("n.Nr")])[1]','nvarchar(max)') IS NOT NULL
)
SELECT p.ID
,p.SomeString
,SUM(TheVal * CASE TheUnit WHEN 'days' THEN 24.0
WHEN 'hours' THEN 1.0
WHEN 'Minutes' THEN (1.0/60.0) END) AS Agg
FROM Params AS p
GROUP BY p.ID,p.SomeString;
Assumption: Always number-unit separated by exactly one blank
The query will split your values to get the numbers together with ther associated unit.

select a particular string from a semi-colon delimited list [duplicate]

This question already has answers here:
Using T-SQL, return nth delimited element from a string
(14 answers)
Closed 6 years ago.
I want to extract a string which has semi-colon as a delimiter. I tried using Substring, Charindex and Left function. But I'm not able to get the desired result. Below is my select statement. Output result must be "Unsure how to perform task. Meter read 10 in office before testing". Thanks
Declare #string Varchar(max)='Sampling:45;Traveling:30;CalibratedNo;uncalibratedReason:: ' +
'Unsure how to perform task. Meter read 10 in office before ' +
'testing.;pH1:6.5;pH2:6.5;Dis.Oxygen1:7.4'
Select SubString(#string, (CHARINDEX('uncalibratedReason:', #string, 0) + 19),
(CharIndex('uncalibratedReason:', LEFT(#string, (LEN(#string) -
(CharIndex(';', #string, 0)))), 0) - 0)) As New
Try it like this:
Declare #string Varchar(max) = 'Sampling:45;Traveling:30;CalibratedNo;uncalibratedReason:Unsure how to perform task. Meter read 10 in office before testing.;pH1:6.5;pH2:6.5;Dis.Oxygen1:7.4';
SELECT CAST('<x>' + REPLACE(#string,';','</x><x>') + '</x>' AS XML).value('x[4]','nvarchar(max)')
The result is:
uncalibratedReason:Unsure how to perform task. Meter read 10 in office before testing.
You can take away the leading uncalibratedReason: simply with SUBSTRING and CHARINDEX looking for : if you need this.
UPDATE
Here is the full code:
DECLARE #result NVARCHAR(MAX)=
(SELECT CAST('<x>' + REPLACE(#string,';','</x><x>') + '</x>' AS XML).value('x[4]','nvarchar(max)'));
SELECT SUBSTRING(#result,CHARINDEX(':',#result)+1,10000)
UPDATE 2: Find position by starting string
DECLARE #result NVARCHAR(MAX)=
(SELECT CAST('<x>' + REPLACE(#string,';','</x><x>') + '</x>' AS XML).value('(x[substring(.,1,string-length("uncalibratedReason:")) eq "uncalibratedReason:"])[1]','nvarchar(max)'));
SELECT SUBSTRING(#result,CHARINDEX(':',#result)+1,10000)
UPDATE 3 The ultimative solution :-)
Declare #string Varchar(max) = 'Sampling:45;Traveling:30;CalibratedNo;uncalibratedReason:Unsure how to perform task. Meter read 10 in office before testing.;pH1:6.5;pH2:6.5;Dis.Oxygen1:7.4';
WITH Casted(ThePart) AS
(
SELECT Node.value('.','nvarchar(max)')
FROM
(
SELECT CAST('<x>' + REPLACE(#string,';','</x><x>') + '</x>' AS XML)
) AS tbl(AsXML)
CROSS APPLY AsXML.nodes('/x') AS The(Node)
)
,Splitted(SpecificPart) AS
(
SELECT CAST('<x>' + REPLACE(ThePart,':','</x><x>') + '</x>' AS XML)
FROM Casted
)
SELECT SpecificPart.value('x[1]','nvarchar(max)') AS Caption
,SpecificPart.value('x[2]','nvarchar(max)') AS Data
FROM Splitted
The result
Caption Data
CalibratedNo NULL
Dis.Oxygen1 7.4
pH1 6.5
pH2 6.5
Sampling 45
Traveling 30
uncalibratedReason Unsure how to perform task. Meter read 10 in office before testing.
Shnugo anwser very cool. (UpVote)
However, this UDF Parser returns the sequence and value
Declare #string Varchar(max)='Sampling:45;Traveling:30;CalibratedNo;uncalibratedReason:: ' +
'Unsure how to perform task. Meter read 10 in office before ' +
'testing.;pH1:6.5;pH2:6.5;Dis.Oxygen1:7.4'
Select * from [dbo].[udf-Str-Parse](#String,';')
--Where Key_PS = 5
--Where Key_Value Like '%:%'
--Where Key_Value Like 'pH1%'
Returns
Key_PS Key_Value
1 Sampling:45
2 Traveling:30
3 CalibratedNo
4 uncalibratedReason:: Unsure how to perform task. Meter read 10 in office before testing.
5 pH1:6.5
6 pH2:6.5
7 Dis.Oxygen1:7.4
The UDF
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1), Key_Value varchar(max))
As
Begin
Declare #XML xml;Set #XML = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #ReturnTable Select ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM #XML.nodes('x') as T(String)
Return
End

T-SQL - Update first letter in each word of a string that are not 'or', 'of' or 'and' to uppercase. Lowercase 'or', 'of' or 'and' if found

Given the below table and data:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
Code INT,
PDescription VARCHAR(2000)
)
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001,'c and d, together'),
(2,0002,'equals or Exceeds $27.00'),
(3,0003,'Fruit Evaporating Or preserving'),
(4,0004,'Domestics And domestic Maintenance'),
(5,0005,'Bakeries and cracker')
SELECT *
FROM #Temp
DROP TABLE #Temp
Output:
ID Code PDescription
1 1 c and d, together
2 2 equals or Exceeds $27.00
3 3 Fruit Evaporating Or preserving
4 4 Domestics And domestic Maintenance
5 5 Bakeries and cracker
I need a way to achieve the below update to the description field:
ID Code PDescription
1 1 C and D, Together
2 2 Equals or Exceeds $27.00
3 3 Fruit Evaporating or Preserving
4 4 Domestics and Domestic Maintenance
5 5 Bakeries and Cracker
If you fancied going the SQL CLR route the function could look something like
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
public partial class UserDefinedFunctions
{
//One or more "word characters" or apostrophes
private static readonly Regex _regex = new Regex("[\\w']+");
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString ProperCase(SqlString subjectString)
{
string resultString = null;
if (!subjectString.IsNull)
{
resultString = _regex.Replace(subjectString.ToString().ToLowerInvariant(),
(Match match) =>
{
var word = match.Value;
switch (word)
{
case "or":
case "of":
case "and":
return word;
default:
return char.ToUpper(word[0]) + word.Substring(1);
}
});
}
return new SqlString(resultString);
}
}
Doubtless there may be Globalization issues in the above but it should do the job for English text.
You could also investigate TextInfo.ToTitleCase but that still leaves you needing to handle your exceptions.
The following function is not the most elegant of solutions but should do what you want.
ALTER FUNCTION [dbo].[ToProperCase](#textValue AS NVARCHAR(2000))
RETURNS NVARCHAR(2000)
AS
BEGIN
DECLARE #reset BIT;
DECLARE #properCase NVARCHAR(2000);
DECLARE #index INT;
DECLARE #character NCHAR(1);
SELECT #reset = 1, #index=1, #properCase = '';
WHILE (#index <= len(#textValue))
BEGIN
SELECT #character= substring(#textValue,#index,1),
#properCase = #properCase + CASE WHEN #reset=1 THEN UPPER(#character) ELSE LOWER(#character) END,
#reset = CASE WHEN #character LIKE N'[a-zA-Z\'']' THEN 0 ELSE 1 END,
#index = #index +1
END
SET #properCase = N' ' + #properCase + N' ';
SET #properCase = REPLACE(#properCase, N' And ', N' and ');
SET #properCase = REPLACE(#properCase, N' Or ', N' or ');
SET #properCase = REPLACE(#properCase, N' Of ', N' of ');
RETURN RTRIM(LTRIM(#properCase))
END
Example use:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
Code INT,
PDescription VARCHAR(2000)
)
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001, N'c and d, together and'),
(2,0002, N'equals or Exceeds $27.00'),
(3,0003, N'Fruit Evaporating Or preserving'),
(4,0004, N'Domestics And domestic Maintenance'),
(5,0005, N'Bakeries and cracker')
SELECT ID, Code, dbo.ToProperCase(PDescription) AS [Desc]
FROM #Temp
DROP TABLE #Temp
If you want to convert your text to proper case before inserting into table, then simply call function as follow:
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001, dbo.ToProperCase( N'c and d, together and')),
(2,0002, dbo.ToProperCase( N'equals or Exceeds $27.00')),
(3,0003, dbo.ToProperCase( N'Fruit Evaporating Or preserving')),
(4,0004, dbo.ToProperCase( N'Domestics And domestic Maintenance')),
(5,0005, dbo.ToProperCase( N'Bakeries and cracker'))
This is a dramatically modified version of my Proper UDF. The good news is you may be able to process the entire data-set in ONE SHOT rather than linear.
Take note of #OverR (override)
Declare #Table table (ID int,Code int,PDescription varchar(150))
Insert into #Table values
(1,1,'c and d, together'),
(2,2,'equals or Exceeds $27.00'),
(3,3,'Fruit Evaporating Or preserving'),
(4,4,'Domestics And domestic Maintenance'),
(5,5,'Bakeries and cracker')
-- Generate Base Mapping Table - Can be an Actual Table
Declare #Pattn table (Key_Value varchar(25));Insert into #Pattn values (' '),('-'),('_'),(','),('.'),('&'),('#'),(' Mc'),(' O''') -- ,(' Mac')
Declare #Alpha table (Key_Value varchar(25));Insert Into #Alpha values ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P'),('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('X')
Declare #OverR table (Key_Value varchar(25));Insert Into #OverR values (' and '),(' or '),(' of ')
Declare #Map Table (MapSeq int,MapFrom varchar(25),MapTo varchar(25))
Insert Into #Map
Select MapSeq=1,MapFrom=A.Key_Value+B.Key_Value,MapTo=A.Key_Value+B.Key_Value From #Pattn A Join #Alpha B on 1=1
Union All
Select MapSeq=99,MapFrom=A.Key_Value,MapTo=A.Key_Value From #OverR A
-- Convert Base Data Into XML
Declare #XML xml
Set #XML = (Select KeyID=ID,String=+' '+lower(PDescription)+' ' from #Table For XML RAW)
-- Convert XML to varchar(max) for Global Search & Replace
Declare #String varchar(max)
Select #String = cast(#XML as varchar(max))
Select #String = Replace(#String,MapFrom,MapTo) From #Map Order by MapSeq
-- Convert Back to XML
Select #XML = cast(#String as XML)
-- Generate Final Results
Select KeyID = t.col.value('#KeyID', 'int')
,NewString = ltrim(rtrim(t.col.value('#String', 'varchar(150)')))
From #XML.nodes('/row') AS t (col)
Order By 1
Returns
KeyID NewString
1 C and D, Together
2 Equals or Exceeds $27.00
3 Fruit Evaporating or Preserving
4 Domestics and Domestic Maintenance
5 Bakeries and Cracker
You don't even need functions and temporary objects. Take a look at this query:
WITH Processor AS
(
SELECT ID, Code, 1 step,
CONVERT(nvarchar(MAX),'') done,
LEFT(PDescription, CHARINDEX(' ', PDescription, 0)-1) process,
SUBSTRING(PDescription, CHARINDEX(' ', PDescription, 0)+1, LEN(PDescription)) waiting
FROM #temp
UNION ALL
SELECT ID, Code, step+1,
done+' '+CASE WHEN process IN ('and', 'or', 'of') THEN LOWER(process) ELSE UPPER(LEFT(process, 1))+LOWER(SUBSTRING(process, 2, LEN(process))) END,
CASE WHEN CHARINDEX(' ', waiting, 0)>0 THEN LEFT(waiting, CHARINDEX(' ', waiting, 0)-1) ELSE waiting END,
CASE WHEN CHARINDEX(' ', waiting, 0)>0 THEN SUBSTRING(waiting, CHARINDEX(' ', waiting, 0)+1, LEN(waiting)) ELSE NULL END FROM Processor
WHERE process IS NOT NULL
)
SELECT ID, Code, done PSDescription FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY step DESC) RowNum FROM Processor
) Ordered
WHERE RowNum=1
ORDER BY ID
It produces desired result as well. You can SELECT * FROM Processor to see all steps executed.

Retrieving numerical values from a Nvarchar column

I have a table with a nvarchar column called Custom#5 which has the following data.
row number Custom#5
1 267.5
2 tbc
3
4 34
I want to be able to clean this data up so always returns a numerical value.
row number Custom#5
1 267.5
2 0
3 0
4 34
My current query is;
SELECT CASE
WHEN BomHeaders_1.Custom#5 NOT LIKE '%[^0-9]%'
THEN 0
WHEN BomHeaders_1.Custom#5 IS NULL
THEN 0
ELSE BomHeaders_1.Custom#5
END AS Custom5
FROM [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomHeaders AS BomHeaders_1
INNER JOIN [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomComponents AS BomComponents_1 ON BomHeaders_1.ID = BomComponents_1.HeaderID
INNER JOIN [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomHeaders AS BomHeaders_2 ON BomComponents_1.StockCode = BomHeaders_2.BomReference
INNER JOIN manu_STOCK ON BomHeaders_1.BomReference = manu_STOCK.STOCK_CODE
WHERE (BomComponents_1.StockCode LIKE N'21%')
The current error i'm getting with this is
"Conversion failed when converting the nvarchar value '267.5' to data type int."
If you are using 2012 or greater, I would use the tryparse function
select coalesce(TRY_PARSE ( [Custom#5] AS decimal(18,2)),0)
declare #t table (R INT,C varchar(10))
inSERT INTO #t(R,c)values (1,'267.5'),(2,'tbc'),(3,''),(4,'34')
select R,CASE WHEN C LIKE '%[^a-zA-Z]%' THEN C ELSE CAST(0 AS VARCHAR) END from #t
If your using SQL Server < 2012 and have no option in using try_parse, you can use this:
DECLARE #string nvarchar(255)
SET #string = 'Hali891236.5€hHalo'
SELECT Substring(
#string,
PATINDEX('%[0-9.]%',#string),
PATINDEX('%[^0-9.]%',
Substring(
#string,
PATINDEX(
'%[0-9.]%',
#string
),
LEN(#string)
)
)-1
)
GO

Is CHAR(14) not allowed in SQL Server T-SQL patindex range?

What's the problem with CHAR(13) or perhaps CHAR(14) in TSQL patindex?
As soon as I include CHAR(14) in a pattern, I get no records found.
Searching for an answer, I just found my own question (unanswered) from 2009 (here: http://www.sqlservercentral.com/Forums/Topic795063-338-1.aspx).
Here is another simple test, to show what I mean:
/* PATINDEX TEST */
DECLARE #msg NVARCHAR(255)
SET #msg = 'ABC' + NCHAR(13) + NCHAR(9) + 'DEF'
DECLARE #unwanted NVARCHAR(50)
-- unwanted chars in a "chopped up" string
SET #unwanted = N'%[' + NCHAR(1) + '-' + NCHAR(13) + NCHAR(14) + '-' + NCHAR(31) + ']%'
SELECT patindex(#unwanted, #msg)
-- Result: 4
-- NOW LET THE unwanted string includ the whole range from 1 to 31
SET #unwanted = '%['+NCHAR(1)+'-'+NCHAR(31)+']%' -- -- As soon as Char(14) is included, we get no match with patindex!
SELECT patindex(#unwanted, #msg)
-- Result: 0
It is permitted.
You need to bear in mind that the ranges are based on collation sort order not character codes however so perhaps in your default collation it sorts in a position that you do not expect.
What is your database's default collation?
What does the following return?
;WITH CTE(N) AS
(
SELECT 1 UNION ALL
SELECT 9 UNION ALL
SELECT 13 UNION ALL
SELECT 14 UNION ALL
SELECT 31
)
SELECT N
FROM CTE
ORDER BY NCHAR(N)
For me it returns
N
-----------
1
14
31
9
13
So both characters 9 and 13 are outside the range 1-31. Hence
'ABC' + NCHAR(13) + NCHAR(9) + 'DEF' NOT LIKE N'%['+NCHAR(1)+N'-'+NCHAR(31)+N']%'
Which explains the results in your question. Character 14 doesn't enter into it.
You can use a binary collate clause to get it to sort more as you were expecting. e.g.
SELECT patindex(#unwanted COLLATE Latin1_General_100_BIN, #msg)
Returns 4 in the second query too.

Resources