Related
This question already has answers here:
T-SQL split string
(27 answers)
Closed 4 years ago.
I have a column with three groups data delimited by a forward slash, like so
AB/1234/10
The column is always formatted the same in every row, with 2 characters, a slash, some number of characters, a slash, and then 2 more characters. I need to split this one column into three. So the above example becomes
Column1 Column2 Column3
AB 1234 10
I'm not quite sure how to go about this. I've been using SELECT SUBSTRING but that isn't quite giving me what I need.
select SUBSTRING(MyColumn, 1, CHARINDEX('/', MyColumn, 1)-1)
FROM MyTable
Will return AB, and that's great. But I can't wrap my mind around how to grab the middle and the end sections. I thought that
select SUBSTRING(MyColumn, 4, CHARINDEX('/', MyColumn, 4))
FROM MyTable
Would work in grabbing the middle, but it returns 1234/10
I hope my question is clear and I would appreciate any advice pointing me in the right direction, Thank you.
You can work with fixed offsets, since you defined that the string always starts with two, and ends with two characters.
Here is a full working example:
DECLARE #tmp TABLE (
Merged nvarchar(max)
)
INSERT INTO #tmp SELECT N'AB/1234/10'
INSERT INTO #tmp SELECT N'AB/ANYNUMBEROF-CHARACTERS/10'
SELECT
LEFT(Merged,2) AS Column1,
SUBSTRING(Merged,4,LEN(Merged)-6) AS Column2,
RIGHT(Merged,2) AS Column3
FROM #tmp
We subtract the length of the string minus a constant (6 = two chars left, two chars right, two slashes) to extract the variable-length part from the middle.
Result:
Column1 Column2 Column3
AB 1234 10
AB ANYNUMBEROF-CHARACTERS 10
One approach is to use PARSENAME:
SELECT PARSENAME(REPLACE('AB/1234/10','/','.'), 3) Col1,
PARSENAME(REPLACE('AB/1234/10','/','.'), 2) Col2,
PARSENAME(REPLACE('AB/1234/10','/','.'), 1) Col3
This will replace the / with ., and pull out each section of the string with PARSENAME.
The benefit is that it will work with any length of characters in any position. The limits is that PARSENAME only handles up to 4 positions (in this case you are using 3), and will fail if periods . exist in the string already.
Try this. This should work with any number of characters.
DECLARE #str VARCHAR(100) = 'AB/1234/10'
SELECT LEFT(#str, CHARINDEX('/', #str) - 1) AS Column1
, SUBSTRING(#str, CHARINDEX('/', #str) + 1, CHARINDEX('/', SUBSTRING(#str, CHARINDEX('/', #str) + 1, LEN(#str))) - 1) AS Column2
, RIGHT(#str, CHARINDEX('/', REVERSE(#str)) - 1) AS Column3
I need to do a select in a column that contains a query string like:
user_id=300&company_id=201503&status=WAITING OPERATION&count=1
I want to perform a select and break each value in a new column, something like:
user_id | company_id | status | count
300 | 201503 | WAITING OPERATION | 1
How can i do it in SQL Server without use procs?
I've tried a function:
CREATE FUNCTION [xpto].[SplitGriswold]
(
#List NVARCHAR(MAX),
#Delim1 NCHAR(1),
#Delim2 NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
SELECT
Val1 = PARSENAME(Value,2),
Val2 = PARSENAME(Value,1)
FROM
(
SELECT REPLACE(Value, #Delim2, '&') FROM
(
SELECT LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim1, #List + #Delim1, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim1 + #List, [Number], LEN(#Delim1)) = #Delim1
) AS y(Value)
) AS z(Value)
);
GO
Execution:
select QueryString
from User.Log
CROSS APPLY notifier.SplitGriswold(REPLACE(QueryString, ' ', N'ŏ'), N'ŏ', '&') AS t;
But it returns me only one column with all inside:
QueryString
user_id=300&company_id=201503&status=WAITING OPERATION&count=1
Thanks in advance.
I've had to do this many times before, and you're in luck! Since you only have 3 delimiters per string, and that number is fixed, you can use SQL Server's PARSENAME function to do it. That's far less ugly than the best alternative (using the XML parsing stuff). Try this (untested) query (replace TABLE_NAME and COLUMN_NAME with the appropriate names):
SELECT
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),1) AS 'User',
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),2) AS 'Company_ID',
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),3) AS 'Status',
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),4) AS 'Count',
FROM TABLE_NAME
That'll get you the results in the form "user_id=300", which is far and away the hard part of what you want. I'll leave it to you to do the easy part (drop the stuff before the "=" sign).
NOTE: I can't remember if PARSENAME will freak out over the illegal name character (the "=" sign). If it does, simply nest another REPLACE in there to turn it into something else, like an underscore.
You need to use SQL SUBSTRING as part of your select statement. You would first need to build the first row, then use a UNION to return the second row.
Problem Statement
Example is shown in below image :
The last 2 rows have the patterns like "1.283 2 3" in a single cell. The numbers are seperated by space in the column. We need to add those nos and represent in the format given in Output.
So, the cell having "1.283 2 3" must be converted to 6.283
Challenges facing :
The column values are in string format.
Add nos after casting them into integer
Donot want to take data in UNIX box and manipulate the same.
In TD14 there would be a built-in table UDF named STRTOK_SPLIT_TO_TABLE, before you need to implement your own UDF or use a recursive query.
I modified an existing string splitting script to use blanks as delimiter:
CREATE VOLATILE TABLE Strings
(
groupcol INT NOT NULL,
string VARCHAR(991) NOT NULL
) ON COMMIT PRESERVE ROWS;
INSERT INTO Strings VALUES (1,'71.792');
INSERT INTO Strings VALUES (2,'71.792 1 2');
INSERT INTO Strings VALUES (3,'1.283 2 3');
WITH RECURSIVE cte
(groupcol,
--string,
len,
remaining,
word,
pos
) AS (
SELECT
GroupCol,
--String,
POSITION(' ' IN String || ' ') - 1 AS len,
TRIM(LEADING FROM SUBSTRING(String || ' ' FROM len + 2)) AS remaining,
TRIM(SUBSTRING(String FROM 1 FOR len)) AS word,
1
FROM strings
UNION ALL
SELECT
GroupCol,
--String,
POSITION(' ' IN remaining)- 1 AS len_new,
TRIM(LEADING FROM SUBSTRING(remaining FROM len_new + 2)),
TRIM(SUBSTRING(remaining FROM 1 FOR len_new)),
pos + 1
FROM cte
WHERE remaining <> ''
)
SELECT
groupcol,
-- remove the NULLIF to get 0 for blank strings
SUM(CAST(NULLIF(word, '') AS DECIMAL(18,3)))
FROM cte
GROUP BY 1
This might use a lot of spool, hopefully you're not running that on a large table.
I have a one database table field called Amount which type is decimal(18,6). so it is stored in database up to 6 decimal points like 9.786534 But while retrieving that field using select query i have to take care like following
Remove trialling zero e.g if number is 9.230000 then result is only 9.23
If decimal points are all zero then only remove only four trialling zero e.g If number is 9.000000 then result is 9.00
Result is up to 2 decimal point if there are trialling zero.
If we write simple query like
select TOP 1 Amount From EmployeeMaster
then it gives 9.230000
but my intension is to remove trailing zero..
Please help me..
It works for removing trailing zeros, but I am still not able to convert 9 to 9.00 in this method.
Declare #myvalue varchar(50),
#Price Varchar(50)
Set #Price = '9.230000'
set #Myvalue = reverse(substring(#Price,patindex('%.%',#Price)+1,len(#Price)))
SELECT
case
When patindex('%.%[1-9]%',#price) = 0 Then
substring(#price,1,patindex('%.%',#price)-1)
else
substring(#price,1,patindex('%.%',#price)-1) + '.' + Reverse(substring(#Myvalue,patindex('%[1-9]%',#Myvalue),len(#Myvalue)))
END
Coming from decimal(18,6) you could do...
select cast(Amount as decimal(18,2))
Most databases that support the CAST function will round the number while converting it. On SQLServer this is what I would do if I wanted rounding.
If what you actually want is a string with only two digits after the decimal then you could
select cast((Amount as decimal(18,2)) as nvarchar)
nvarchar is SQLServer's variable length unicode type. Databases do not agree much on string types. Your database may have a different one. The rest of that sql is ANSI standard. Not all dbs support that either but many do.
This should work
SELECT CAST(REPLACE(RTRIM(REPLACE(CAST(CAST(33.9082976 AS DECIMAL(38,8)) AS NVARCHAR(256)),'0',' ')),' ','0') AS FLOAT)
Does this work?
select TOP 1 ROUND(Amount, 2) From EmployeeMaster
TRY below mentioned code.
SELECT TOP 1 CONVERT(DECIMAL(10,2),Amount) From EmployeeMaster
Hope it will work as expected.
An alternative approach:
1) convert the decimal to a string;
2) split the string into 2 parts, separating the last 4 characters from the rest of the string;
3) remove trailing zeros from the last 4 characters;
4) concatenate the two parts back.
WITH data (V) AS (SELECT CAST(9.786534 AS decimal(18,6))
UNION ALL
SELECT CAST(9.78653 AS decimal(18,6))
UNION ALL
SELECT CAST(9.7800 AS decimal(18,6))
UNION ALL
SELECT CAST(9.7 AS decimal(18,6))
UNION ALL
SELECT CAST(9.00000 AS decimal(18,6))
)
, AsString (V) AS (SELECT CAST(V AS varchar) FROM data)
, Split (L, R) AS (SELECT LEFT(V, LEN(V) - 4), RIGHT(V, 4) FROM AsString)
, Adjusted AS (SELECT L,
REPLACE(RTRIM(REPLACE(R, '0', ' ')), ' ', '0') AS R
FROM Split)
SELECT Result = L + R FROM Adjusted
The output of the above script is:
Result
--------
9.786534
9.78653
9.78
9.70
9.00
I guess using patindex in your case:
CASE WHEN FLOOR(Amount) <> CEILING(Amount) THEN
LTRIM(SUBSTRING(STR(Amount, 18, 6), 1, LEN(STR(Amount, 18, 6)) - PATINDEX('%[^0]%', REVERSE(str(Amount, 18, 6))) + 1))
ELSE STR(Amount,18,2)
END
for a decimal(18,6) field this should work:
select trim(to_char(Amount, '999999999999999999.99')) from EmployeeMaster
(at least for Oracle, not sure about other types)
I've got a column in a database table (SQL Server 2005) that contains data like this:
TQ7394
SZ910284
T r1534
su8472
I would like to update this column so that the first two characters are uppercase. I would also like to remove any spaces between the first two characters. So T q1234 would become TQ1234.
The solution should be able to cope with multiple spaces between the first two characters.
Is this possible in T-SQL? How about in ANSI-92? I'm always interested in seeing how this is done in other db's too, so feel free to post answers for PostgreSQL, MySQL, et al.
Here is a solution:
EDIT: Updated to support replacement of multiple spaces between the first and the second non-space characters
/* TEST TABLE */
DECLARE #T AS TABLE(code Varchar(20))
INSERT INTO #T SELECT 'ab1234x1' UNION SELECT ' ab1234x2'
UNION SELECT ' ab1234x3' UNION SELECT 'a b1234x4'
UNION SELECT 'a b1234x5' UNION SELECT 'a b1234x6'
UNION SELECT 'ab 1234x7' UNION SELECT 'ab 1234x8'
SELECT * FROM #T
/* INPUT
code
--------------------
ab1234x3
ab1234x2
a b1234x6
a b1234x5
a b1234x4
ab 1234x8
ab 1234x7
ab1234x1
*/
/* START PROCESSING SECTION */
DECLARE #s Varchar(20)
DECLARE #firstChar INT
DECLARE #secondChar INT
UPDATE #T SET
#firstChar = PATINDEX('%[^ ]%',code)
,#secondChar = #firstChar + PATINDEX('%[^ ]%', STUFF(code,1, #firstChar,'' ) )
,#s = STUFF(
code,
1,
#secondChar,
REPLACE(LEFT(code,
#secondChar
),' ','')
)
,#s = STUFF(
#s,
1,
2,
UPPER(LEFT(#s,2))
)
,code = #s
/* END PROCESSING SECTION */
SELECT * FROM #T
/* OUTPUT
code
--------------------
AB1234x3
AB1234x2
AB1234x6
AB1234x5
AB1234x4
AB 1234x8
AB 1234x7
AB1234x1
*/
UPDATE YourTable
SET YourColumn = UPPER(
SUBSTRING(
REPLACE(YourColumn, ' ', ''), 1, 2
)
)
+
SUBSTRING(YourColumn, 3, LEN(YourColumn))
UPPER isn't going to hurt any numbers, so if the examples you gave are completely representative, there's not really any harm in doing:
UPDATE tbl
SET col = REPLACE(UPPER(col), ' ', '')
The sample data only has spaces and lowercase letters at the start. If this holds true for the real data then simply:
UPPER(REPLACE(YourColumn, ' ', ''))
For a more specific answer I'd politely ask you to expand on your spec, otherwise I'd have to code around all the other possibilities (e.g. values of less than three characters) without knowing if I was overengineering my solution to handle data that wouldn't actually arise in reality :)
As ever, once you've fixed the data, put in a database constraint to ensure the bad data does not reoccur e.g.
ALTER TABLE YourTable ADD
CONSTRAINT YourColumn__char_pos_1_uppercase_letter
CHECK (ASCII(SUBSTRING(YourColumn, 1, 1)) BETWEEN ASCII('A') AND ASCII('Z'));
ALTER TABLE YourTable ADD
CONSTRAINT YourColumn__char_pos_2_uppercase_letter
CHECK (ASCII(SUBSTRING(YourColumn, 2, 1)) BETWEEN ASCII('A') AND ASCII('Z'));
#huo73: yours doesn't work for me on SQL Server 2008: I get 'TRr1534' instead of 'TR1534'.
update Table set Column = case when len(rtrim(substring (Column , 1 , 2))) < 2
then UPPER(substring (Column , 1 , 1) + substring (Column , 3 , 1)) + substring(Column , 4, len(Column)
else UPPER(substring (Column , 1 , 2)) + substring(Column , 3, len(Column) end
This works on the fact that if there is a space then the trim of that part of string would yield length less than 2 so we split the string in three and use upper on the 1st and 3rd char. In all other cases we can split the string in 2 parts and use upper to make the first two chars to upper case.
If you are doing an UPDATE, I would do it in 2 steps; first get rid of the space (RTRIM on a SUBSTRING), and second do the UPPER on the first 2 chars:
// uses a fixed column length - 20-odd in this case
UPDATE FOO
SET bar = RTRIM(SUBSTRING(bar, 1, 2)) + SUBSTRING(bar, 3, 20)
UPDATE FOO
SET bar = UPPER(SUBSTRING(bar, 1, 2)) + SUBSTRING(bar, 3, 20)
If you need it in a SELECT (i.e. inline), then I'd be tempted to write a scalar UDF