I have a data set that looks like the below (the input).
IR# CR#
1 1,2
2 3
3 4,5,6
I would like the following output. You can consider all fields varchar for this example.
IR# CR#
1 1
1 2
2 3
3 4
3 5
3 6
I have UDFs to split a CSV string into rows...but not something to split 1 row in a table into multiple rows and then union will the next row, etc.
Thanks!
Use CROSS APPLY in conjunction with your splitting UDF. The string splitter I'm using for my example comes from here.
/* Create function for purposes of demo */
CREATE FUNCTION [dbo].[fnParseStringTSQL] (#string NVARCHAR(MAX),#separator NCHAR(1))
RETURNS #parsedString TABLE (string NVARCHAR(MAX))
AS
BEGIN
DECLARE #position int
SET #position = 1
SET #string = #string + #separator
WHILE charindex(#separator,#string,#position) <> 0
BEGIN
INSERT into #parsedString
SELECT substring(#string, #position, charindex(#separator,#string,#position) - #position)
SET #position = charindex(#separator,#string,#position) + 1
END
RETURN
END
go
/* Set up sample data */
declare #t table (
IR int,
CR varchar(100)
)
insert into #t
(IR, CR)
select 1, '1,2' union all
select 2, '3' union all
select 3, '4,5,6'
/* Here's the query that solves the problem */
select t.IR, p.string
from #t t
cross apply [dbo].[fnParseStringTSQL](t.CR,',') p
/* clean up after demo */
drop function [dbo].[fnParseStringTSQL]
Related
I want to know, if exist a way to create a CURSOR with a variable column names
Example:
I have this table:
Translation image for text
ID | TEST1 | TEST2 | TEST3
7 1 3 1
8 2 3 4
9 3 4 5
10 3 3 1
11 2 3 4
12 3 4 5
13 1 3 1
14 2 3 4
15 3 4 5
SQL Code:
DECLARE
#count int,
#columnX varchar(5),
#aux2previous int,
#aux2 int,
#aux1 int,
#columnXResult int,
#id int;
SET #aux2previous = 0;
SET #count = 1;
SET #columnX = 'test' + count;
DECLARE cursor1 CURSOR FOR
SELECT ID, #columnX FROM table
OPEN cursor1
FETCH NEXT FROM cursor1 INTO #id,#columnXResult
...
SET #aux1 = #columnXResult+ #aux2previous
SET #aux2 = #aux2previous + 1
SET #string = 'SXW_'+#columnX+'_'+#aux1+'<>'+#aux2
INSERT INTO tblAuxiliary VALUES(#aux1,#aux2,#string)
SET #count = #count + 1;
SET #aux2previous = #aux2
...
Translation image for text
Foreach row in my first table i have a new row here:
for column Test1
AUX1 | AUX2 | STRING
1 1 SXW_Test1_1<>1
3 2 SXW_Test1_3<>2
for column Test2
AUX1 | AUX2 | STRING
3 1 SXW_Test2_3<>1
6 4 SXW_Test2_6<>4
for column Test3
AUX1 | AUX2 | STRING
1 1 SXW_Test3_1<>1
5 2 SXW_Test3_5<>2
when i do with #columnX
SELECT ID, #columnX FROM table
i got this error:
Conversion failed when converting the varchar value 'test1' to data type int
I see something like #sql = 'select '+#columnX ..., but i need to exec it and i can't do this with cursor.
It is example how use dynamic sql and cursor:
create table test (
t1 int not null,
t2 varchar(10) not null
)
declare #query varchar(1000)
select #query = '
declare #column int
declare query cursor for
select t'+cast(1 as varchar(1))+' from test
open query
fetch next from query into #column
while(##FETCH_STATUS=0)
begin
select #column
fetch next from query into #column
end
close query
deallocate query
'
exec (#query)
There's no way to create a cursor with variable column names unless you use dynamic SQL: you build up a string of SQL code and use EXEC sp_executesql to run it. It's messy and difficult to get right, but you can do it with some trial and error (preferably on a development system, not in production).
FWIW, I think some of the comments above a being a little harsh because they've forgotten what it's like to start out in their field: you're doing fine. Cursors are often misused by web developers who think procedurally instead of "set based", but I still use them occasionally where they make sense (or are just easier). If you want, you can post a new question with more details about what you're trying to accomplish and ask "how can I get rid of this cursor". But if the performance with the cursor is okay, don't worry about it.
HTH!
Check this out.
DECLARE #table TABLE(ID INT,test1 INT,test2 INT,test3 INT)
INSERT INTO #TABLE VALUES
(7,1,3,1),(8,2,3,4),(9,3,4,5),(10,3,3,1),
(11,2,3,4),(12,3,4,5),(13,1,3,1),(14,2,3,4),
(15,3,4,5)
SELECT top 2
t.test1 AUX1,(t.test1+T.prev_test1) AUX2,concat('SXW_Test1_',t.test1,'<>',(t.test1+T.prev_test1)) as string FROM
(
SELECT test1,
LAG(test1,1,0) OVER (ORDER BY ID) prev_test1
FROM #table
) t
union all
SELECT top 2
t.test2 AUX1,(t.test2+T.prev_test2) AUX2,concat('SXW_Test2_',t.test2,'<>',(t.test2+T.prev_test2)) as string FROM
(
SELECT test2,
LAG(test2,1,0) OVER (ORDER BY ID) prev_test2
FROM #table
) t
union all
SELECT top 2
t.test3 AUX1,(t.test3+T.prev_test3) AUX2,concat('SXW_test3_',t.test3,'<>',(t.test3+T.prev_test3)) as string FROM
(
SELECT test3,
LAG(test3,1,0) OVER (ORDER BY ID) prev_test3
FROM #table
) t
I have a SQL Server table as shown below, in that one column contains comma-separated integer values. What I want is to get particular number with count as below expected result
Edited:
I know how to split the comma separated value. but my problem is output that I want. see the expected output in that count column shows in all table howmnay times particular number repeat.
For example :
Id values
---------------
1 2,3
2 1,2,3
3 1,3
4 2,3
Expected result :
number repeat count
--------------
1 2
2 3
3 4
Can anyone help me with this? How to write the query to get this desired output?
Thanks in advance
It looks like the question is how to aggregate the results of a SPLIT function, not how to split the values.
SQL Server 2016 provides the built-in STRING_SPLIT function to split a delimited string and return the values as a table. Individual values are returned in the value field. The following query groups the value field and returns the count:
declare #table table (id int, somevalues nvarchar(200))
insert into #table
values
(1,N'2,3'),
(2,N'1,2,3'),
(3,N'1,3'),
(4,N'2,3')
select value,count(* )
from #table
cross apply string_split(somevalues,',')
group by value
The same query can be used in previous versions as long as a split function is available. Almost all of the available techniques are described in Aaron Bertrand's articles like this one and this follow up. The fastest methods use CLR and XML.
The queries are the same, the only things that change are the names of the columns returned by the split function, eg:
select item,count(* )
from #table
cross apply dbo.SplitStrings_XML(somevalues,',')
group by item
In both cases the result is :
value (No column name)
1 2
2 3
3 4
First create split function like this
CREATE FUNCTION SplitString (
#Input NVARCHAR(MAX)
, #Character CHAR(1)
)
RETURNS #Output TABLE (Item NVARCHAR(1000))
AS
BEGIN
DECLARE #StartIndex INT
, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output (Item)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
GO
then you can adapt the query as follows
create table #temp(
id int,
test varchar(20)
)
insert into #temp (id,test) values (1,'1,2,3')
SELECT t.id, count(sf.Item)
FROM #temp AS t
CROSS APPLY dbo.SplitString(t.test,',') AS sf
group by t.id;
drop table #temp
I have multiple values in one cell in a table which are separated by an space from each other. this is somehow how my table looks like, there is a space in between each string in every cell:
column1 | column2
|
abc fgt | rty lkj
I want to create another table based on the first table in which "abc" and "rty" are in one row because they both are located in the first place, "fgt" and "lkj" are in another row for the same relational reason (they are in the 2nd place and so on):
column1 | column2
|
abc | rty
fgt | lkj
How can I do that?
You can use a function like this
IF EXISTS(SELECT * FROM sysobjects WHERE ID = OBJECT_ID('UF_CSVToTable'))
DROP FUNCTION UF_CSVToTable
GO
CREATE FUNCTION UF_CSVToTable
(
#psCSString VARCHAR(8000)
)
RETURNS #otTemp TABLE(sID VARCHAR(20),tID VARCHAR(20))
AS
BEGIN
DECLARE #sTemp VARCHAR(10)
DECLARE #tTemp VARCHAR(10)
WHILE LEN(#psCSString) > 0
BEGIN
SET #sTemp = LEFT(#psCSString, ISNULL(NULLIF(CHARINDEX(' ', #psCSString) - 1, -1),
LEN(#psCSString)))
SET #psCSString = SUBSTRING(#psCSString,ISNULL(NULLIF(CHARINDEX(' ', #psCSString), 0),
LEN(#psCSString)) + 1, LEN(#psCSString))
INSERT INTO #otTemp(sID) VALUES (#sTemp)
SET #tTemp = LEFT(#psCSString, ISNULL(NULLIF(CHARINDEX(' ', #psCSString) - 1, -1),
LEN(#psCSString)))
SET #psCSString = SUBSTRING(#psCSString,ISNULL(NULLIF(CHARINDEX(' ', #psCSString), 0),
LEN(#psCSString)) + 1, LEN(#psCSString))
UPDATE #otTemp SET tID=#tTemp WHERE sID=#sTemp
END
RETURN
END
Go
It can be called like this.
select * from UF_CSVToTable('1 2 3 4 5 6 7 15 55 59 86')
You need to pass your column as input parameter.
SQL FIDDLE DEMO
First of all, you can create UDF for splitting values by specific delimiter (in your case this is SPACE):
CREATE FUNCTION [dbo].[Split](#String nvarchar(4000), #Delimiter char(1))
returns #temptable TABLE (rownumber INT , items nvarchar(4000))
as
begin
declare #idx int
declare #slice nvarchar(4000)
DECLARE #rownumber int = 1
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(rownumber, Items) values(#rownumber, #slice)
set #String = right(#String,len(#String) - #idx)
SET #rownumber = #rownumber + 1
if len(#String) = 0 break
end
return
end
GO
After that, it is enough to write simple INSERT INTO SELECT... query:
INSERT INTO dbo.Table_2
( column1, column2 )
SELECT
item1.items ,
item2.items
FROM dbo.Split((SELECT column1 FROM dbo.Table_1), SPACE(1)) AS item1
FULL JOIN dbo.Split((SELECT column2 FROM dbo.Table_1), SPACE(1)) AS item2
ON item1.rownumber = item2.rownumber
Table_1 is table where actually values are, Table_2 is table for final rows.
SQL Fiddle Demo
I have User table, it has UserId uniqueidentifier, Name varchar and IsActive bit.
I want to create store procedure to set IsActive to false for many user, for example, if I want to deactive 2 users, I want to send Guid of those users to store procedure (prefer as array). I want to know how can I do it?
P.S. I'm working on Microsoft SQL Azure
Along the same lines than Elian, take a look at XML parameters. Generally speaking you should have a cleaner/safer implementation using xml than parsing a list of strings. Click here for a code example
Here is a solution I used a while ago and that was working fine.
Send the list of guid you want to deactive merged into a comma separated string to the sp.
Then in the sp, you first convert this string into a table thanks to a table-valued function.
Here is a sample with bigint, but you can easily modify it so that it works with guid
Step 1 : the table-valued function
CREATE FUNCTION [dbo].[BigIntListToTable] (
#list VARCHAR(max)
)
RETURNS
#tbl TABLE
(
nval BIGINT NOT NULL
) AS
BEGIN
DECLARE #nPos INT
DECLARE #nNextPos INT
DECLARE #nLen INT
SELECT #nPos = 0, #nNextPos = 1
WHILE #nNextPos > 0
BEGIN
SELECT #nNextPos = CHARINDEX(',', #list, #nPos + 1)
SELECT #nLen = CASE WHEN #nNextPos > 0
THEN #nNextPos
ELSE LEN(#list) + 1
END - #nPos - 1
INSERT #tbl (nval)
VALUES (CONVERT(BIGINT, SUBSTRING(#list, #nPos + 1, #nLen)))
SELECT #nPos = #nNextPos
END
RETURN
END
Step 2 : the stored proc
CREATE PROCEDURE [dbo].[spMySP]
#IdList VARCHAR(max)
AS
BEGIN
SET NOCOUNT ON;
SET ROWCOUNT 0
UPDATE dbo.YourTable
SET isActive = 0
FROM dbo.YourTable
INNER JOIN dbo.BigIntListToTable(#IdList) l
ON dbo.YourTable.id = l.nval
END
I found a few threads on this using the search feature, but nothing for a purely T-SQL solution.
the need - A system is storing a weekly schedule as 0's and 1's in a string format to represent a week. 1 means yes, 0 means no....so 1100111 means sunday yes (first one), Monday yes (second 1), Tuesday no (the 0)...etc.
Short question - How do I go from an ascii char such as '>' to it's hex code '3E' and ultimately to it's binary '00111110' representation?
Long question - I'm extracting from a flat file system that stores a table as:
ID int,
priority_1 varchar(2)
...
It actually goes to priroity_128 (silly flat file), but I'm only interested in 1-7 and the logic for one should be easily reused for the others. I unfortunately have no control over this part of the extract. The values I get look like:
1 >
2 (edit, I actually put a symbol here that I receive from the system but the forum doesn't like.)
3 |
4 Y
I get the feeling these are appearing as their ascii chars because of the conversion as I extract.
select convert(varbinary,'>',2)
This returns 0x3E. The 0x part can be ignored... 3 in binary is 0011 and E is 1110...3E = 00111110. Trim the first 0 and it leaves the 7 bit code that I'm looking for. Unfortunately I have no idea how to express this logic here in T-SQL. Any ideas? I'm thinking as a function would be easiest to use...something like:
select id, binaryversionof(priority_1)
Here's a UDF that will convert from base-10 to any other base, including base-2...
Here's how you can use it:
SELECT YourDatabase.dbo.udf_ConvertFromBase10(convert(varbinary, '>', 2), 2)
Here's what it returns:
111110
And here's the function definition:
CREATE FUNCTION [dbo].[udf_ConvertFromBase10]
(
#num INT,
#base TINYINT
)
RETURNS VARCHAR(255)
AS
BEGIN
-- Check for a null value.
IF (#num IS NULL)
RETURN NULL
-- Declarations
DECLARE #string VARCHAR(255)
DECLARE #return VARCHAR(255)
DECLARE #finished BIT
DECLARE #div INT
DECLARE #rem INT
DECLARE #char CHAR(1)
-- Initialize
SELECT #string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
SELECT #return = CASE WHEN #num <= 0 THEN '0' ELSE '' END
SELECT #finished = CASE WHEN #num <= 0 THEN 1 ELSE 0 END
SELECT #base = CASE WHEN #base < 2 OR #base IS NULL THEN 2 WHEN #base > 36 THEN 36 ELSE #base END
-- Loop
WHILE #finished = 0
BEGIN
-- Do the math
SELECT #div = #num / #base
SELECT #rem = #num - (#div * #base)
SELECT #char = SUBSTRING(#string, #rem + 1, 1)
SELECT #return = #char + #return
SELECT #num = #div
-- Nothing left?
IF #num = 0 SELECT #finished = 1
END
-- Done
RETURN #return
END
Your solution returns a string of a variable length. Not sure whether it was by design or you simply overlooked that fact.
Anyway, here's my solution, which always returns 7 0s or 1s:
CREATE FUNCTION fnIntTo7Bits (#Value int)
RETURNS varchar(7)
AS BEGIN
DECLARE #Bits varchar(7);
SELECT #Bits = COALESCE(#Bits, '') + CAST(CAST(#Value & number AS bit) AS varchar)
FROM master..spt_values
WHERE type = 'P' AND number IN (1, 2, 4, 8, 16, 32, 64)
ORDER BY number DESC;
RETURN #Bits;
END;
The master..spt_values table is a system table used internally but also accessible to the user. It seems to have been inherited from Sybase so it's a very old tool, which, to my mind, means it won't go too soon.
But if you like, you can use your own number table, which you don't even have to materialise, like this:
...
SELECT #Bits = COALESCE(#Bits, '') + CAST(CAST(#Value & number AS bit) AS varchar)
FROM (
SELECT 1 UNION ALL SELECT 2 UNION ALL
SELECT 4 UNION ALL SELECT 8 UNION ALL
SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 64
) s (number)
ORDER BY number DESC;
...
Answering my own question...though curious if anyone has something more elegant. I found this unsourced function using google:
CREATE FUNCTION udf_bin_me (#IncomingNumber int)
RETURNS varchar(200)
as
BEGIN
DECLARE #BinNumber VARCHAR(200)
SET #BinNumber = ''
WHILE #IncomingNumber <> 0
BEGIN
SET #BinNumber = SUBSTRING('0123456789', (#IncomingNumber % 2) + 1, 1) + #BinNumber
SET #IncomingNumber = #IncomingNumber / 2
END
RETURN #BinNumber
END
Then use the Ascii function to get the char to it's ascii decimal value:
select dbo.udf_bin_me(ascii('>'))
Seems to be a bit of a run around, but I can work from that. Better solution anyone?
I just whipped this up, it maybe buggy... but it works:
DECLARE #value INT, #binary VARCHAR(10)
SELECT #value = ASCII('m'), #binary = ''
;WITH [BINARY] ([Location], [x], [BIT])
AS
(
-- Base case
SELECT 64, #value, #value % 2
UNION ALL
-- Recursive
SELECT [BINARY].[Location] / 2, [BINARY].[x] / 2, ([BINARY].[x] / 2) % 2
FROM [BINARY]
WHERE [BINARY].[Location] >= 2
)
SELECT #binary = CAST([BIT] AS CHAR(1)) + #binary FROM [BINARY]
SELECT #binary