This question already has answers here:
SQL split values to multiple rows
(12 answers)
Closed 7 years ago.
I have a table like this.
id items
1 1|2|3
2 3|4
...
Now, I want to split items to multiple rows, like this.
id item
1 1
1 2
1 3
2 3
2 4
...
Can SQL do this job?
You shouldn't store data in this format in your database due to index usages etc. But in some cases, you can't avoid it, if an foreign application delivers information in this way for example. If you have to store it this way and disaggregate it to store it in a good format you can use the following user defined function to achieve it.
CREATE FUNCTION dbo.udf_split (#String nvarchar(max), #Delimiter nchar(1))
RETURNS #Results Table (Items nvarchar(max))
AS
BEGIN
DECLARE #Index int
DECLARE #Slice nvarchar(max)
SET #Index = 1
IF #String IS NULL RETURN
WHILE #Index != 0
BEGIN
SELECT #Index = CHARINDEX(#Delimiter, #String)
IF #Index != 0
SELECT #Slice = LEFT(#String, #Index - 1)
ELSE
SELECT #Slice = #String
INSERT INTO #Results(Items) VALUES (LTRIM(RTRIM(#Slice)))
SELECT #String = RIGHT(#String, LEN(#String) - #Index)
IF Len(#String) = 0 BREAK
END
RETURN
END
GO
Related
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 9 years ago.
Improve this question
In this Question the accepted solution using bitwise XOR in PHP to find first different Charindex of two string. How can implement this solution in SQL query or is there a better solution for this problem?
Update: Here's Sql XOR solution:
only for string with len letter than 8
DECLARE #A VARCHAR(50)
DECLARE #B VARCHAR(50)
DECLARE #Pos INT
set #A='ABrC12#4'
set #B='ABrC1234'
SET #Pos=( select
case
when len(#A)<>LEN(#B) then -1
when #A=#B then 0
ELSE 1+PATINDEX('%[1-9]%',
CONVERT(varchar(max),cast(cast(CAST(#A AS varbinary) as BIGINT) ^
cast(CAST(#B AS varbinary) as BIGINT)
as varbinary ),2))/2
end)
Print #Pos
You can use a little nice result with this:
create FUNCTION [dbo].[fnFirstPosDif]
(
#Word as Nvarchar(70),
#Word2 as Nvarchar(70)
)
RETURNS INT
AS
BEGIN
declare #Strings2 TABLE
(
FirstPosDif INT
)
declare #FirstPosDif as int
;with C as
(
select #Word as Word,#Word2 as Word2 ,0 as Iteration
union all
select cast(substring(#Word,Iteration+1,1)as Nvarchar(70)) as Word,cast(substring(#Word2,Iteration+1,1)as Nvarchar(70)) as Word2,Iteration + 1 from C
WHERE Iteration < len(#Word) and cast(substring(#Word,Iteration+1,1)as Nvarchar(70))=cast(substring(#Word2,Iteration+1,1)as Nvarchar(70))
)
insert into #Strings2(FirstPosDif) select MAX(Iteration) as FirstPosDif from C
set #FirstPosDif=(select top 1 FirstPosDif from #Strings2)
return #FirstPosDif
END
I do not believe that there is an equivalent to any of the methods shown in the question you referenced for SQL Server.
The only way appears to be to manually walk through the string (as per the PHP suggestion), but in SQL it is even more laborious due to the fact that you cannot address CHAR types as if they were arrays:
DECLARE #A NVARCHAR(50)
DECLARE #B NVARCHAR(50)
DECLARE #I INT
DECLARE #Pos INT
SET #A = 'Hello World!'
SET #B = 'Hella World!'
SET #I = 0
SET #Pos = 0
-- Loop through each character
WHILE (#I < LEN(#A) AND #I < LEN(#B) AND #Pos = 0)
BEGIN
-- See if the characters at this position differ
IF (SUBSTRING(#A, #I, 1) != SUBSTRING(#B, #I, 1))
SET #Pos = #I
SET #I = #I + 1
END
IF (#Pos > 0)
PRINT 'Difference at position ' + CAST(#Pos AS VARCHAR) + ' A:' + SUBSTRING(#A, #Pos, 1) + ' B:' + SUBSTRING(#B, #Pos, 1)
ELSE
PRINT 'Strings are the same'
Running this as per the above would yield the following output:
Difference at position 5 A:o B:a
Note that you could put this into a UDF for simplicity if you use it often, and additionally I have not included checks for NULL values.
i have a stored procedure that searches like bellow:
BEGIN
#At first, Search in name of job:
(SELECT * FROM tb_job WHERE `name` LIKE '%some%' AND `name` LIKE '%thing%')
UNION
# second, search for tags:
(SELECT * FROM tb_job WHERE id IN
(
SELECT idJob FROM
(
(SELECT 2 AS priority1, COUNT(tb_job_tag.idTag) AS priority2, idJob FROM tb_job_tag WHERE idTag IN
(SELECT tb_tag.id FROM tb_tag WHERE tag LIKE '%some%' OR tag LIKE '%thing%')
GROUP BY tb_job_tag.idJob)
UNION
(SELECT 1, COUNT(tb_job_tag.idTag), idJob FROM tb_job_tag WHERE idTag IN
(SELECT tb_tag.id FROM tb_tag WHERE tag LIKE '%some%' AND tag LIKE '%thing%')
GROUP BY tb_job_tag.idJob)
)
AS t ORDER BY priority1, priority2 DESC
)
)
END
now i have 2 questions: how can i pass an array of words and separate them in mysql and use them in LIKE? second, how can i make this search better?
(i have 3table: tb_job, tb_tag, tb_job_tag that stores job's id and tag's id). thanks for your help.
/**
* http://www.aspdotnet-suresh.com/2013/07/sql-server-split-function-example-in.html
*/
CREATE FUNCTION dbo.Array(#String nvarchar(4000), #Delimiter char(1))
RETURNS #Results TABLE ([id] [bigint] IDENTITY(1,1) NOT NULL, Items nvarchar(4000))
AS
BEGIN
DECLARE #INDEX INT
DECLARE #SLICE nvarchar(4000)
-- HAVE TO SET TO 1 SO IT DOESNT EQUAL Z
-- ERO FIRST TIME IN LOOP
SELECT #INDEX = 1
WHILE #INDEX !=0
BEGIN
-- GET THE INDEX OF THE FIRST OCCURENCE OF THE SPLIT CHARACTER
SELECT #INDEX = CHARINDEX(#Delimiter,#STRING)
-- NOW PUSH EVERYTHING TO THE LEFT OF IT INTO THE SLICE VARIABLE
IF #INDEX !=0
SELECT #SLICE = LEFT(#STRING,#INDEX - 1)
ELSE
SELECT #SLICE = #STRING
-- PUT THE ITEM INTO THE RESULTS SET
INSERT INTO #Results(Items) VALUES(#SLICE)
-- CHOP THE ITEM REMOVED OFF THE MAIN STRING
SELECT #STRING = RIGHT(#STRING,LEN(#STRING) - #INDEX)
-- BREAK OUT IF WE ARE DONE
IF LEN(#STRING) = 0 BREAK
END
RETURN
END
execute this function once in your database and now onward to access specific value you can write your query like below
DECLARE #VALUE VARCHAR(100);
SELECT TOP 1 #VALUE = Items FROM [dbo].[Array] ('some,thing,like,that' , ',') where id = 2
PRINT #VALUE
All you need to change is id in select statement. It'll accept only String values for now. But you can convert String to Int in SQL using CAST
I just created this function in hurry if you have any suggestions/modifications let me know...
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Concatenate many rows into a single text string?
I have 2 tables in SQL Server 2008 database - tblQuestion and tblSummary. I'm selecting QuestionIDs from tblQuestion as follows.
SELECT QuestionId from tblQuestion WHERE Status='Completed';
The result is as follows (from multiple records),
QuestionId
----------
1
2
5
7
8
9
[6 rows]
Now, I need to insert above selected IDs to "CompletedSections" column (which is VARCHAR type) in tblSummary. It should be inserted as a CSV like format - 1,2,5,7,8,9
For example, if I select those from tblSummary will be as follows.
SELECT CompletedSections FROM tblSummary WHERE <Some Condition>
Result should be,
CompletedSections
-----------------
1,2,5,7,8,9
[1 row]
How this can be done at the database level using t-SQL (not using any programming language like C#)? I'm hoping to implement this with t-SQL using a scheduled SQL SP/ Function/ Trigger.
Thanks,
Chatur
Here is a solution I have used in the past. It is a little hacky, but it should be faster than using a cursor to create the CSV. Here is some example code to get you started.
DECLARE #tblQuestion TABLE
(
QuestionId nvarchar(10)
)
DECLARE #tblSummary TABLE
(
CompletedSections nvarchar(100)
)
INSERT INTO #tblQuestion
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)
INSERT INTO #tblSummary
SELECT SUBSTRING(
(SELECT ',' + QuestionId
from #tblQuestion
FOR XML PATH('')),2,200000) AS CSV
SELECT * FROM #tblSummary
This will do the job;
DECLARE #S VARCHAR(5000)
SELECT #S=ISNULL(#S+ ',', '') + CONVERT(VARCHAR(10),QuestionId)
FROM tblQuestion
SELECT #S
DECLARE #STRING VARCHAR(MAX) = ''
DECLARE #VALUE VARCHAR(MAX)
DECLARE A_CURSOR CURSOR FOR
SELECT QuestionId from tblQuestion WHERE Status='Completed'
OPEN A_CURSOR
FETCH A_CURSOR INTO #VALUE
WHILE ##FETCH_STATUS = 0
BEGIN
if #STRING <> ''
set #STRING = #STRING + ', '
set #STRING = #STRING + CONVERT(VARCHAR(MAX),#VALUE)
FETCH A_CURSOR INTO #VALUE
END
CLOSE A_CURSOR
DEALLOCATE A_CURSOR
INSERT INTO CompletedSection (tblSummary) SELECT #STRING
I have the following table with each row having comma-separated values:
ID
-----------------------------------------------------------------------------
10031,10042
10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039
10009
20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041
I need to get a count for each ID (a groupby).
I am just trying to avoid cursor implementation and stumped on how to do this without cursors.
Any Help would be appreciated !
You will want to use a split function:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end;
And then you can query the data in the following manner:
select items, count(items)
from table1 t1
cross apply dbo.split(t1.id, ',')
group by items
See SQL Fiddle With Demo
Well, the solution i always use, and probably there might be a better way, is to use a function that will split everything. No use for cursors, just a while loop.
if OBJECT_ID('splitValueByDelimiter') is not null
begin
drop function splitValueByDelimiter
end
go
create function splitValueByDelimiter (
#inputValue varchar(max)
, #delimiter varchar(1)
)
returns #results table (value varchar(max))
as
begin
declare #delimeterIndex int
, #tempValue varchar(max)
set #delimeterIndex = 1
while #delimeterIndex > 0 and len(isnull(#inputValue, '')) > 0
begin
set #delimeterIndex = charindex(#delimiter, #inputValue)
if #delimeterIndex > 0
set #tempValue = left(#inputValue, #delimeterIndex - 1)
else
set #tempValue = #inputValue
if(len(#tempValue)>0)
begin
insert
into #results
select #tempValue
end
set #inputValue = right(#inputValue, len(#inputValue) - #delimeterIndex)
end
return
end
After that you can call the output like this :
if object_id('test') is not null
begin
drop table test
end
go
create table test (
Id varchar(max)
)
insert
into test
select '10031,10042'
union all select '10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039'
union all select '10009'
union all select '20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041'
select value
from test
cross apply splitValueByDelimiter(Id, ',')
Hope it helps, although i am still looping through everything
After reiterating the comment above about NOT putting multiple values into a single column (Use a separate child table with one value per row!),
Nevertheless, one possible approach: use a UDF to convert delimited string to a table. Once all the values have been converted to tables, combine all the tables into one table and do a group By on that table.
Create Function dbo.ParseTextString (#S Text, #delim VarChar(5))
Returns #tOut Table
(ValNum Integer Identity Primary Key,
sVal VarChar(8000))
As
Begin
Declare #dlLen TinyInt -- Length of delimiter
Declare #wind VarChar(8000) -- Will Contain Window into text string
Declare #winLen Integer -- Length of Window
Declare #isLastWin TinyInt -- Boolean to indicate processing Last Window
Declare #wPos Integer -- Start Position of Window within Text String
Declare #roVal VarChar(8000)-- String Data to insert into output Table
Declare #BtchSiz Integer -- Maximum Size of Window
Set #BtchSiz = 7900 -- (Reset to smaller values to test routine)
Declare #dlPos Integer -- Position within Window of next Delimiter
Declare #Strt Integer -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
-- ---------------------------
If #delim is Null Set #delim = '|'
If DataLength(#S) = 0 Or
Substring(#S, 1, #BtchSiz) = #delim Return
-- --------------------------------------------
Select #dlLen = DataLength(#delim),
#Strt = 1, #wPos = 1,
#wind = Substring(#S, 1, #BtchSiz)
Select #winLen = DataLength(#wind),
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, #Strt)
-- --------------------------------------------
While #Strt <= #winLen
Begin
If #dlPos = 0 Begin -- No More delimiters in window
If #isLastWin = 1 Set #dlPos = #winLen + 1
Else Begin
Set #wPos = #wPos + #Strt - 1
Set #wind = Substring(#S, #wPos, #BtchSiz)
-- ----------------------------------------
Select #winLen = DataLength(#wind), #Strt = 1,
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, 1)
If #dlPos = 0 Set #dlPos = #winLen + 1
End
End
-- -------------------------------
Insert #tOut (sVal)
Select LTrim(Substring(#wind,
#Strt, #dlPos - #Strt))
-- -------------------------------
-- Move #Strt to char after last delimiter
Set #Strt = #dlPos + #dlLen
Set #dlPos = CharIndex(#delim, #wind, #Strt)
End
Return
End
Then write, (using your table schema),
Declare #AllVals VarChar(8000)
Select #AllVals = Coalesce(#allVals + ',', '') + ID
From Table Where ID Is Not null
-- -----------------------------------------
Select sVal, Count(*)
From dbo.ParseTextString(#AllVals, ',')
Group By sval
I am trying to decide whether to set min_word_length to be 2 or 3 on my new MySQL instance, so i figure if I count the number of 2 letter words in the column to be indexed it will give an indication of the right answer.
So, question is, is it possible using a SQL query to count the number two letter words within a column?
Not tested, but could you try
SELECT SUM(
CASE WHEN yourColumn REGEXP '[[:<:]][a-zA-Z]{2}[[:>:]]'
THEN 1
ELSE 0 END
) AS matches
FROM ...
Although I think, that it might only count if a 2-character-word is in a row, not how many times in one row.
UPDATE: Tested and it works like I thought, so please just ignore this answer.
The LEN() function returns the length of the value in a text field.
SELECT LEN(column_name) FROM table_name
The COUNT(*) function returns the number of records in a table:
SELECT COUNT(column_name) FROM table_name
So your query should be something like this :
SELECT COUNT(column_name) FROM table_name
WHERE LEN(column_name)=2
UPDATE:
Ok sorry I guess I misunderstood you. In the case you want it I'm afraid there is no build in functions to do what you want it to do. So you have to make a function on your own. Something like this should work:
CREATE FUNCTION [dbo].[WordCount] ( #InputString VARCHAR(4000) )
RETURNS INT
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #PrevChar CHAR(1)
DECLARE #WordCount INT
DECLARE #CharCount INT
SET #Index = 1
SET #WordCount = 0
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
SET #CharCount = #CharCount + 1
SET #PrevChar = CASE WHEN #Index = 1 THEN ' '
ELSE SUBSTRING(#InputString, #Index - 1, 1)
END
IF #PrevChar = ' '
SET #CharCount = 1
END
IF #Char = ' ' AND #CharCount < 3 AND #CharCount > 1
SET #WordCount = #WordCount + 1
SET #Index = #Index + 1
END
RETURN #WordCount
END
Now I have not tested it so you have to test it on your own but it should work. It will return all 2 letter words in the selected string. To get the 3 letters words just change
IF #Char = ' ' AND #CharCount < 4 AND #CharCount > 2
I hope this helps.