I have a simple table that has the following columns. I want to create a WHILE loop that will return each column like so. How can I do this?
create table #bracket_example (row_num int identity(0,1), [0] int, [1] int, [2] int, [3] int)
insert #bracket_example values (25,35,45,55)
insert #bracket_example values (32,53,98,67)
insert #bracket_example values (33,44,55,66)
insert #bracket_example values (11,14,15,16)
declare #j int
set #j = 0
while #j < 4
begin
select '[' + cast(#j as varchar(2)) + ']' from #bracket_example
set #j = #j + 1
end
so instead of listing the values, it's just listing the column names. How can I get this to list values instead?
Try this:
create table #bracket_example (row_num int identity(0,1), [0] int, [1] int, [2] int, [3] int)
insert #bracket_example values (25,35,45,55)
insert #bracket_example values (32,53,98,67)
insert #bracket_example values (33,44,55,66)
insert #bracket_example values (11,14,15,16)
declare #j int
declare #DynSQL varchar(50)
set #j = 0
while #j < 4
begin
set #DynSQL = 'select [' + cast(#j as varchar(2)) + '] as Column_' + cast(#j as varchar(10)) + ' from #bracket_example'
EXEC (#DynSQL)
set #j = #j + 1
end
If I understand correctly you need to use DynamicSql. Please try this :
while #j < 4
begin
EXEC sp_executesql 'select [' + cast(#j as varchar(2)) + '] from #bracket_example'
set #j = #j + 1
end
Related
so I was trying to create a index fragmentation stored procedure is TSQL but I am encoutering an error and can not wrap my head around this problem
the code i used is:
create or alter proc sp_maintain_index (#db nvarchar(50), #table nvarchar(50),
#index nvarchar(50)) as
begin
declare #ext_frg float
declare #int_frg float
declare #string1 nvarchar(500)
declare #string2 nvarchar(500)
set #string1 = N'select avg(avg_fragmentation_in_percent)
from sys.dm_db_index_physical_stats
(DB_ID(''' + #db + N'''), object_id(''' + #table + N'''),
Object_ID(''' + #index + N'''), null, ''Detailed'') group by object_id'
exec sp_executesql #string1, N'#ext_frg float out', #ext_frg OUTPUT
set #string2 = N'select avg(avg_page_space_used_in_percent)
from sys.dm_db_index_physical_stats
(DB_ID(''' + #db + N'''), object_id(''' + #table + N'''),
Object_ID(''' + #index + N'''), null, ''Detailed'') group by object_id'
exec sp_executesql #string2, N'#int_frg float out', #int_frg OUTPUT
declare #string nvarchar(500)
set #string = N'alter index ' + #index + N' on ' + #table
begin
if (#ext_frg >= 5 and #ext_frg <=30) or ((100 - #int_frg) < 70)
set #string = #string + N' reogranize'
else if (#ext_frg > 30) or ((100 - #int_frg) < 30)
set #string = #string + N' rebuild'
end
execute(#string)
end
go
exec dbo.sp_maintain_index N'virtual29', N'dbo.person', N'randomid'
Works now. Thanks for the flags. I messed up the dynamic sql, and used nvarchar instead for the variables.
How to generate a matrix of random numbers where the values in each row add up to X in T-SQL?
The solution matrix should be dynamic:
User can specify number of columns to be returned in the result
User can specify number of rows to be returned in the result
Each row must sum to X (eg. 1)
create proc RandomNumberGenerator
(
#rows int
, #cols int
, #rowsumtotal float
)
as
....
First create a UDF...
CREATE FUNCTION [dbo].[_ex_fn_SplitToTable] (#str varchar(5000), #sep varchar(1) = null)
RETURNS #ReturnVal table (n int, s varchar(5000))
AS
/*
Alpha Test
-----------
select * from [dbo].[_ex_fn_SplitToTable_t2]('a b c d e',' ')
*/
BEGIN
if #sep = ' '
begin
set #sep = CHAR(167)
set #str = REPLACE(#str,' ',CHAR(167))
end
declare #str2 varchar(5000)
declare #sep2 varchar(1)
if LEN(ISNULL(#sep,'')) = 0
begin
declare #i int
set #i = 0
set #str2 = ''
declare #char varchar(1)
startloop:
set #i += 1
--print #i
set #char = substring(#str,#i,1)
set #str2 = #str2 + #char + ','
if LEN(#str) <= #i
goto exitloop
goto startloop
exitloop:
set #str2 = left(#str2,LEN(#str2) - 1)
set #sep2 = ','
--print #str2
end
else
begin
set #str2 = #str
set #sep2 = #sep
end
;WITH Pieces(n, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep2, #str2)
UNION ALL
SELECT n + 1, stop + 1, CHARINDEX(#sep2, #str2, stop + 1)
FROM Pieces
WHERE stop > 0
)
insert into #ReturnVal(n,s)
SELECT n,
SUBSTRING(#str2, start, CASE WHEN stop > 0 THEN stop-start ELSE 5000 END) AS s
FROM Pieces option (maxrecursion 32767)
RETURN
END
GO
Then, create this stored proc...
CREATE PROC [dbo].[RandomNumberGenerator]
(
#Pockets int = 6,
#SumTo float = 100,
#i_iterations int = 100
)
/*
ALPHA TEST
----------
exec RandomNumberGenerator 10, 100, 500
*/
AS
if object_id('tempdb..#_Random_00') is not null drop table #_Random_00
declare #columnstring varchar(max) = (SELECT REPLICATE('c ',#Pockets) as Underline)
print #columnstring
if object_id('tempdb..#_Random_columns') is not null drop table #_Random_columns
select s+CONVERT(varchar,dbo.PadLeft(convert(varchar,n),'0',3)) cols
into #_Random_columns
from [dbo].[_ex_fn_SplitToTable](#columnstring,' ') where LEN(s) > 0
-- ===========================
--select * from #_Random_columns
-- ===========================
declare #columns_sql varchar(max)
set #columns_sql =
(
select distinct
stuff((SELECT distinct + cast(cols as varchar(50)) + ' float, '
FROM (
select cols
from #_Random_columns
) t2
--where t2.n = t1.n
FOR XML PATH('')),3,0,'')
from (
select cols
from #_Random_columns
) t1
)
set #columns_sql = LEFT(#columns_sql,LEN(#columns_sql) - 1)
print #columns_sql
declare #sql varchar(max)
set #sql = 'if object_id(''tempdb..##_proctable_Random_01'') is not null drop table ##_proctable_Random_01 '
print #sql
execute(#sql)
set #sql = 'create table ##_proctable_Random_01 (rowid int,' + #columns_sql + ')'
print #sql
execute(#sql)
declare #TotalOfRand float
declare #i_inner int
declare #i_outer int
set #i_outer = 0
start_outer:
set #i_outer = #i_outer + 1
set #i_inner = 0
declare #sumstring varchar(max)
set #sumstring = ''
start_inner:
set #i_inner = #i_inner+1
set #sumstring = #sumstring + CONVERT(varchar, rand()) + ','
if #i_inner >= #Pockets
goto exit_inner
goto start_inner
exit_inner:
set #TotalOfRand = ( select sum(convert(float,s)) from dbo._ex_fn_SplitToTable(#sumstring,',') )
declare #sumstring_quotient varchar(max)
set #sumstring_quotient = replace(#sumstring,',', '/' + Convert(varchar,#TotalOfRand) + '*' + convert(varchar,#SumTo) + ',')
set #sumstring_quotient = LEFT(#sumstring_quotient,len(#sumstring_quotient) - 1)
print #sumstring_quotient
set #sql = '
insert into ##_proctable_Random_01
select
( select count(*) + 1 from ##_proctable_Random_01 ) rowid,' + #sumstring_quotient
execute(#sql)
if #i_outer >= #i_iterations
goto exit_outer
goto start_outer
exit_outer:
select * from ##_proctable_Random_01
drop table ##_proctable_Random_01
GO
I have an table include 1 column ID and 12 columns month (Month01 to Month12). If i have an parameter #month=9, how can i compare to get column Month01 to Month09 without Month10, Month11, Month12. Please help me!
Simo's answer is more elegant, but below is an alternative "basic" way to do this:
DECLARE #month INT
SET #month = 9
IF #Month = 1
BEGIN
SELECT Id, Month01
FROM MonthlyData
END
IF #Month = 2
BEGIN
SELECT Id, Month01, Month02
FROM MonthlyData
END
-- Repeat for months 3-9
IF #Month = 9
BEGIN
SELECT Id, Month01, Month02, Month03, Month04,
Month05, Month06, Month07, Month08, Month09
FROM MonthlyData
END
-- Repeat for months 10-12
See my SQL Fiddle.
declare #str varchar(1000)
SELECT #str= coalesce(#str + ', ', '') + a.name
FROM (
Select name from sys.columns
where object_id = OBJECT_ID('table_months')
and isnumeric(right(name,2))= 1 and name like 'month%'
and cast(right(name,2) as int) < '03') a
-- Instead of '03' you use a variable and assign required moths number
declare #sql nvarchar(100)
set #sql = 'select ID, '+ #str+' from table_months'
exec sp_executesql #sql
you may try this out also :)
DECLARE #month int = 9
DECLARE #Sql nvarchar(100) =''
DECLARE #pos int = 1
WHILE(#pos <= #month)
BEGIN
SET #Sql = #Sql + (',month' + right('00'+ rtrim(#pos), 2))
SET #pos = #pos + 1
END
SET #Sql= 'SELECT ID ' +#Sql + ' FROM table'
EXEC sp_executesql #Sql
DECLARE #month int
DECLARE #tableName nvarchar(128)
SET #month = 9;
SET #tableName = 'months' -- YOUR TABLE's NAME
DECLARE #query nvarchar(2048)
SET #query =
'SELECT id, ' + STUFF((SELECT DISTINCT ', ' + name
FROM sys.columns WHERE name LIKE'Month%'
AND object_id = OBJECT_ID(#tableName)
AND CONVERT(INT, RIGHT(name, 2)) <= #month
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
+ ' FROM ' + #tableName;
EXEC(#query)
Say I have the following string value:
declare #cs nvarchar(100) =
'Data Source=server\instance;Initial Catalog=MyDatabase;Integrated Security=True';
What is the T-SQL to extract the string MyDatabase from #cs?
Alternately, what T-SQL function(s) should I look at for a means to figure it out for myself?
It's not pretty and i'm sure you could do this faster using regex / clr patindex & substrings, but a very quick and easy way is to use the split functions below to do something like this:
Example Usage:
DECLARE #cs NVARCHAR(100) = 'Data Source=server\instance;Initial Catalog=MyDatabase;Integrated Security=True';
SELECT id ,
Data ,
dbo.fnParseString(2, '=', Data)
FROM dbo.fnc_Split(#cs, ';')
WHERE Data LIKE '%Initial Catalog%'
Returns:
2 Initial Catalog=MyDatabase MyDatabase
As for the split functions, the first splts strings into tables, and the second splits strings into columns:
Split into tables:
CREATE FUNCTION dbo.fnc_Split
(
#Data VARCHAR(2000) ,
#Sep VARCHAR(5)
)
RETURNS #Temp TABLE
(
Id INT IDENTITY(1, 1) ,
Data NVARCHAR(100)
)
AS
BEGIN
DECLARE #Cnt INT
SET #Cnt = 1
WHILE ( CHARINDEX(#Sep, #Data) > 0 )
BEGIN
INSERT INTO #Temp
( data
)
SELECT Data = LTRIM(RTRIM(SUBSTRING(#Data, 1, CHARINDEX(#Sep, #Data) - 1)))
SET #Data = SUBSTRING(#Data, CHARINDEX(#Sep, #Data) + 1, LEN(#Data))
SET #Cnt = #Cnt + 1
END
INSERT INTO #Temp
( data )
SELECT Data = LTRIM(RTRIM(#Data))
RETURN
END
GO
Split into columns:
CREATE FUNCTION dbo.fnParseString
(
#Section SMALLINT ,
#Delimiter CHAR ,
#Text VARCHAR(MAX)
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #NextPos SMALLINT ,
#LastPos SMALLINT ,
#Found SMALLINT
--#### Uncomment the following 2 lines to emulate PARSENAME functionality
--IF #Section > 0
-- SELECT #Text = REVERSE(#Text)
SELECT #NextPos = CHARINDEX(#Delimiter, #Text, 1) ,
#LastPos = 0 ,
#Found = 1
WHILE #NextPos > 0
AND ABS(#Section) <> #Found
SELECT #LastPos = #NextPos ,
#NextPos = CHARINDEX(#Delimiter, #Text, #NextPos + 1) ,
#Found = #Found + 1
RETURN CASE
WHEN #Found <> ABS(#Section) OR #Section = 0 THEN NULL
--#### Uncomment the following lines to emulate PARSENAME functionality
--WHEN #Section > 0 THEN REVERSE(SUBSTRING(#Text, #LastPos + 1, CASE WHEN #NextPos = 0 THEN DATALENGTH(#Text) - #LastPos ELSE #NextPos - #LastPos - 1 END))
WHEN #Section > 0 THEN SUBSTRING(#Text, #LastPos + 1, CASE WHEN #NextPos = 0 THEN DATALENGTH(#Text) - #LastPos ELSE #NextPos - #LastPos - 1 END)
ELSE SUBSTRING(#Text, #LastPos + 1, CASE WHEN #NextPos = 0 THEN DATALENGTH(#Text) - #LastPos ELSE #NextPos - #LastPos - 1 END)
END
END
you might find this to be a lot easier, just change the from_table to your table and the connection string field to your field.
SELECT SUBSTRING(SUBSTRING([ConnectionString], CHARINDEX('Initial Catalog=', [ConnectionString]) + LEN('Initial Catalog='), 100), 0, CHARINDEX(';', SUBSTRING([ConnectionString], CHARINDEX('Initial Catalog=', [ConnectionString]) + LEN('Initial Catalog='), 100)))
AS DbName
FROM [from_table]
i assume after taking the strings you might want to do something like this:
(iteration over the names and query some dbs)
DECLARE #DbName varchar(max)
DECLARE #QaQuery NVARCHAR(MAX);
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT SUBSTRING(SUBSTRING([ConnectionString], CHARINDEX('Initial Catalog=', [ConnectionString]) + LEN('Initial Catalog='), 100), 0, CHARINDEX(';', SUBSTRING([ConnectionString], CHARINDEX('Initial Catalog=', [ConnectionString]) + LEN('Initial Catalog='), 100)))
AS Connections
FROM [ConnTable]
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #DbName
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT #DbName
SET #QaQuery = N'SELECT TOP 10 [RecordID],[SomeStuff]
FROM ['+#DbName+'].[dbo].[SomeTable]
WHERE [SomeStuff] IS NOT NULL'
EXEC sp_executesql #QaQuery;
FETCH NEXT FROM MY_CURSOR INTO #DbName
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
The question is self explanatory. Could you please point out a way to put spaces between each capital letter of a string.
SELECT dbo.SpaceBeforeCap('ThisIsATestString')
would result in
This Is A Test String.
This will add spaces only if the previous and next character is lowercase. That way 'MyABCAnalysis' will be 'My ABC Analysis'.
I added a check for a previous space too. Since some of our strings are prefixed with 'GR_' and some also contain underscores, we can use the replace function as follows:
select dbo.GR_SpaceBeforeCap(replace('GR_ABCAnalysis_Test','_',' '))
Returns 'GR ABC Analysis Test'
CREATE FUNCTION GR_SpaceBeforeCap (
#str nvarchar(max)
)
returns nvarchar(max)
as
begin
declare
#i int, #j int
, #cp nchar, #c0 nchar, #c1 nchar
, #result nvarchar(max)
select
#i = 1
, #j = len(#str)
, #result = ''
while #i <= #j
begin
select
#cp = substring(#str,#i-1,1)
, #c0 = substring(#str,#i+0,1)
, #c1 = substring(#str,#i+1,1)
if #c0 = UPPER(#c0) collate Latin1_General_CS_AS
begin
-- Add space if Current is UPPER
-- and either Previous or Next is lower
-- and Previous or Current is not already a space
if #c0 = UPPER(#c0) collate Latin1_General_CS_AS
and (
#cp <> UPPER(#cp) collate Latin1_General_CS_AS
or #c1 <> UPPER(#c1) collate Latin1_General_CS_AS
)
and #cp <> ' '
and #c0 <> ' '
set #result = #result + ' '
end -- if #co
set #result = #result + #c0
set #i = #i + 1
end -- while
return #result
end
Assuming SQL Server 2005 or later, this modified from code taken here: http://www.kodyaz.com/articles/case-sensitive-sql-split-function.aspx
CREATE FUNCTION SpaceBeforeCap
(
#str nvarchar(max)
)
returns nvarchar(max)
as
begin
declare #i int, #j int
declare #returnval nvarchar(max)
set #returnval = ''
select #i = 1, #j = len(#str)
declare #w nvarchar(max)
while #i <= #j
begin
if substring(#str,#i,1) = UPPER(substring(#str,#i,1)) collate Latin1_General_CS_AS
begin
if #w is not null
set #returnval = #returnval + ' ' + #w
set #w = substring(#str,#i,1)
end
else
set #w = #w + substring(#str,#i,1)
set #i = #i + 1
end
if #w is not null
set #returnval = #returnval + ' ' + #w
return ltrim(#returnval)
end
This can then be called just as you have suggested above.
This function combines previous answers. Selectively choose to preserve adjacent CAPS:
CREATE FUNCTION SpaceBeforeCap (
#InputString NVARCHAR(MAX),
#PreserveAdjacentCaps BIT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE
#i INT, #j INT,
#previous NCHAR, #current NCHAR, #next NCHAR,
#result NVARCHAR(MAX)
SELECT
#i = 1,
#j = LEN(#InputString),
#result = ''
WHILE #i <= #j
BEGIN
SELECT
#previous = SUBSTRING(#InputString,#i-1,1),
#current = SUBSTRING(#InputString,#i+0,1),
#next = SUBSTRING(#InputString,#i+1,1)
IF #current = UPPER(#current) COLLATE Latin1_General_CS_AS
BEGIN
-- Add space if Current is UPPER
-- and either Previous or Next is lower or user chose not to preserve adjacent caps
-- and Previous or Current is not already a space
IF #current = UPPER(#current) COLLATE Latin1_General_CS_AS
AND (
#previous <> UPPER(#previous) COLLATE Latin1_General_CS_AS
OR #next <> UPPER(#next) collate Latin1_General_CS_AS
OR #PreserveAdjacentCaps = 0
)
AND #previous <> ' '
AND #current <> ' '
SET #result = #result + ' '
END
SET #result = #result + #current
SET #i = #i + 1
END
RETURN #result
END
GO
SELECT dbo.SpaceBeforeCap('ThisIsASampleDBString', 1)
GO
SELECT dbo.SpaceBeforeCap('ThisIsASampleDBString', 0)
CLR and regular expressions or 26 replace statements a case sensitive collate clause and a trim.
Another strategy would be to check the ascii value of each character:
create function SpaceBeforeCap
(#str nvarchar(max))
returns nvarchar(max)
as
begin
declare #result nvarchar(max)= left(#str, 1),
#i int = 2
while #i <= len(#str)
begin
if ascii(substring(#str, #i, 1)) between 65 and 90
select #result += ' '
select #result += substring(#str, #i, 1)
select #i += 1
end
return #result
end
/***
SELECT dbo.SpaceBeforeCap('ThisIsATestString')
**/
To avoid loops altogether, use of a tally table can help here. If you are running on SQL 2022, then the generate_series function can remove even this dependency. This method will be significantly faster than iterating through a loop.
create function core.ufnAddSpaceBeforeCapital
(
#inputString nvarchar(max)
)
returns nvarchar(max)
as
begin
declare #outputString nvarchar(max)
select
#outputString = string_agg(iif(t.value = 1, upper(substring(#inputString,t.value,1)),iif(ascii(substring(#inputString,t.value,1)) between 65 and 90, ' ','') + substring(#inputString,t.value,1)),'')
from
generate_series(1,cast(len(#inputString) as int)) t
return #outputString
end
The scalar function is not inlineable, so I've provided an alternative inline table-valued function if that's what you need.
create function core.ufnAddSpaceBeforeCapitalITVF
(
#inputString nvarchar(max)
)
returns table
as
return
(
select
string_agg(iif(t.value = 1, upper(substring(#inputString,t.value,1)),iif(ascii(substring(#inputString,t.value,1)) between 65 and 90, ' ','') + substring(#inputString,t.value,1)),'') as outputString
from
generate_series(1,cast(len(#inputString) as int)) t
)
end
While I really like the char looping answers I was not thrilled with the performance. I have found this performs in a fraction of the time for my use case.
CREATE function SpaceBeforeCap
(#examine nvarchar(max))
returns nvarchar(max)
as
begin
DECLARE #index as INT
SET #index = PatIndex( '%[^ ][A-Z]%', #examine COLLATE Latin1_General_BIN)
WHILE #index > 0 BEGIN
SET #examine = SUBSTRING(#examine, 1, #index) + ' ' + SUBSTRING(#examine, #index + 1, LEN(#examine))
SET #index = PatIndex( '%[^ ][A-Z]%', #examine COLLATE Latin1_General_BIN)
END
RETURN LTRIM(#examine)
end
This makes use of the fact that
case sensitive pattern search only works in some collations. The character class [^ ] means anything except space, so as we add the missing spaces we match farther into the string until it is complete.