Convert Rows to Columns SQL 2008 - sql

I just want to transpose following table
RegionID Region RedionCode RegionSupervisor
1 Eastern E01 Mark
2 Western W01 Jim
3 Northern N01 Paul
4 Southern S01 David
to
Eastern Western Northern Southern
1 2 3 4
E01 W01 N01 S01
Mark Jim Paul David
I use SQL 2008. Any help would be really appreciated
cheers!

You can do this using the PIVOT and UNPIVOT tsql commands.

You can use PIVOT, but you will also need to incorporate dynamic SQL, as PIVOT by itself will only support situations where you know in advance the full set of columns in the resultant table. Pivots with Dynamic Columns in SQL Server 2005

Ok guys, finally I found a way of doing it but possibly not the more effective way. I could not find a solution with PIVOT yet
BEGIN
DECLARE #ColumnList varchar(200)
DECLARE #ColumnInList varchar(200)
DECLARE #TableName varchar(20)
DECLARE #TableScript varchar(2000)
SET #ColumnList = ''
SET #ColumnInList = ''
SELECT #ColumnInList += RTRIM(RegionDescription) + ',', #ColumnList += '[' + RTRIM(RegionDescription) + '] varchar(50) , '
FROM RegionSup
SET #ColumnList = LEFT(#ColumnList, LEN(#ColumnList) - 1)
SET #ColumnInList = LEFT(#ColumnInList, LEN(#ColumnInList) - 1)
SELECT #TableName = 'TEMP' + CONVERT(char(12),GETDATE(),14);
SELECT #TableName = REPLACE(#TableName,':','')
SET #TableScript = 'CREATE TABLE ' + #TableName + ' (' +
#ColumnList + ')'
EXECUTE (#TableScript)
--Column Values
DECLARE #RegionID varchar(30)
DECLARE #RegionSupervisor varchar(50)
DECLARE #RegionCode varchar(50)
--End Column Values
SET #RegionID = ''
SET #RegionSupervisor = ''
SET #RegionCode = ''
SELECT #RegionID += '''' + CONVERT(varchar(10),RegionID) + ''',',
#RegionSupervisor += '''' + RegionSupervisor + ''',',
#RegionCode += '''' + RegionCode + ''','
FROM RegionSup
SET #RegionID = LEFT(#RegionID,LEN(#RegionID) - 1)
SET #RegionSupervisor = LEFT(#RegionSupervisor,LEN(#RegionSupervisor) - 1)
SET #RegionCode = LEFT(#RegionCode,LEN(#RegionCode) - 1)
DECLARE #InsertStatement nvarchar(max)
SET #InsertStatement = ''
SET #InsertStatement = 'INSERT INTO ' + #TableName + '(' + #ColumnInList + ') VALUES ' +
'(' + #RegionID + '),' +
'(' + #RegionSupervisor + '),' +
'(' + #RegionCode + ')'
EXECUTE(#InsertStatement)
EXECUTE('SELECT * FROM ' + #TableName)
EXECUTE('DROP TABLE ' + #TableName)
END

Related

Replacing more than 1 period to 1 period in sql

I have the following code to convert more than one period to one period in a column of a table.
alter proc replace_characters_1
#COLUMN_NAME varchar(30),
#TABLE_NAME varchar(30)
as
declare #SQL varchar(MAX)
while #COLUMN_NAME like '%..%'
begin
set #SQL= 'update [' +#TABLE_NAME+ '] set [' +#COLUMN_NAME+ '] = replace([' +#COLUMN_NAME+ '],''..'',''.'')';
exec(#SQL)
end
I want to change the Anna...Amal to Anna.Amal with one go, but the loop is not working. What should I do?`
One possible approach is to use nested REPLACE()s:
SET ColumnName = REPLACE(REPLACE(REPLACE(ColumnName, '.', '<>'), '><', ''), '<>', '.')
After the first REPLACE() the part from the text that contains periods (.) looks like <><><>. After the second REPLACE() the result is only <> and the final REPLACE() returns single period (.). If the characters < and > exist in the input text, you can choose another pair of characters.
Table:
CREATE TABLE Data (Name varchar(100))
INSERT INTO Data (Name)
VALUES
('ANNA..Amal'),
('ANNA..Amal.'),
('ANNA.Amal.'),
('ANNA...........Amal.'),
('ANNA.....Amal')
Procedure:
CREATE PROC replace_characters_1
#COLUMN_NAME sysname,
#TABLE_NAME sysname
AS
BEGIN
DECLARE #SQL nvarchar(MAX)
DECLARE #RC int
SET #SQL =
N'UPDATE ' + QUOTENAME(#TABLE_NAME) + N' ' +
N'SET ' + QUOTENAME(#COLUMN_NAME) + N' = ' +
N'REPLACE(REPLACE(REPLACE(' + QUOTENAME(#COLUMN_NAME) + ', ''.'', ''<>''), ''><'', ''''), ''<>'', ''.'') ' +
N'WHERE ' + QUOTENAME(#COLUMN_NAME) + N' LIKE ''%..%'''
EXEC #RC = sp_executesql #SQL
RETURN #RC
END
Result:
EXEC replace_characters_1 N'Name', N'Data'
SELECT * FROM Data
Name
ANNA.Amal
ANNA.Amal.
ANNA.Amal.
ANNA.Amal.
ANNA.Amal
Here is an approach that will reduce repeating characters.
Example
Declare #YourTable Table ([SomeCol] varchar(50))
Insert Into #YourTable Values
('Anna...Amal')
,('Anna........Amal')
,('Anna.Amal')
,('Anna Amal')
Select *
,NewVal = replace(replace(replace(SomeCol,'.','†‡'),'‡†',''),'†‡','.')
from #YourTable
Returns
SomeCol NewVal
Anna...Amal Anna.Amal
Anna........Amal Anna.Amal
Anna.Amal Anna.Amal
Anna Amal Anna Amal
Please check Zhorov's answer as it avoids multiple operations like this one.
CREATE PROCEDURE replace_characters_1
#COLUMN_NAME varchar(30),
#TABLE_NAME varchar(30)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX) = N'
UPDATE T SET
' + QUOTENAME(#COLUMN_NAME) + N' = REPLACE(' + QUOTENAME(#COLUMN_NAME) + N',''..'',''.'')
FROM
' + QUOTENAME(#TABLE_NAME) + N' AS T
WHERE
T.' + QUOTENAME(#COLUMN_NAME) + N' LIKE ''%..%'';
SET #UpdatedRowsOut = ##ROWCOUNT;';
DECLARE #UpdatedRows INT = 1;
WHILE #UpdatedRows > 0
BEGIN
EXECUTE sp_executesql
#SQL,
N'#UpdatedRowsOut INT OUTPUT',
#UpdatedRowsOut = #UpdatedRows OUTPUT;
END
END
Dynamic SQL is now returning the amount of rows that were updated, so it keeps going as long as there are values with .. for that column (note that there is a WHERE filter, you don't want to update all rows every time!).
SQL Server isn't the best for regex expressions, maybe you want to consider using a CLR function if you need to do different kind of stuff with regex.
I am using CHARINDEX and STUFF to derive the resultset
DECLARE #sqlstring Varchar(max) = 'Anna...Amal'
-- Getting first index of .
DECLARE #firstidx INT = CHARINDEX('.',#sqlstring)
-- Getting last index of .
Declare #lastidx int = (LEN(#sqlstring) - CHARINDEX('.',REVERSE(#sqlstring))) + 1
-- Stuffing the gap with emptystring
SELECT STUFF(#sqlstring,#firstidx+1,(#lastidx-#firstidx),'') as result
Result
+-----------+
| result |
+-----------+
| Anna.Amal |
+-----------+
UPDATE: If there are multiple comma separated values
DECLARE #sqlstring Varchar(max) = 'Anna...Amal,Vimal...Mathew'
SELECT STRING_agg(removedvalues,',') as values
FROM
(SELECT STUFF(value, CHARINDEX('.',value)+1
,(LEN(value) - CHARINDEX('.',REVERSE(value)) + 1) - CHARINDEX('.',value),'') AS removedvalues
FROM string_split(#sqlstring,',') ) AS t
+------------------------+
| Values |
+------------------------+
| Anna.Amal,Vimal.Mathew |
+------------------------+

Dynamically decide number of joins

I have two tables.
Table 1: Question_Master which contains the questions
id question
1 Q1
2 Q2
3 Q3
Table 2: Option Master Which contains the Options
id option
1 H
2 N
3 S
I want all the combinations of options for all the questions.
Something Like this
Q1 Q2 Q3
H H H
H H N
H H s
H N H
NOTE: There can be any number of records in both table.If it has 4 records in option_master than i want all combination for 4 records.
You can do it dynamically by using some string concatenation queries to build out the Select statement based on the Question_Master table values
DECLARE #SelectSQL VARCHAR(MAX),
#JoinSQL VARCHAR(MAX),
#OrderSQL VARCHAR(MAX)
SELECT #SelectSQL = COALESCE(#SelectSQL + ',', '')
+ QUOTENAME(question) + '.[option] as ' + QUOTENAME(question),
#JoinSQL = COALESCE(#JoinSQL + ' CROSS JOIN ', '')
+ 'Option_Master as ' + QUOTENAME(question),
#OrderSQL = COALESCE(#OrderSql + ',', '')
+ QUOTENAME(question) + '.[option]'
FROM Question_Master
ORDER BY question
DECLARE #Sql AS NVARCHAR(MAX) = N'SELECT ' + #SelectSQL + ' FROM ' + #JoinSQL + ' ORDER BY ' + #OrderSQL
EXECUTE sp_executesql #Sql;
using QUOTENAME will allow you to have questions that have spaces or some other characters in the value.
SQL Fiddle Example
You need to CROSS JOIN the Option_Master with itself. And then you need to cross join the result again with Option_Master. This has to be repeated for each question. I think this has to be done by dynamically creating the SQL statement. Try this example to get an idea:
declare #NumberOfQuestions int
set #NumberOfQuestions = (
select count(*)
from question_master
)
declare #sql varchar(max)
set #sql = 'select om1.opt '
declare #counter int
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
, om' + cast (#counter as varchar(1)) + '.opt '
set #counter = #counter + 1
end
set #sql = #sql + '
from option_master om1 '
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
cross join option_master om' + cast(#counter as varchar(1)) + ' '
set #counter = #counter + 1
end
set #sql = #sql + '
order by om1.opt '
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
, om' + cast(#counter as varchar(1)) + '.opt '
set #counter = #counter + 1
end
exec (#sql)
Albert

Table Columns as parameters to Stored Procedure [duplicate]

I have created a procedure in dynamic SQL which has a select statement and the code looks like:
ALTER PROCEDURE cagroup (
#DataID INT ,
#days INT ,
#GName VARCHAR(50) ,
#T_ID INT ,
#Act BIT ,
#Key VARBINARY(16)
)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SchemaName SYSNAME
DECLARE #TableName SYSNAME
DECLARE #DatabaseName SYSNAME
DECLARE #BR CHAR(2)
SET #BR = CHAR(13) + CHAR(10)
SELECT #SchemaName = Source_Schema ,
#TableName = Source_Table ,
#DatabaseName = Source_Database
FROM Source
WHERE ID = #DataID
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ #T_ID + ' AS To_ID ,' + #BR
+ #DataID + ' AS DataSoID ,' + #BR
+ #Act + ' AS Active ,' + #BR
+ Key + ' AS key' + #BR
+ 'R_ID AS S_R_ID' + #BR
+ 'FROM' + #DatabaseName + '.'
+ #SchemaName + '.'
+ #TableName + ' t' + #BR
+ 'LEFT OUTER JOIN Gro g ON g.GName = '
+ #GName + #BR + 'AND g.Data_ID] =' + #DataID + #BR
+ 't.[I_DATE] > GETDATE() -' + #days + #BR
+ 'g.GName IS NULL
AND ' + #GName + ' IS NOT NULL
AND t.[Act] = 1' + #BR
PRINT (#SQL)
END
When I am executing this procedure with this statement:
Exec dbo.cagroup 1,10,'[Gro]',1,1,NULL
I am getting the following error.
Msg 245, Level 16, State 1, Procedurecagroup, Line 33
Conversion failed when converting the nvarchar value 'SELECT [Gro] AS GName ,
' to data type int.
Where am I doing wrong?
You need to CAST all numbers to nvarchar in the concatenation.
There is no implicit VBA style conversion to string. In SQL Server data type precedence means ints are higher then nvarchar: so the whole string is trying to be CAST to int.
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ CAST(#T_ID AS nvarchar(10)) + ' AS To_ID ,' ...
Edit: Will A has a good point: watch for NULLs!
If you have to build this kind of dynamic SQL, it is better to get the column information from the meta-data than to pass it around.
Select * from Information_Schema.Columns Where Table_name=#TableName
The you have to write an ugly cursor to build the SQL. Expect performance problems. I do lots of this during development to write code for me, but I don't dare run it in production.

Dynamic SQL error converting nvarchar to int

I have created a procedure in dynamic SQL which has a select statement and the code looks like:
ALTER PROCEDURE cagroup (
#DataID INT ,
#days INT ,
#GName VARCHAR(50) ,
#T_ID INT ,
#Act BIT ,
#Key VARBINARY(16)
)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SchemaName SYSNAME
DECLARE #TableName SYSNAME
DECLARE #DatabaseName SYSNAME
DECLARE #BR CHAR(2)
SET #BR = CHAR(13) + CHAR(10)
SELECT #SchemaName = Source_Schema ,
#TableName = Source_Table ,
#DatabaseName = Source_Database
FROM Source
WHERE ID = #DataID
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ #T_ID + ' AS To_ID ,' + #BR
+ #DataID + ' AS DataSoID ,' + #BR
+ #Act + ' AS Active ,' + #BR
+ Key + ' AS key' + #BR
+ 'R_ID AS S_R_ID' + #BR
+ 'FROM' + #DatabaseName + '.'
+ #SchemaName + '.'
+ #TableName + ' t' + #BR
+ 'LEFT OUTER JOIN Gro g ON g.GName = '
+ #GName + #BR + 'AND g.Data_ID] =' + #DataID + #BR
+ 't.[I_DATE] > GETDATE() -' + #days + #BR
+ 'g.GName IS NULL
AND ' + #GName + ' IS NOT NULL
AND t.[Act] = 1' + #BR
PRINT (#SQL)
END
When I am executing this procedure with this statement:
Exec dbo.cagroup 1,10,'[Gro]',1,1,NULL
I am getting the following error.
Msg 245, Level 16, State 1, Procedurecagroup, Line 33
Conversion failed when converting the nvarchar value 'SELECT [Gro] AS GName ,
' to data type int.
Where am I doing wrong?
You need to CAST all numbers to nvarchar in the concatenation.
There is no implicit VBA style conversion to string. In SQL Server data type precedence means ints are higher then nvarchar: so the whole string is trying to be CAST to int.
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ CAST(#T_ID AS nvarchar(10)) + ' AS To_ID ,' ...
Edit: Will A has a good point: watch for NULLs!
If you have to build this kind of dynamic SQL, it is better to get the column information from the meta-data than to pass it around.
Select * from Information_Schema.Columns Where Table_name=#TableName
The you have to write an ugly cursor to build the SQL. Expect performance problems. I do lots of this during development to write code for me, but I don't dare run it in production.

How can I dynamically create columns in SQL select statement

I have 3 tables. Team, Option, OptionTeam.
The Team holds a TeamId and Name
Option holds OptionId, OptionGroup
OptionTeam holds TeamId, OptionId, OptionGroup
select a.TeamId, a.Name
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=4) as Option1,
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=5) as Option2,
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=6) as Option3,
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=11) as Option4
from Team a
I want to get a list of Teams, and extra columns indicating how many options of each group are connected to each Team. This is done by the above query, but I want to replace the 4,5,6,11 with values of OptionGroup from a table Option.
It has to be dynamic, because there might be a new OptionGroup in the future, and I want the stored procedure to be able to handle it.
Sample data:
Team
TeamId
1
2
3
Option
OptionId | OptionGroup
11 | 4
12 | 5
13 | 4
14 | 4
15 | 5
OptionTeam
TeamId | OptionId | OptionGroup
1 | 11 | 4
1 | 13 | 4
2 | 12 | 5
2 | 14 | 4
3 | 15 | 5
And the list I want to get is
TeamId | Group4 (OptionGroup=4) | Group5 (OptionGroup=5)
1 | 2 | 0
2 | 1 | 1
3 | 0 | 1
You'll need a dynamic pivot to do this. Here's the stored procedure:
CREATE PROC [dbo].[pivotsp]
#query AS NVARCHAR(MAX), -- The query, can also be the name of a table/view.
#on_rows AS NVARCHAR(MAX), -- The columns that will be regular rows.
#on_cols AS NVARCHAR(MAX), -- The columns that are to be pivoted.
#agg_func AS NVARCHAR(257) = N'SUM', -- Aggregate function.
#agg_col AS NVARCHAR(MAX), -- Column to aggregate.
#output AS NVARCHAR(257) = N'', -- Table for results
#debug AS bit = 0 -- 1 for debugging
AS
-- Example usage:
-- exec pivotsp
-- 'select * from vsaleshistory',
-- 'market,marketid,family,familyid,Forecaster,Forecasterid,product,productid',
-- 'month',
-- 'sum',
-- 'ku',
-- '##sales'
-- Input validation
IF #query IS NULL OR #on_rows IS NULL OR #on_cols IS NULL
OR #agg_func IS NULL OR #agg_col IS NULL
BEGIN
RAISERROR('Invalid input parameters.', 16, 1);
RETURN;
END
-- Additional input validation goes here (SQL Injection attempts, etc.)
BEGIN TRY
DECLARE
#sql AS NVARCHAR(MAX),
#cols AS NVARCHAR(MAX),
#newline AS NVARCHAR(2);
SET #newline = NCHAR(13) + NCHAR(10);
-- If input is a valid table or view
-- construct a SELECT statement against it
IF COALESCE(OBJECT_ID(#query, N'U'),
OBJECT_ID(#query, N'V')) IS NOT NULL
SET #query = N'SELECT * FROM ' + #query;
-- Make the query a derived table
SET #query = N'(' + #query + N') AS Query';
-- Handle * input in #agg_col
IF #agg_col = N'*'
SET #agg_col = N'1';
-- Construct column list
SET #sql =
N'SET #result = ' + #newline +
N' STUFF(' + #newline +
N' (SELECT N'','' + quotename( '
+ 'CAST(pivot_col AS sysname)' +
+ ') AS [text()]' + #newline +
N' FROM (SELECT DISTINCT('
+ #on_cols + N') AS pivot_col' + #newline +
N' FROM' + #query + N') AS DistinctCols' + #newline +
N' ORDER BY pivot_col' + #newline +
N' FOR XML PATH(''''))' + #newline +
N' ,1, 1, N'''');'
IF #debug = 1
PRINT #sql
EXEC sp_executesql
#stmt = #sql,
#params = N'#result AS NVARCHAR(MAX) OUTPUT',
#result = #cols OUTPUT;
IF #debug = 1
PRINT #cols
-- Create the PIVOT query
IF #output = N''
begin
SET #sql =
N'SELECT *' + #newline +
N'FROM (SELECT '
+ #on_rows
+ N', ' + #on_cols + N' AS pivot_col'
+ N', ' + #agg_col + N' AS agg_col' + #newline +
N' FROM ' + #query + N')' +
+ N' AS PivotInput' + #newline +
N' PIVOT(' + #agg_func + N'(agg_col)' + #newline +
N' FOR pivot_col IN(' + #cols + N')) AS PivotOutput;'
end
ELSE
begin
set #sql = 'IF EXISTS (SELECT * FROM tempdb.sys.objects WHERE ' +
'name = ''' + #output + ''' AND type = N''U'') DROP TABLE tempdb.' + #output
EXEC sp_executesql #sql;
SET #sql =
N'SELECT * INTO ' + #output + #newline +
N'FROM (SELECT '
+ #on_rows
+ N', ' + #on_cols + N' AS pivot_col'
+ N', ' + #agg_col + N' AS agg_col' + #newline +
N' FROM ' + #query + N')' +
+ N' AS PivotInput' + #newline +
N' PIVOT(' + #agg_func + N'(agg_col)' + #newline +
N' FOR pivot_col IN(' + #cols + N')) AS PivotOutput;'
end
IF #debug = 1
PRINT #sql
EXEC sp_executesql #sql;
END TRY
BEGIN CATCH
DECLARE
#error_message AS NVARCHAR(2047),
#error_severity AS INT,
#error_state AS INT;
SET #error_message = ERROR_MESSAGE();
SET #error_severity = ERROR_SEVERITY();
SET #error_state = ERROR_STATE();
RAISERROR(#error_message, #error_severity, #error_state);
RETURN;
END CATCH
With that, it's easy to pivot on a variable number of columns:
EXEC pivotsp
'SELECT TeamID, OptionGroup, OptionID AS Options FROM OptionTeam',
'Teamid', -- Row headers
'optiongroup', -- item to aggregate
'count', -- aggregation function
'optiongroup', -- Column header
'##temp' -- output table name
SELECT * FROM ##temp
Results:
Teamid 4 5
1 2 0
2 1 1
3 0 1
SELECT a.*, o.optionGroup, COUNT(*)
FROM team a
CROSS JOIN
option o
JOIN OptionTeam ot
ON ot.teamId = a.teamId
AND ot.optionGroup = o.optionGroup
WHERE o.OptionId = #id
GROUP BY
a.teamId, o.optionGroup
select teamID,
sum(case when optionGroup = 4 then 1 else 0 end) as optionGroup4,
sum(case when optionGroup = 5 then 1 else 0 end) as optionGroup5,
from optionteam
group by teamID
to add more optiongroups without changing the code, try grouping by that field:
select teamID,optionGroup,count(optionID) as optionCount
from optionteam
group by teamID,optionGroup