I know how to do a left, right and substring in T-SQL, but I'm having difficulty extracting just the name of the person below since the length of the name are not the same. Any ideas or syntax that I can use to extract just the name? Thanks
Data Value:
581;#Jackson, Daniel H; 501;#Sims, Katy L; 606;#Lawrence, Jennifer O
You can get the length of the name dynamically using PATINDEX, but it assumes that the Names are ALWAYS formatted the same way.
Here is an example of a TSQL Select that will give select the first names from the data you supplied:
CREATE TABLE #Temp (
ID INT NOT NULL,
FullName VARCHAR(100)
)
INSERT #Temp VALUES (581, 'Jackson, Daniel H')
INSERT #Temp VALUES (606, 'Lawrence, Jennifer O')
SELECT ID, FullName , SUBSTRING(FullName, PATINDEX('%, % _', FullName) + 2,
PATINDEX('% _', FullName) - PATINDEX('%, %', FullName) - 2) FirstName
FROM #Temp
DROP TABLE #Temp
I use a function to split up CSV-Strings:
create function Do_Split
(#InputString NVARCHAR(4000)
,#Delimiter NVARCHAR(50) = ';')
RETURNS #Items TABLE (Item NVARCHAR(4000)) AS
BEGIN --Function
IF (#Delimiter = ' ')
BEGIN
SET #Delimiter = ';'
SET #InputString = REPLACE(#InputString, ' ', #Delimiter)
END;
IF (#Delimiter IS NULL OR #Delimiter = '') SET #Delimiter = ';';
DECLARE #Item NVARCHAR(4000)
DECLARE #ItemList NVARCHAR(4000)
DECLARE #DelimIndex INT
SET #ItemList = #InputString;
SET #DelimIndex = CHARINDEX(#Delimiter, #ItemList, 0);
WHILE (#DelimIndex != 0)
BEGIN
SET #Item = SUBSTRING(#ItemList, 0, #DelimIndex);
INSERT INTO #Items VALUES (#Item);
-- Set #ItemList = #ItemList minus one less item
SET #ItemList = SUBSTRING(#ItemList, #DelimIndex+1, LEN(#ItemList)-#DelimIndex);
SET #DelimIndex = CHARINDEX(#Delimiter, #ItemList, 0);
END; -- End WHILE
IF #Item IS NOT NULL -- At least one delimiter was encountered in #InputString
BEGIN
SET #Item = #ItemList;
INSERT INTO #Items VALUES (#Item);
END;
ELSE -- No delimiters were encountered in #InputString, so just return #InputString
BEGIN
INSERT INTO #Items VALUES (#InputString);
END;
RETURN
END -- End Function
go
Usage:
SELECT * FROM Do_Split('581;#Jackson, Daniel H; 501;#Sims, Katy L; 606;#Lawrence, Jennifer O',';');
Result:
581
#Jackson, Daniel H
501
#Sims, Katy L
606
#Lawrence, Jennifer O
It isn't clear how your data looks like, because your example concatenates multiple values in one row.
Case 1
Assume that there're 2 columns id and full_name in your_table. The semicolon ; was added by you intentionally to distinguish columns. In this case, you can obtain the value of full name using the function RIGHT. The length would be the length of full_name minus 1 which excludes the #.
--
-- id full_name
-- -- ---------
-- 581 #Jackson, Daniel H
-- 501 #Sims, Katy L
-- 606 #Lawrence, Jennifer O
--
SELECT RIGHT(full_name, LEN(full_name) - 1) AS full_name
FROM your_table;
Case 2
If the above solution is not suitable, let's discuss another case. Assume that there's 1 column content in your_table and 3 rows. The semicolon ; was inside the value of content and need to be treated explicitly. In this case, you can obtain the value of full name using the function SUBSTRING. The full name will begin after just after the index of char # and this index can be obtained using CHARINDEX (Transact-SQL):
--
-- content
-- -------
-- 581;#Jackson, Daniel H
-- 501;#Sims, Katy L
-- 606;#Lawrence, Jennifer O
--
SELECT SUBSTRING(content, CHARINDEX('#', content) + 1, 1000) AS full_name
FROM your_table;
Related
The SQL server is 2008. I have an Access 2016 front-end for reporting purposes. One report requires that one or more Product Classes from a list be chosen to report on. I have the VBA that creates the pass-through query with the appropriate single line:
exec dbo.uspINVDAYS 'A3,A4,A6,AA,AB'
I have this SQL code that should take the list as hard-coded here:
DECLARE #parProductClasses NVARCHAR(200) = 'A3,A4,A6,AA,AB';
DECLARE #ProductClasses NVARCHAR(200),#delimiter NVARCHAR(1) = ',';
SET #ProductClasses = #parProductClasses;
DECLARE #DAYS INT,#numDAYS int;
SET #DAYS = 395;
SET #numDAYS = #DAYS;
SELECT UPINVENTORY.StockCode, UPINVENTORY.[Description], UPINVENTORY.Supplier, UPINVENTORY.ProductClass
, UPINVENTORY.WarehouseToUse
, CAST(UPINVENTORY.Ebq AS INT)Ebq
, cast(UPINVENTORY.QtyOnHand AS INT)QtyOnHand
, cast(UPINVENTORY.PrevYearQtySold AS INT)PrevYearQtySold
, cast(UPINVENTORY.YtdQtyIssued AS INT)YtdQtyIssued
,#numDAYS as numDAYS
,CAST(ROUND((PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS,0) AS INT)TOTAL
,CASE WHEN (PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS
= 0 THEN 0
ELSE CAST(ROUND(QTYONHAND/((PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS),0)AS INT)
END FINAL
,CASE WHEN (PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS
= 0 THEN 0
ELSE CAST(ROUND(QTYONHAND/((PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS),0)AS INT)
END FINAL1
FROM
TablesCoE.dbo.vwRPUpInventory UPINVENTORY
WHERE UPINVENTORY.ProductClass IN (Select val From TablesCoE.dbo.split(#ProductClasses,','));
When I run this I get:
Msg 468, Level 16, State 9, Line 9
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Latin1_General_BIN" in the equal to operation.
I cannot determine where
COLLATE SQL_Latin1_General_CP1_CI_AS
should go. Where am I equating or comparing? The SQL IN clause cannot handle the comma-separated list since it is not a strict SQL table.
Here's the code used to create the dbo.split() function:
CREATE FUNCTION dbo.split(
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
) RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
Thanks to Sandeep Mittal and I am sure others have very similar split functions. Run separately this function does operate as expected and provides a table of the comma-separated list objects.
DECLARE #parProductClasses NVARCHAR(200) = 'A3,A4,A6,AA,AB';
DECLARE #ProductClasses NVARCHAR(200),#delimiter NVARCHAR(1) = ',';
SET #ProductClasses = #parProductClasses;
Select val From TablesCoE.dbo.split(#ProductClasses,',')
Returns
val
A3
A4
A6
AA
AB
try this.
WHERE concat(',',#ProductClasses,',') like concat('%',UPINVENTORY.ProductClass,'%')
it's a silly way of checking if your productClass is within the #productClasses list.
After attempting to use a prefabricated table-valued variable versus on the fly in the WHERE clause, neither worked, I then started to try different placements of the COLLATE statement. I was complacent in applying COLLATE to the right-side with the collation listed on the left in the SQL error message. I tried the collation listed on the right of the SQL error message to the left side of the WHERE clause and the SQL code works to spec now. Here it is:
DECLARE #parProductClasses NVARCHAR(200) = 'A3,A4,A6,AA,AB';
DECLARE #ProductClasses NVARCHAR(200),#delimiter NVARCHAR(1) = ',';
SET #ProductClasses = #parProductClasses;
DECLARE #DAYS INT,#numDAYS int;
SET #DAYS = 395;
SET #numDAYS = #DAYS;
SELECT UPINVENTORY.StockCode, UPINVENTORY.[Description], UPINVENTORY.Supplier, UPINVENTORY.ProductClass
, UPINVENTORY.WarehouseToUse
, CAST(UPINVENTORY.Ebq AS INT)Ebq
, cast(UPINVENTORY.QtyOnHand AS INT)QtyOnHand
, cast(UPINVENTORY.PrevYearQtySold AS INT)PrevYearQtySold
, cast(UPINVENTORY.YtdQtyIssued AS INT)YtdQtyIssued
,#numDAYS as numDAYS
,CAST(ROUND((PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS,0) AS INT)TOTAL
,CASE WHEN (PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS
= 0 THEN 0
ELSE CAST(ROUND(QTYONHAND/((PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS),0)AS INT)
END FINAL
,CASE WHEN (PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS
= 0 THEN 0
ELSE CAST(ROUND(QTYONHAND/((PREVYEARQTYSOLD + YTDQTYISSUED)/#DAYS),0)AS INT)
END FINAL1
FROM
TablesCoE.dbo.vwRPUpInventory UPINVENTORY
WHERE UPINVENTORY.ProductClass COLLATE Latin1_General_BIN IN (SELECT val FROM TablesCoE.dbo.split(#ProductClasses,','));
Thanks for your suggestions #Krish and #Isaac.
Tim
I have a column with data that looks like this in a single field:
"a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b"
Using some sort of regex or SQL function I would like to make it look like this:
"a,b,c,a,b,a,c,a,b"
Essentially I am trying to get rid of repeated values that appear in order but keep the unique changes from one value to another.
My knowledge of reg-expressions pretty much ends at removing duplicates. Any help is greatly appreciated!
use regexp:
SELECT regexp_replace('a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b', '(\w)(,\1)+', '\1', 'g')
(\w)(,\1)+ mutches: (any word char) and following (, and this same word char) more than one time...
Fiddle example
RegExr example
You can convert the elements into rows, check if the previous row is different to the current and then keep only those where something changed. This can then be aggregated back into a comma separated list:
select string_agg(ch, ',' order by idx)
from (
select u.ch, u.idx,
coalesce(u.ch <> lag(u.ch) over (order by u.idx), true) as is_change
from unnest(string_to_array('a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b', ',')) with ordinality as u(ch, idx)
) t
where is_change
The with ordinality returns the original array index, so that we can sort the elements correctly when aggregating them.
This can also be put into a function:
create or replace function cleanup(p_input text)
returns text
as
$$
select string_agg(ch, ',' order by idx)
from (
select u.ch, u.idx,
coalesce(u.ch <> lag(u.ch) over (order by u.idx), true) as is_change
from unnest(string_to_array(p_input, ',')) with ordinality as u(ch, idx)
) t
where is_change;
$$
language sql;
Online example
My understanding is:
If the character is the same as previous character, you want to remove it from the string.
So I will use while loop and if statement in this case:
--CREATE TABLE TEST (ID VARCHAR(100));
--INSERT INTO TEST VALUES ('a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b');
DO $$
DECLARE
V_NEWSTRING VARCHAR(100) := '';
V_I INTEGER := 1;
V_LENGTH INTEGER := 0;
V_CURRENT VARCHAR(10) := '';
V_LAST VARCHAR(10) := '';
BEGIN
SELECT LENGTH(ID) FROM TEST INTO V_LENGTH;
WHILE V_I <= V_LENGTH LOOP
SELECT SUBSTRING(ID,V_I,1) from TEST INTO V_CURRENT;
IF V_CURRENT <> V_LAST THEN
V_NEWSTRING = V_NEWSTRING || V_CURRENT || ',';
END IF;
V_LAST = V_CURRENT;
V_I = V_I + 2;
END LOOP;
raise notice 'Value: %', V_NEWSTRING;
END $$;
Test Result (PostgreSQL-9.4):
Let's say I have data:
heloo
cuube
triniity
How to write script that will replace those "doubled" characters with only one? So the result from the above data set would be:
helo
cube
trinity
Usually I post some script where I tried to achieve this, but this time I can't think of any.
This should work:
CREATE PROCEDURE remove_duplicate_characters(#string VARCHAR(100))
AS
DECLARE #result VARCHAR(100)
SET #result=''
SELECT #result=#result+MIN(SUBSTRING(#string ,number,1)) FROM
(
SELECT number FROM master..spt_values WHERE type='p' AND number BETWEEN 1 AND len(#string )) AS t GROUP BY SUBSTRING(#string,number,1) ORDER BY MIN(number)
)
SELECT #result
GO
You then call it like this:
EXEC remove_duplicate_characters 'heloo'
Source
This script does not depend on having access to master functions, and just relies on t-sql string functions.
declare #word varchar(100) = 'aaaacuuuuuubeeeee', #result varchar(100) = ''
declare #letter char, #idx int = 0, #lastletter char = ''
while(#idx <= len(#word))
begin
select #letter = substring(#word,#idx,1)
if (#letter != #lastletter)
begin
select #result = concat(#result,#letter)
end
select #lastletter = #letter,#idx = #idx + 1
end
select #result
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE primes
( pos SERIAL NOT NULL PRIMARY KEY
, val INTEGER NOT NULL
, CONSTRAINT primes_alt UNIQUE (val)
);
CREATE FUNCTION is_prime(_val INTEGER)
RETURNS BOOLEAN
AS $func$
DECLARE ret BOOLEAN ;
BEGIN
SELECT False INTO ret
WHERE EXISTS (SELECT *
FROM primes ex
WHERE ex.val = $1
OR ( (ex.val * ex.val) <= $1 AND ($1 % ex.val) = 0 )
);
RETURN COALESCE(ret, True);
END;
$func$ LANGUAGE plpgsql STABLE;
CREATE VIEW vw_prime_step AS (
-- Note when the table is empty we return {2,3,1} as a bootstrap
SELECT
COALESCE(MAX(val) +2,2) AS start
, COALESCE((MAX(val) * MAX(val))-1, 3) AS stop
, COALESCE(min(val), 1) AS step
FROM primes
);
SELECT * FROM vw_prime_step;
-- The same as a function.
-- Works, but is not usable in a query that alters the primes table.
-- ; even not with the TEMP TABLE construct
CREATE FUNCTION fnc_prime_step ( OUT start INTEGER, OUT stop INTEGER, OUT step INTEGER)
RETURNS RECORD
AS $func$
BEGIN
/***
CREATE TEMP TABLE tmp_limits
ON COMMIT DROP
AS SELECT ps.start,ps.stop,ps.step FROM vw_prime_step ps
;
-- RETURN QUERY
SELECT tl.start,tl.stop,tl.step INTO $1,$2,$3
FROM tmp_limits tl
LIMIT 1
;
***/
SELECT tl.start,tl.stop,tl.step INTO $1,$2,$3
FROM vw_prime_step tl
LIMIT 1;
END;
$func$
-- Try lying ...
-- IMMUTABLE LANGUAGE plpgsql;
-- Try lying ...
Stable LANGUAGE plpgsql;
-- This works
SELECT * FROM fnc_prime_step();
INSERT INTO primes (val)
SELECT gs FROM fnc_prime_step() sss
, generate_series( 2, 3, 1 ) gs
WHERE is_prime(gs) = True
;
-- This works
SELECT * FROM fnc_prime_step();
INSERT INTO primes (val)
SELECT gs FROM fnc_prime_step() sss
, generate_series( 5, 24, 2 ) gs
WHERE is_prime(gs) = True
;
-- This does not work
-- ERROR: function expression in FROM cannot refer to other relations of same query level:1
SELECT * FROM fnc_prime_step();
INSERT INTO primes (val)
SELECT gs FROM fnc_prime_step() sss
, generate_series( sss.start, sss.stop, sss.step ) gs
WHERE is_prime(gs) = True
;
SELECT * FROM primes;
SELECT * FROM fnc_prime_step();
Of course, this question is purely hypothetic, I am not stupid enough to attempt to calculate a table of prime numbers in an DBMS. But the question remains: is there a clean way to hack around the absence of LATERAL?
As you can see, I tried with a view (does not work), function around this view (does not work either), a temp table in this function (njet), and twiddling the function's attributes.
Next step will probably be some trigger-hack (but I really,really hate triggers, basically because they are invisible to the strictness of the DBMS schema)
you can use SRF function in target list, but there should be some strange corner cases. LATERAL is best.
postgres=# select i, generate_series(1,i) X from generate_series(1,3) g(i);
i | x
---+---
1 | 1
2 | 1
2 | 2
3 | 1
3 | 2
3 | 3
(6 rows)
my problem is pretty simple. I get a value from a sql select which looks like this:
ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG
and I need it like this:
AR,AM,AU,BE,BA,BR,BG,CN,DK,DE,EE,FO,FI,FR,GE,GR,IE,IS,IT,JP,YU,CA,KZ,KG
The length is different in each dataset.
I tried it with format(), stuff() and so on but nothing brought me the result I need.
Thanks in advance
With a little help of a numbers table and for xml path.
-- Sample table
declare #T table
(
Value nvarchar(100)
)
-- Sample data
insert into #T values
('ARAMAU'),
('ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG')
declare #Len int
set #Len = 2;
select stuff(T2.X.value('.', 'nvarchar(max)'), 1, 1, '')
from #T as T1
cross apply (select ','+substring(T1.Value, 1+Number*#Len, #Len)
from Numbers
where Number >= 0 and
Number < len(T1.Value) / #Len
order by Number
for xml path(''), type) as T2(X)
Try on SE-Data
Time to update your resume.
create function DontDoThis (
#string varchar(max),
#count int
)
returns varchar(max)
as
begin
declare #result varchar(max) = ''
declare #token varchar(max) = ''
while DATALENGTH(#string) > 0
begin
select #token = left(#string, #count)
select #string = REPLACE(#string, #token, '')
select #result += #token + case when DATALENGTH(#string) = 0 then '' else ',' end
end
return #result
end
Call:
declare #test varchar(max) = 'ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG'
select dbo.DontDoThis(#test, 2)
gbn's comment is exactly right, if not very diplomatic :) TSQL is a poor language for string manipulation, but if you write a CLR function to do this then you will have the best of both worlds: .NET string functions called from pure TSQL.
I believe this is what QQping is looking for.
-- select .dbo.DelineateEachNth('ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG',2,',')
create function DelineateEachNth
(
#str varchar(max), -- Incoming String to parse
#length int, -- Length of desired segment
#delimiter varchar(100) -- Segment delimiter (comma, tab, line-feed, etc)
)
returns varchar(max)
AS
begin
declare #resultString varchar(max) = ''
-- only set delimiter(s) when lenght of string is longer than desired segment
if LEN(#str) > #length
begin
-- continue as long as there is a remaining string to parse
while len(#str) > 0
begin
-- as long as know we still need to create a segment...
if LEN(#str) > #length
begin
-- build result string from leftmost segment length
set #resultString = #resultString + left(#str, #length) + #delimiter
-- continually shorten result string by current segment
set #str = right(#str, len(#str) - #length)
end
-- as soon as the remaining string is segment length or less,
-- just use the remainder and empty the string to close the loop
else
begin
set #resultString = #resultString + #str
set #str = ''
end
end
end
-- if string is less than segment length, just pass it through
else
begin
set #resultString = #str
end
return #resultString
end
With a little help from Regex
select Wow=
(select case when MatchIndex %2 = 0 and MatchIndex!=0 then ',' + match else match end
from dbo.RegExMatches('[^\n]','ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG',1)
for xml path(''))