When concatenating using COALESCE, number more than 9 displays as asterisk * - sql

I want to concatenate values from multiple rows into one. I am using COALESCE for this purpose. One of the columns I have is an ID column. When concatenating ID column, values up to 9 are displayed correctly but after nine, asterisk is displayed. Anyone knows why this is? See my code below using COALESCE to concatenate all rows in one:
CREATE TABLE #test
(id int, name varchar(50))
insert into #test
values(1, 'ana'),
(2, 'bob'),
(3, 'steph'),
(4, 'bill'),
(5, 'john'),
(6, 'jose'),
(7, 'kerry'),
(8, 'frank'),
(9, 'noah'),
(10, 'melissa')
--SELECT * FROM #test
DECLARE #NameAndID VARCHAR(1000)
SELECT #NameAndID = COALESCE(#NameAndID +'; ', '') + CAST(ID AS VARCHAR(1))+'. ' + name
FROM #test
SELECT #NameAndID

You are casting the number to varchar(1) - and any number that have more than a single digit will overflow the one char and therefor will be turned into an asterisks (*).
When casting ints, I find it best to use varchar(11), since this covers the maximum amount of chars that might be needed to display an int.
The int minimum value is -2,147,483,648 - removing the thousands separators it's 10 digits and a minus sign:
-2147483648
123456789 1 (10 is missing in the chars count to make it more clear)
By the way, there are better ways of doing string aggregation in T-Sql.
For versions prior to 2017, use a combination of stuff and for xml path, like this:
SELECT STUFF(
(
SELECT '; ' + CAST(id as varchar(11)) + '. ' + name
FROM #test
FOR XML PATH('')
),1 ,2, '')
For version 2017 or higher, use the built in string_agg function, like this:
SELECT STRING_AGG(CAST(id as varchar(11)) + '. '+ name, '; ')
FROM #Test
for more information, check out this SO post.

The * is an indicator that the result length (was) too short to display. In your example you're trying to fit a two digit number into VARCHAR(1). In this particular case the result is the * instead of throwing an error.
The behavior is described in the docs.

Related

How to get values from table using multiple ID's in single column in SQL?

I am working in an existing stored procedure. In that, they fetch some data using select Queries. Now I am trying to fetch some more data. The problems I have is, the column I am using to fetch data from another table have more than one Id saved in with comma(,) in between.
Procedure with 2 tables as Tbl_av,Tbl_aa.
I have one column in Tbl_av as processId that is in Varchar and it stores data like 1,2,4 etc.. (that is, it stores the Id of the Process).
Now I am trying to get the Process Name from one more table Tbl_Process using this ProcessId under the table Tbl_av. In that table, the process Id is Unique one and it is in INTEGER.
Tbl_Process as follows:
Tbl_av as follows:
I have procedure few part as follows:
SET #strQuery= 'SELECT isnull(av.ID, 0) AS ID
,isnull(ItemNumber, '''') AS Number
,isnull(av.ItemName,'''') as Item Name
,av.Description
,(select top 1 Name from TBL_Names where Id =aa.id)as Person Incharge from Tbl_AV av, Tbl_aa aa WHERE av.S_number = aa.S_number'
Now what I am trying to do is , I need to fetch that Process names from Tbl_Process using this Tbl_av ProcessId's inside the procedure.
I dont know how to achieve it, as it both columns are in different datatype and more than 1 id is saved in one column
NOTE: We can simply achieve this as 'SELECT Process_Name from Tbl_Process WHERE Id in (av.ProcessId)' - But doing this ,displays data in column format..
I want data to be selected as if Id is 1,2 means I want my output as Process_Name = Item 1,Item 2.
Kindly help.
Two tricks are needed. One is joining based on a comma-separated list of IDs. That can easily be done poorly resulting in unwanted matches such as 1 and 2 matching 12. The article Stephen Jennings referenced has some good reliable solutions.
The second is concatenating a collection of results into a single string. For recent versions of SQL Server, the STRING_AGG is the preferred solution. For older versions (such as 2014) the most common method is the "FOR XML" trick.
I've combined the two techniques below.
DECLARE #Tbl_Process TABLE(ID INT, process_Name VARCHAR(100))
INSERT #Tbl_Process
VALUES (1, 'Item 1'), (2, 'Item 2'), (3, 'Item 3'), (4, 'Item 4'), (5, 'Item 5'), (12, 'Item 12')
DECLARE #Tbl_av TABLE(ID INT, ProcessId VARCHAR(100))
INSERT #Tbl_av
VALUES (1, '1,3,5'), (2, '2,4'), (3, '1,2,3'), (4, '1,5'), (5, ''), (6, '3,4,12')
SELECT AV.*, PL.*
FROM #Tbl_av AV
CROSS APPLY (
SELECT ISNULL(STUFF(
(
SELECT ',' + P.process_Name
FROM #Tbl_Process P
--(bad) WHERE CHARINDEX(CONVERT(VARCHAR, P.ID), AV.ProcessId) > 0
WHERE ',' + AV.ProcessId + ',' LIKE '%,' + CONVERT(VARCHAR, P.ID) + ',%'
ORDER BY P.ID -- alternately ORDER BY P.process_Name
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)')
, 1, 1, '')
, '(none)')
AS Process_Names
) PL
Results
ID
ProcessId
Process_Names
1
1,3,5
Item 1,Item 3,Item 5
2
2,4
Item 2,Item 4
3
1,2,3
Item 1,Item 2,Item 3
4
1,5
Item 1,Item 5
5
(none)
6
3,4,12
Item 3,Item 4,Item 12
The FOR XML PATH('') trick causes the results to concatenate into a single XML string with no tags. The , TYPE together with .value('text()[1]','nvarchar(max)') safely extracts the resulting text, undoing any XML specific text encodings such as &, <, or >. The STUFF() removes the leading comma, and the ISNULL() provides a default if there are no values.
See How Stuff and 'For Xml Path' work in SQL Server? for more on how the FOR XML trick works.
If you prefer comma-space list separators, you can update the ',', but will also need to adjust the STUFF to strip two characters instead of one.
The contents of the cross apply could be moved directly into the SELECT clause, but as a matter of style, the CROSS APPLY allows separation of complex logic from the rest of the query.

Concatenate Strings with Spaces into a varchar(255) column

I am writing an ETL logic to insert four source columns at a certain position of a certain length into a target varchar(255) column. I have tried several ways but unable to find a solution for it. Any help is much appreciated.
Ex:
Source:
Column_id at Column 14, len 8
+
name at Column 43, len 27
+
term at Column 133, len 1
Target:
Description varchar(255)
You could convert the data to char like this:
select REPLICATE(' ', 14)+convert(char(8), column_id)+REPLICATE(' ', 43-8-14) + convert(char(27), name) + REPLICATE(' ', 133-43-27)+convert(char(1), term)
from <whatever table not provided>
I left '133-43-27' as a example, test it so it's the right position...
You can try something along this:
a declared table to simulate your issue
DECLARE #tbl TABLE(id INT IDENTITY, [name] VARCHAR(100), term VARCHAR(100));
INSERT INTO #tbl VALUES('Name One','first term')
,('One more name','One more term');
-- some variables for a generic approach
DECLARE #posId INT=1
,#posName INT=10
,#posTerm INT=50;
--the query
SELECT t.*
,STUFF(
STUFF(
STUFF(trg,#posId, LEN(t.id), t.id)
,#posName, LEN(t.[name]), t.[name])
,#posTerm, LEN(t.term), t.term)
FROM #tbl t
CROSS APPLY(SELECT REPLICATE(' ',255)) A(trg)
--the result
1 Name One first term
2 One more name One more term
The idea in short:
First we use CROSS APPLY(SELECT ...) to add a column to our result set. This column is a string, created off 255 blanks.
Now we can use STUFF(). This functions stuffs given characters into an existing string. By replacing the exact count of characters we will not touch the total length.
Hint 1: If your data might have trailing blanks LEN() can trick you out. You can either use TRIM() (older versions LTRIM() and RTRIM()) or DATALENGTH() (be aware of 2 bytes with NVARCHAR!) then...
Hint 2: If you have to cut your data to a max length, you can use LEFT()
STUFF() does what you want. But you want to be really careful about overwriting all the data that is there. For that, I would suggest casting to a char() type:
SELECT t.*,
STUFF(STUFF(STUFF(target, 14, 8, CONVERT(CHAR(8), t.id
), 43, 27, CONVERT(CHAR(27), t.name
), 133, 1, CONVERT(CHAR(1), t.term
)
FROM t;
The CHAR() type pads the values with spaces, which means that this code will overwrite any existing data in those positions (and only in those positions).

LIKE operator as a replacement for RegEx

My task is to select entries for the following mask - SNNN000
Where:
"N" – any numerical symbol;
"S" – any numerical or alphabetic symbol(Latin);
"0" - any numerical or alphabetic symbol (Latin), can be
missed;
Here's what I got - "[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]".
There was a problem with "0", how can I make it so that the mask can ignore the conditions for this symbol? All entries except the 5th(Id) from the #table table should be displayed.
DECLARE #table TABLE (
id INT
,Txt NVARCHAR(100)
);
INSERT INTO #table (id, Txt)
VALUES (1, N'S123AB1')
,(2, N'S123')
,(3, N'S123A')
,(4, N'S123AB')
,(5, N'S123.#!');
SELECT *
FROM #table AS t
WHERE t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]'
I understand that I could add conditions via the OR operator. But I would like to do it in a single expression and I could do it in regular expressions "[0-9A-Za-z]\d{3}[0-9A-Za-z]?[0-9A-Za-z]?[0-9A-Za-z]?". As I understand it, there are no full regular expressions in SQL, if I am wrong, then I would appreciate an explanation.
SELECT *
FROM #table AS t
WHERE t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]'
OR t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9]';
Unfortunately, using OR is probably the best you can do using SQL Server's enhanced LIKE operator:
SELECT *
FROM #table AS t
WHERE
t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9]' OR
t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z]' OR
t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z][0-9A-Za-z]' OR
t.Txt LIKE N'[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]';
The simplest method I can think of is:
SELECT t.*
FROM #table AS t
WHERE (t.Txt + 'AAA') LIKE '[0-9A-Za-z][0-9][0-9][0-9][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]%' AND
LEN(t.Txt) BETWEEN 4 AND 7;
This adds three extra characters and checks that the first 7 characters match. It then validates the length of the column.

sql extract rightmost number in string and increment

i have transaction codes like
"A0004", "1B2005","20CCCCCCC21"
I need to extract the rightmost number and increment the transaction code by one
"AA0004"----->"AA0005"
"1B2005"------->"1B2006"
"20CCCCCCCC21"------>"20CCCCCCCC22"
in SQL Server 2012.
unknown length of string
right(n?) always number
dealing with unsignificant number of string and number length is out of my league.
some logic is always missing.
LEFT(#a,2)+RIGHT('000'+CONVERT(NVARCHAR,CONVERT(INT,SUBSTRING( SUBSTRING(#a,2,4),2,3))+1)),3
First, I want to be clear about this: I totally agree with the comments to the question from a_horse_with_no_name and Jeroen Mostert.
You should be storing one data point per column, period.
Having said that, I do realize that a lot of times the database structure can't be changed - so here's one possible way to get that calculation for you.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
col varchar(100)
);
INSERT INTO #T (col) VALUES
('A0004'),
('1B2005'),
('1B2000'),
('1B00'),
('20CCCCCCC21');
(I've added a couple of strings as edge cases you didn't mention in the question)
Then, using a couple of cross apply to minimize code repetition, I came up with that:
SELECT col,
LEFT(col, LEN(col) - LastCharIndex + 1) +
REPLICATE('0', LEN(NumberString) - LEN(CAST(NumberString as int))) +
CAST((CAST(NumberString as int) + 1) as varchar(100)) As Result
FROM #T
CROSS APPLY
(
SELECT PATINDEX('%[^0-9]%', Reverse(col)) As LastCharIndex
) As Idx
CROSS APPLY
(
SELECT RIGHT(col, LastCharIndex - 1) As NumberString
) As NS
Results:
col Result
A0004 A0005
1B2005 1B2006
1B2000 1B2001
1B00 1B01
20CCCCCCC21 20CCCCCCC22
The LastCharIndex represents the index of the last non-digit char in the string.
The NumberString represents the number to increment, as a string (to preserve the leading zeroes if they exists).
From there, it's simply taking the left part of the string (that is, up until the number), and concatenate it to a newly calculated number string, using Replicate to pad the result of addition with the exact number of leading zeroes the original number string had.
Try This
DECLARE #test nvarchar(1000) ='"A0004", "1B2005","20CCCCCCC21"'
DECLARE #Temp AS TABLE (ID INT IDENTITY,Data nvarchar(1000))
INSERT INTO #Temp
SELECT #test
;WITH CTE
AS
(
SELECT Id,LTRIM(RTRIM((REPLACE(Split.a.value('.' ,' nvarchar(max)'),'"','')))) AS Data
,RIGHT(LTRIM(RTRIM((REPLACE(Split.a.value('.' ,' nvarchar(max)'),'"','')))),1)+1 AS ReqData
FROM
(
SELECT ID,
CAST ('<S>'+REPLACE(Data,',','</S><S>')+'</S>' AS XML) AS Data
FROM #Temp
) AS A
CROSS APPLY Data.nodes ('S') AS Split(a)
)
SELECT CONCAT('"'+Data+'"','-------->','"'+CONCAT(LEFT(Data,LEN(Data)-1),CAST(ReqData AS VARCHAR))+'"') AS ExpectedResult
FROM CTE
Result
ExpectedResult
-----------------
"A0004"-------->"A0005"
"1B2005"-------->"1B2006"
"20CCCCCCC21"-------->"20CCCCCCC22"
STUFF(#X
,LEN(#X)-CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END+1
,LEN(((RIGHT(#X,CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END)/#N)+1)#N)
,((RIGHT(#X,CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END)/#N)+1)#N)
works on number only strings
99 becomes 100
mod(#N) increments

Uppercase first two characters in a column in a db table

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