SQL: Append string after pattern matching for multiple occurrences - sql-server

I have following data:
declare #tmptable TABLE
(
ID INT IDENTITY(1,1),
Text NVARCHAR(200)
)
insert into #tmptable values ('PREFIX_1.A + PREFIX_2.B')
insert into #tmptable values ('PREFIX_1.C + PREFIX_2.E + PREFIX_1.X')
insert into #tmptable values ('PREFIX_2.D')
insert into #tmptable values ('')
declare #suffix nvarchar(20) = '_new'
I want to replace/ append data as follows:
EXPECTED OUTPUT
PREFIX_1.A_new + PREFIX_2.B_new
PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new
PREFIX_2.D_new
What I have tried:
select REPLACE(Text,
SUBSTRING(Text, CHARINDEX('.', Text) + 1, CHARINDEX(' ',Text )),
SUBSTRING(Text, CHARINDEX('.', Text) + 1, CHARINDEX(' ',Text )) + #suffix
)
FROM #tmptable
where Text like ('%PREFIX_1%') or Text like ('%PREFIX_2%')
NOTE:
What I want:
Append suffix to the text present after the dot.
Input:
PREFIX_1.A + PREFIX_2.B - PREFIX_1.C * PREFIX_1.D
OUTPUT:
PREFIX_1.A_new + PREFIX_2.B_new - PREFIX_1.C_new * PREFIX_1.D_new
Find text just after dot(.) and append suffix to it.
Thanks

For this sample data all you have to do is replace all occurrences of ' +' with #suffix + ' +' and append also #suffix at the end:
select id, replace(Text, ' +', #suffix + ' +') + #suffix as Text
from tmptable
where Text like ('%PREFIX[_]1%') or Text like ('%PREFIX[_]2%')
See the demo.
Results:
> id | Text
> -: | :-----------------------------------------------
> 1 | PREFIX_1.A_new + PREFIX_2.B_new
> 2 | PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new
> 3 | PREFIX_2.D_new

Here are two methods.
The first one uses a recursive CTE and FOR XML to deconstruct & reconstruct the strings.
The second uses a combination of STRING_SPLIT & STRING_AGG to cut them up & glue the parts back together.
Requires Sql Server 2017+ though.
CREATE TABLE #tmpTable
(
ID INT IDENTITY(1,1) PRIMARY KEY,
[Text] NVARCHAR(200)
);
GO
✓
insert into #tmpTable ([Text]) values
('PREFIX_1.A + PREFIX_2.B')
, ('PREFIX_1.C + PREFIX_2.E + PREFIX_1.X + PREFIX_2.Y')
, ('PREFIX_2.D + PREFIX_9.E + PREFIX_1.Y')
, ('PREFIX_1.E')
, ('')
GO
5 rows affected
DECLARE #search NVARCHAR(30) = '%PREFIX[_][1-2][.]%';
DECLARE #delim VARCHAR(1) = '+';
DECLARE #suffix NVARCHAR(30) = '_new';
WITH RCTE AS
(
SELECT id
, text+#delim as str
, 1 as lvl
, 0 as pos1
, CHARINDEX(#delim,text+#delim) as pos2
FROM #tmpTable
WHERE Text LIKE (#search)
UNION ALL
SELECT id, str, lvl+1
, pos2+1
, CHARINDEX(#delim, str, pos2+1)
FROM RCTE
WHERE pos2 > pos1
)
SELECT id
, ISNULL(a.NewText, t.Text) as [Text]
FROM #tmpTable t
OUTER APPLY
(
SELECT STUFF(CAST(q.parts AS NVARCHAR(MAX)),1,3,'') as NewText
FROM
(
SELECT CONCAT(' ',#delim,' ', RTRIM(LTRIM(SUBSTRING(str, pos1, pos2-pos1))), CASE WHEN SUBSTRING(str, pos1, pos2-pos1) LIKE #search THEN #suffix END)
FROM RCTE c
WHERE c.pos2 > c.pos1
AND c.id = t.id
ORDER BY c.lvl
FOR XML PATH(''), TYPE
) q(parts)
) a
ORDER BY id;
GO
id | Text
-: | :----------------------------------------------------------------
1 | PREFIX_1.A_new + PREFIX_2.B_new
2 | PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new + PREFIX_2.Y_new
3 | PREFIX_2.D_new + PREFIX_9.E + PREFIX_1.Y_new
4 | PREFIX_1.E_new
5 |
DECLARE #search NVARCHAR(30) = '%PREFIX[_][1-2][.]%';
DECLARE #suffix NVARCHAR(30) = '_new';
SELECT id, NewText
FROM #tmpTable t
CROSS APPLY
(
SELECT
STRING_AGG(
CONCAT(RTRIM(LTRIM(s.value)),
CASE
WHEN s.value LIKE #search
THEN #suffix
END),' + ') AS NewText
FROM STRING_SPLIT(t.[Text],'+') s
) a;
GO
id | NewText
-: | :----------------------------------------------------------------
1 | PREFIX_1.A_new + PREFIX_2.B_new
2 | PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new + PREFIX_2.Y_new
3 | PREFIX_2.D_new + PREFIX_9.E + PREFIX_1.Y_new
4 | PREFIX_1.E_new
5 |
db<>fiddle here

Related

populate two variables in one query

I am writing a stored procedure with a pivot in it. The pivot field names can change depending on the data in the table.
So I have the two variables below. However this seems quite inefficient because I run two queries on the same table, this is probably due to my lack of knowledge.
declare #code nvarchar(max) = ''
select #code = #code + '[' + Code + '],' from (select Code from myTbl) as c
set #code = substring(#code , 1, len(#code ) - 1)
declare #Name nvarchar(max) = ''
select #Name = #Name + '[' + Name + '],' from (select Name from myTbl) as c
set #Name = substring(#Name , 1, len(#Name ) - 1)
Is it possible to populate both variables and only query the table once?
Yes you can, here is a simple sample
CREATE TABLE T(
Code VARCHAR(45),
Name VARCHAR(45)
);
INSERT INTO T VALUES
('Code1', 'Name1'),
('Code2', 'Name2');
DECLARE #Code VARCHAR(MAX) = '',
#Name VARCHAR(MAX) = '';
SELECT #Code = #Code + QUOTENAME(Code) + ',',
#Name = #Name + QUOTENAME(Name) + ','
FROM T;
SELECT #Code, #Name;
Returns:
+------------------+------------------+
| No column name) | (No column name) |
+------------------+------------------+
| [Code1],[Code2], | [Name1],[Name2], |
+------------------+------------------+
If you have SQL Server 2017, then no need to use substring, you can just use STRING_AGG() as
SELECT STRING_AGG(QUOTENAME(Code), ','),
STRING_AGG(QUOTENAME(Name), ',')
FROM T;
Returns:
+------------------+------------------+
| (No column name) | (No column name) |
+------------------+------------------+
| [Code1],[Code2] | [Name1],[Name2] |
+------------------+------------------+
I have stripped down the subquery (select Code from myTbl) as c since I do not think it added something in this context.
Given that. I believe it could work like this:
declare #code nvarchar(max) = ''
declare #Name nvarchar(max) = ''
select #code = #code + '[' + Code + '],', #Name = #Name + '[' + Name + '],' from myTbl
set #code = substring(#code , 1, len(#code ) - 1)
set #Name = substring(#Name , 1, len(#Name ) - 1)

SQL Server 2016 T-SQL convert comma string to inner join database name(s)

I have a value for one of my columns that looks like this:
ID | userPerms | Name | DOB
-----+-----------+-----------+----------
5985 |1,3,4 |Bob Barker |12/12/1923
895 |1,2 |Bill Gates |10/14/1955
5897 |1,2,4 |Steve Jobs |02/24/1955
That column being the userPerms column.
I need to be able to Inner Join with the userPerm table associated with those numbers.
My query is currently:
SELECT
uT.employeeID + '|' + uT.lastFirstMiddle + '|' + uT.ntName + '|' + uT.email + '|' +
uT.firstName + '|' + uT.lastName + '|' + uT.active + '|' + uT.userPerms + '|' +
uT.userPermPages
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP ON uP.id = uT.userPerms
WHERE
uT.id = 1
Naturally it won't work since the data has commas in it.
So what I am looking for in the output:
ID | userPerms | Name | DOB
-----+------------------+-----------+------------
5985 |Read,Upload,Admin |Bob Barker |12/12/1923
895 |Read,Write |Bill Gates |10/14/1955
5897 |Read,Write,Admin |Steve Jobs |02/24/1955
Does anyone know how to split these out so that the inner join would then work as designed?
UPDATE 1
I got it working but:
It does not combine the userPerms into the original string
SELECT
uT.employeeID + '|' + uT.lastFirstMiddle + '|' + uT.ntName + '|' +
uT.email + '|' + uT.firstName + '|' + uT.lastName + '|' + uT.active,
(
SELECT
',' + uP.type
FROM
usersPermissions AS uP
WHERE
',' + uT.userPerms + ','
LIKE
'%,' + cast(uP.id AS nvarchar(20)) + ',%'
FOR
XML PATH(''), TYPE
).value('substring(text()[1], 2)', 'varchar(max)') AS userPerms,
(
SELECT
',' + uP.name
FROM
pagePermissions AS uP
WHERE
',' + uT.userPerms + ','
LIKE
'%,' + cast(uP.id AS nvarchar(20)) + ',%'
FOR
XML PATH(''), TYPE
).value('substring(text()[1], 2)', 'varchar(max)') AS userPermPages
FROM
usersTbl as uT
WHERE
uT.id = '1';
Not great DB Design to a list of IDs like that. It's better to use a relation table to store the many to many relationship. That said, this might work:
SELECT
uT.employeeID + '|' + uT.lastFirstMiddle + '|' + uT.ntName + '|' + uT.email + '|' +
uT.firstName + '|' + uT.lastName + '|' + uT.active + '|' + uT.userPerms + '|' +
uT.userPermPages
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
ON ','+uT.userPerms+',' like '%,'+uP.id+',%'
WHERE
uT.id = 1
The idea is to add commas to the beginning and end of uT.userPerms and use the like operator to join to any occurance of uP.id (also surrounded with commas) in uT.userPerms.
Well, you asked me... :-)
I do not have an installation of SQL-Server 2016 working at the moment, so this is untested air code. But I think you need something like this:
SET DATEFORMAT mdy;
DECLARE #tblData TABLE(ID INT, userPerms VARCHAR(100),Name VARCHAR(100),DOB DATE);
INSERT INTO #tblData VALUES
(5985,'1,3,4','Bob Barker','12/12/1923')
,(895,'1,2','Bill Gates','10/14/1955')
,(5897,'1,2,4','Steve Jobs','02/24/1955');
DECLARE #tblPerm TABLE(ID INT, Perm VARCHAR(100));
INSERT INTO #tblPerm VALUES
(1,'Read')
,(2,'Write')
,(3,'Upload')
,(4,'Admin');
SELECT d.ID,d.Name,d.DOB
,STRING_AGG(p.Perm,',') AS Perms
FROM #tblData AS d
CROSS APPLY STRING_SPLIT(d.userPerms,',') AS A
INNER JOIN #tblPerm AS p ON CAST(A.value AS INT)=p.ID --<-- added cast to int
GROUP BY d.ID,d.Name,d.DOB;
The function string_split() will separate the values at commas, which allows you to JOIN your catalog table. GROUP BY in combination with STRING_AGG() should return what you want. But I've never used this before...
Hope this helps!
UPDATE: A working solution without string-split and string-agg
SET DATEFORMAT mdy;
DECLARE #tblData TABLE(ID INT, userPerms VARCHAR(100),Name VARCHAR(100),DOB DATE);
INSERT INTO #tblData VALUES
(5985,'1,3,4','Bob Barker','12/12/1923')
,(895,'1,2','Bill Gates','10/14/1955')
,(5897,'1,2,4','Steve Jobs','02/24/1955');
DECLARE #tblPerm TABLE(ID INT, Perm VARCHAR(100));
INSERT INTO #tblPerm VALUES
(1,'Read')
,(2,'Write')
,(3,'Upload')
,(4,'Admin');
WITH Joined As
(
SELECT d.ID
,d.Name
,d.DOB
,p.Perm
FROM #tblData AS d
CROSS APPLY(SELECT CAST('<x>' + REPLACE(d.userPerms,',','</x><x>') + '</x>' AS XML)) AS A(casted)
CROSS APPLY A.casted.nodes(N'/x') AS B(numbers)
INNER JOIN #tblPerm AS p ON CAST(B.numbers.value(N'text()[1]',N'int') AS INT)=p.ID
)
SELECT j.ID
,j.Name
,j.DOB
,STUFF((
SELECT ',' + x.perm
FROM Joined AS x
WHERE x.ID=j.ID
FOR XML PATH('')
),1,1,'') AS Perm
FROM Joined AS j
GROUP BY ID,Name,DOB
create table #tblData (ID INT, userPerms VARCHAR(100),Name VARCHAR(100),DOB DATE);
INSERT INTO #tbldata VALUES
(5985,'1,3,4','Bob Barker','12/12/1923')
,(895,'1,2','Bill Gates','10/14/1955')
,(5897,'1,2,4','Steve Jobs','02/24/1955');
create table #tblPerm (ID INT, Perm VARCHAR(100));
INSERT INTO #tblPerm VALUES
(1,'Read')
,(2,'Write')
,(3,'Upload')
,(4,'Admin');
Create FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
go
;with cte
as
(
select t1.ID,t1.userPerms,t1.Name,t3.Perm ,t1.DOB from #tblData as t1
cross apply
dbo.fnsplitstring(t1.userPerms,',') as t2
join #tblPerm as t3
on t3.ID=t2.splitdata
)
select id,STUFF((select ','+perm from cte t1
where t1.ID=t2.id
for XML path('')),1,1,'') as userperms,Name,DOB
from cte t2
group by id ,name,DOB

Using Parameters to Add Columns?

I have a stored procedure which adds a currency record to my exchange rate table but I also need a corresponding column for the added currency.
The table structure is as follows:
CurrencyID CurrencyName Rupee Euro Dollar Pound
----------- ------------ ---------------------- ---------------------- ---------------------- ----------------------
1 Rupee 1 0.008 0.009 0.007
2 Euro 121.3 1 1.08 0.84
3 Dollar 111.4 0.91 1 0.77
4 Pound 143.6 1.18 1.28 1
and my stored procedure so far is this:
CREATE PROCEDURE addCurrency
#CurrencyName varchar(30),
#Rupee float,
#Euro float,
#Dollar float,
#Pound float
AS
BEGIN
INSERT into [dbo].[CurrencyTbl] (CurrencyName, Rupee, Euro, Dollar, Pound)
VALUES (#CurrencyName, #Rupee, #Euro, #Dollar, #Pound)
END
BEGIN
DECLARE #SQL VARCHAR(1000)
SELECT #SQL = 'ALTER TABLE [dbo].[CurrencyTbl] ADD ' + #CurrencyName + ' VARCHAR(30)'
END
But the column is not created
Not that I think any of that is a good idea, but you do not actually execute your created #SQL, you are just selecting it.
Your code vulnerable to SQL Injection (because you are directly executing sql with concatenated parameters), so be careful with that code.
Also, if you are storing numbers, why is your datatype varchar(30)? Your other datatypes are float (which should probably be a numeric/decimal instead of float).
You could use exec sp_executesql #SQL like so:
CREATE PROC addCurrency #CurrencyName varchar(30),#Rupee float,
#Euro float,#Dollar float,#Pound float
AS
BEGIN
INSERT into [dbo].[CurrencyTbl] (CurrencyName , Rupee,Euro, Dollar,Pound )
VALUES (#CurrencyName,#Rupee,#Euro,#Dollar,#Pound )
END
BEGIN
Declare #SQL nVarChar(1000)
SELECT #SQL = 'ALTER TABLE [dbo].[CurrencyTbl] ADD ' + #CurrencyName + ' float;'
exec sp_executesql #SQL;
END
dynamic sql
The curse and blessings of dynamic SQL - Erland Sommarskog
sp_executesql
For example:
/* Monies is the term used by Irkens to refer to their form of currency */
exec addCurrency 'Monies',1,1,1,1
select * from CurrencyTbl
rextester demo: http://rextester.com/CUC99912
returns:
+------------+--------------+------------+----------+----------+----------+--------+
| CurrencyID | CurrencyName | Rupee | Euro | Dollar | Pound | Monies |
+------------+--------------+------------+----------+----------+----------+--------+
| 1 | Rupee | 1,000000 | 0,008000 | 0,009000 | 0,007000 | NULL |
| 2 | Euro | 121,300000 | 1,000000 | 1,080000 | 0,840000 | NULL |
| 3 | Dollar | 111,400000 | 0,910000 | 1,000000 | 0,770000 | NULL |
| 4 | Pound | 143,600000 | 1,180000 | 1,280000 | 1,000000 | NULL |
| 5 | Monies | 1,000000 | 1,000000 | 1,000000 | 1,000000 | NULL |
+------------+--------------+------------+----------+----------+----------+--------+
It may be better to consider an alternative form for your table that does not require adding new columns and updating columns using dynamic sql.
Here is one option:
create table CurrencyTbl (FromCurrencyName varchar(30), ExchangeRate decimal(19,6), ToCurrencyName varchar(30))
insert into CurrencyTbl values
('Rupee ',1.000000,'Rupee')
,('Rupee ',0.008000,'Euro')
,('Rupee ',0.009000,'Dollar')
,('Rupee ',0.007000,'Pound')
,('Euro ',121.300000,'Rupee')
,('Euro ',1.000000,'Euro')
,('Euro ',1.090000,'Dollar')
,('Euro ',0.850000,'Pound')
,('Dollar',111.400000,'Rupee')
,('Dollar',0.910000,'Euro')
,('Dollar',1.000000,'Dollar')
,('Dollar',0.770000,'Pound')
,('Pound ',143.600000,'Rupee')
,('Pound ',1.180000,'Euro')
,('Pound ',1.280000,'Dollar')
,('Pound ',1.000000,'Pound')
And you could pivot the table dynamically like so:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + quotename(ToCurrencyName)
from CurrencyTbl
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql = '
select FromCurrencyName as CurrencyName,' + #cols + '
from CurrencyTbl
pivot (max([ExchangeRate]) for [ToCurrencyName] in (' + #cols + ') ) p'
exec sp_executesql #sql;
rextester demo: http://rextester.com/EQSC62833
returns:
+--------------+----------+----------+----------+------------+
| CurrencyName | Dollar | Euro | Pound | Rupee |
+--------------+----------+----------+----------+------------+
| Dollar | 1,000000 | 0,910000 | 0,770000 | 111,400000 |
| Euro | 1,090000 | 1,000000 | 0,850000 | 121,300000 |
| Pound | 1,280000 | 1,180000 | 1,000000 | 143,600000 |
| Rupee | 0,009000 | 0,008000 | 0,007000 | 1,000000 |
+--------------+----------+----------+----------+------------+
I created Temp Tables but this ought to work with standard ones as well...
CREATE TABLE #Currencies (
Currencyid INTEGER identity(1, 1) PRIMARY KEY
,CurrencyName VARCHAR(255)
)
CREATE TABLE #ConversionRates (
CurrencyId1 INTEGER
,CurrencyId2 INTEGER
,Conversion1To2Rate DECIMAL(19, 6)
)
Then we have a stored proc to handle the work
/************************************************************************************************
Stored Procedure: dbo.usp_AddCurrency
M. Jay Wheeler -- mjwheele#yahoo.com
****************************************************************************************************/
CREATE PROCEDURE [dbo].usp_AddCurrency --
#CurrencyName VARCHAR(255) = NULL
,#CurrencyId INTEGER = NULL
,#Currency2Id INTEGER = NULL
,#Currency1ToCurrency2ExchangeRate DECIMAL(19, 6) = NULL
AS
BEGIN
DECLARE #ErrorMessage VARCHAR(MAX) = ''
DECLARE #ThrowErrorOnAddingAlreadyExists CHAR(1) = 'N' -- If N will automatically update the currency Name.
BEGIN TRY
--#############################################################################
-- Check Parameters
--#############################################################################
IF #CurrencyId IS NULL
AND #CurrencyName IS NULL
SELECT #ErrorMessage += '#CurrencyID and #CurrencyName cannot both be null.' + CHAR(13)
IF #CurrencyId IS NULL
AND NOT EXISTS (
SELECT 1
FROM #Currencies
WHERE CurrencyName = #CurrencyName
)
BEGIN
INSERT INTO #Currencies (CurrencyName)
SELECT #CurrencyName
SELECT #CurrencyId = SCOPE_IDENTITY()
END
SELECT #CurrencyId = CurrencyId
FROM #Currencies
WHERE CurrencyName = #CurrencyName
AND #CurrencyId IS NULL
SELECT #CurrencyName = CurrencyName
FROM #Currencies
WHERE Currencyid = #CurrencyId
AND #CurrencyName IS NULL
IF NOT EXISTS (
SELECT 1
FROM #Currencies
WHERE Currencyid = #CurrencyId
)
SELECT #ErrorMessage += '#CurrencyID: ' + isnull(Cast(#CurrencyId AS VARCHAR(15)), 'NULL') + ' Does Not Exist.' + CHAR(13)
IF EXISTS (
SELECT 1
FROM #Currencies c
WHERE c.Currencyid <> #CurrencyId
AND c.CurrencyName = #CurrencyName
)
SELECT #ErrorMessage += 'Currency: ' + #CurrencyName + ' already exists with an ID of ' + cast((
SELECT currencyId
FROM #Currencies
WHERE CurrencyName = #CurrencyName
) AS VARCHAR(15)) + CHAR(13)
IF EXISTS (
SELECT 1
FROM #Currencies c
WHERE c.Currencyid = #CurrencyId
AND c.CurrencyName <> #CurrencyName
)
UPDATE #Currencies
SET CurrencyName = #CurrencyName
WHERE Currencyid = #CurrencyId
IF #Currency2Id IS NOT NULL
AND #Currency1ToCurrency2ExchangeRate IS NULL
SELECT #Errormessage += 'Improper Syntax no exchange rate to assignmen.' + CHAR(13)
IF #Currency2id IS NULL
AND #Currency1ToCurrency2ExchangeRate IS NOT NULL
SELECT #Errormessage += 'Improper Syntax no "To Currency" for rate assignment.' + CHAR(13)
IF len(#ErrorMessage) > 0
RAISERROR ('%s' ,16 ,1 ,#ErrorMessage)
--#############################################################################
-- you get the idea more error checking needed to bullet proof. Now assume
-- We have all the correct Parameters set up, we've made sure anything coming in
-- needs to be added or updated by this point.
--#############################################################################
--#############################################################################
-- nothing to do if #Currency2Id is null
--#############################################################################
IF #Currency2Id IS NULL
RETURN
--#############################################################################
-- see if we need to add/Update a conversion rate.
--#############################################################################
UPDATE #ConversionRates
SET Conversion1To2Rate = #Currency1ToCurrency2ExchangeRate
WHERE #CurrencyId = CurrencyId1
AND #Currency2Id = CurrencyId2
INSERT INTO #ConversionRates (
CurrencyId1
,CurrencyId2
,Conversion1To2Rate
)
SELECT #CurrencyId
,#Currency2Id
,#Currency1ToCurrency2ExchangeRate
WHERE NOT EXISTS (
SELECT 1
FROM #ConversionRates
WHERE #CurrencyId = CurrencyId1
AND #Currency2Id = CurrencyId2
)
END TRY
BEGIN CATCH
IF ##Trancount > 0
ROLLBACK TRAN
DECLARE #ErrorBlockLineLen INTEGER = 0
DECLARE #ErrorBlockGotTheFormat BIT = 0
DECLARE #ErrorFormatIndent INTEGER = 3
DECLARE #ErrorBlockBeenThrough INTEGER = NULL -- must be set to null
DECLARE #ThisProcedureName VARCHAR(255) = ISNULL(OBJECT_NAME(##PROCID), 'Testing')
DECLARE #ErrorProc VARCHAR(4000) = CONVERT(VARCHAR(4000), ISNULL(ERROR_PROCEDURE(), #ThisProcedureName))
WHILE #ErrorBlockGotTheFormat = 0
BEGIN
IF #ErrorBlockBeenThrough IS NOT NULL
SELECT #ErrorBlockGotTheFormat = 1
SET #errormessage = Space(isnull(#ErrorFormatIndent, 0)) + #ThisProcedureName + ' Reports Error Thrown by: ' + #ErrorProc + CHAR(13)
SET #errormessage += Space(isnull(#ErrorFormatIndent, 0)) + '-------->' + ISNULL(CONVERT(VARCHAR(4000), ERROR_MESSAGE()), 'Unknown') + '<--------' + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + REPLICATE('=', #ErrorBlockLineLen) + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + UPPER(#ThisProcedureName + ' Variable dump:') + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + REPLICATE('=', #ErrorBlockLineLen) + CHAR(13) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#Currency1ToCurrency2ExchangeRate:.....<' + ISNULL(CAST(#Currency1ToCurrency2ExchangeRate AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#Currency2Id:..........................<' + ISNULL(CAST(#Currency2Id AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#CurrencyId:...........................<' + ISNULL(CAST(#CurrencyId AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#CurrencyName:.........................<' + ISNULL(CAST(#CurrencyName AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
--SELECT #ErrorBlockLineLen = MAX(LEN(RTRIM(item)))
--FROM dbo.fnSplit(#errormessage, CHAR(13))
SELECT #ErrorBlockLineLen = 120
SELECT #ErrorBlockBeenThrough = 1
END
RAISERROR ('%s' ,16 ,1 ,#ErrorMessage)
END CATCH
END
GO
And this is the code to populate. I used the inverse for some of the rates as it was easier.
set nocount on
exec usp_AddCurrency 'Rupiee'
exec usp_AddCurrency 'Euro'
exec usp_AddCurrency 'Dollar'
exec usp_AddCurrency 'Pound'
exec usp_AddCurrency "Rupiee", Null, 1, 1.000000
exec usp_AddCurrency "Rupiee", Null, 2, 0.008000
exec usp_AddCurrency "Rupiee", Null, 3, 0.009000
exec usp_AddCurrency "Rupiee", Null, 4, 0.007000
Declare #Inverse Decimal(19,6)
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =2 )) as decimal(19,6))
exec usp_AddCurrency 'Euro' , Null, 1, #Inverse
exec usp_AddCurrency 'Euro' , Null, 2, 1.000000
exec usp_AddCurrency 'Euro' , Null, 3, 1.090000
exec usp_AddCurrency 'Euro' , Null, 4, 0.850000
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =3 )) as decimal(19,6))
exec usp_AddCurrency 'Dollar' , Null, 1,#Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 2 and CurrencyId2 =3 )) as decimal(19,6))
exec usp_AddCurrency 'Dollar' , Null, 2, #Inverse
exec usp_AddCurrency 'Dollar' , Null, 3, 1.000000
exec usp_AddCurrency 'Dollar' , Null, 4, 0.770000
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 1, #Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 2 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 2, #Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 3 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 3, #Inverse
exec usp_AddCurrency 'Pound' , Null, 4, 1.000000
This is the end of your question I think.
I tested the output with this code:
Declare #MySQL nVarChar(max) = 'Create Table ##output ([Currency Name] Varchar(255),' + char(13)
SELECT #MySQL += STUFF(( SELECT ',[' + CurrencyName + '] VARCHAR(255) '
FROM #Currencies
order by Currencyid asc
FOR
XML PATH('')
), 1, 1, '') + ')'
exec sp_executesql #MySQL
select * into #output from ##output
Drop Table ##Output
Declare #PivotThings varchar(max)
select #PivotThings = STUFF(( SELECT ',[' + CurrencyName + '] '
FROM #Currencies
order by Currencyid asc
FOR
XML PATH('')
), 1, 1, '')
Select #MySQL = '
SELECT *
FROM(
select c1.CurrencyName as Rows, c1.Currencyid as r, c2.CurrencyName as Cols, Conversion1To2Rate as [Value] from #ConversionRates r
join #Currencies c1 on c1.Currencyid = r.CurrencyId1
join #Currencies c2 on c2.Currencyid = r.CurrencyId2
) WorkOrders
PIVOT
(
SUM(value)
FOR [cols] IN (
##PivotThings##
)
) AS PivotTable
ORDER BY [r]
'
Select #MySQL = REPLACE(#mysql , '##PivotThings##', #pivotthings)
exec sp_executesql #mysql
Hope you find this useful. It is my first post evah!

SQL Server - create or insert table with result from a 'print #variable'

I made this function that allows me to split cells in SQL Server.
DECLARE #LoopCounter INT = 1, #MaxId INT = 8000, #Hashtag nVarchar(max) =''
WHILE(#LoopCounter <= #MaxId)
BEGIN
SELECT #Hashtag = #Hashtag + ' ' + Item FROM dbo.Split(' ', (SELECT
Hashtags_in_Tweet FROM TwitterSentiment WHERE Tweet_ID = #LoopCounter) );
SET #LoopCounter = #LoopCounter + 1
END
print #Hashtag
I used this code for the dbo.Split, which I got from another question posted here a while ago:
create FUNCTION [dbo].[Split] (#sep VARCHAR(32), #s VARCHAR(MAX))
RETURNS TABLE
AS
RETURN
(
SELECT r.value('.','VARCHAR(MAX)') as Item
FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(REPLACE(REPLACE(#s,'& ','& '),'<','<'), #sep, '</r><r>') + '</r></root>') as valxml) x
CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
)
So when I have a row in column Hashtags_in_Tweet with "Cheese party fun", it results into
Cheese
Party
Fun
Now I want these to be inserted in a table Hashs (int IDENTITY(1,1), varchar(800) ) I have created. I tried it with follwing script:
DECLARE #LoopCounter INT = 1, #MaxId INT = 100, #Hashtag nVarchar(max) = ''
WHILE(#LoopCounter <= #MaxId)
BEGIN
SELECT #Hashtag = #Hashtag + ' ' + Item FROM dbo.Split(' ', (SELECT
Hashtags_in_Tweet FROM TwitterSentiment WHERE Tweet_ID = #LoopCounter) );
SET #LoopCounter = #LoopCounter + 1
END
insert INTO Hashs
Values ( #Hashtag )
But that just puts them in one row:
ID | Hashtag
1 | Cheese Party fun
While I actually wanted:
ID | Hashtag
1 | Cheese
2 | Party
3 | Fun
Any ways to fix this?
You can get all of them at once like this:
SELECT ID, Item
, ROW_NUMBER() over(partition by ID order by Item)
FROM TwitterSentiment
Cross Apply dbo.Split(' ', Hashtags_in_Tweet)
Where Tweet_ID <= #MaxId

Simplify Dynamic SQL Pivot Table

I have written a Dynamic Pivot Table Query based on the following. Here is a SQL FIDDLE for reference.
CREATE TABLE TestTable1 ([idnumber] INT, [DataTypeId] INT)
GO
INSERT INTO TestTable1
VALUES (1, 108), (1, 108), (1, 108), (2, 108),
(2, 108), (3, 108), (1, 109),(1, 109),
(1, 110),(2, 110),(1, 111),(4, 108),(4, 108),
(4, 110),(4, 111)
GO
Here is the Dynamic SQL that I wrote
DECLARE #SQL NVARCHAR(MAX),
#Cols NVARCHAR(MAX),
#ColsP NVARCHAR(MAX)
SELECT #Cols = STUFF((select ',
ISNULL([' + CAST([DataTypeId] as varchar(10)) + '], 0) AS ''' + CAST([DataTypeId] as varchar(10)) + ''''
FROM
(
SELECT [DataTypeId] FROM [TestTable1]
GROUP BY [DataTypeId]
HAVING [DataTypeId] <> ''
) AS d
ORDER BY [DataTypeId] FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
-- /////////////THIS IS WHAT I WANT REMOVED ////////////////////
SELECT #ColsP = STUFF((select ',
[' + CAST([DataTypeId] as varchar(10)) + ']'
FROM
(
SELECT [DataTypeId] FROM [TestTable1]
GROUP BY [DataTypeId]
HAVING [DataTypeId] <> ''
) AS d
ORDER BY [DataTypeId] FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
-- /////////////////////////////////////////////////////////////
SET #SQL = 'SELECT idnumber,' + #Cols + '
FROM
(SELECT idnumber, COUNT([DataTypeId]) AS Total, [DataTypeId] FROM [TestTable1]
GROUP BY idnumber, [DataTypeId]
HAVING [DataTypeId] <> ''''
) p
PIVOT
(
SUM(Total) FOR [DataTypeId] IN (' + #ColsP + ')
) AS pvt
ORDER BY pvt.idnumber'
-- print #SQL
EXECUTE( #SQL)
I get the result that I want:
| IDNUMBER | 108 | 109 | 110 | 111 |
|----------|-----|-----|-----|-----|
| 1 | 3 | 2 | 1 | 1 |
| 2 | 2 | 0 | 1 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 2 | 0 | 1 | 1 |
But I am sure it can be done better. I would like to remove where I populate the variable #ColsP - SELECT #ColsP = STUFF((select...")
There should be a way where I can create this dynamic code with just one loop through TestTable1. As you can see, I loop through it twice. Once to read what columns to create for the select statement, and once for the PIVOT table.
Here is the code that is generated by the Dynamic SQL:
SELECT idnumber,
ISNULL([108], 0) AS '108',
ISNULL([109], 0) AS '109',
ISNULL([110], 0) AS '110',
ISNULL([111], 0) AS '111'
FROM
(
SELECT idnumber, COUNT([DataTypeId]) AS Total, [DataTypeId]
FROM [TestTable2]
GROUP BY idnumber, [DataTypeId]
HAVING [DataTypeId] <> ''
) p
PIVOT
(
SUM(Total) FOR [DataTypeId] IN ([108], [109], [110], [111])
) AS pvt
ORDER BY pvt.idnumber
You can shorten your code considerably. First, you can just use count to aggregate the data in the PIVOT. There is no need for the inner count to aggregate the data or the HAVING clause. Finally, you only need to create the list of columns once. You could easily improve the code to:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(DataTypeId)
from TestTable1
group by DataTypeId
order by DataTypeId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= N'SELECT idnumber, ' + #cols + N'
from
(
select idnumber, DataTypeId
from TestTable1
) x
pivot
(
count(DataTypeId)
for DataTypeId in (' + #cols + N')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives the same result:
| IDNUMBER | 108 | 109 | 110 | 111 |
|----------|-----|-----|-----|-----|
| 1 | 3 | 2 | 1 | 1 |
| 2 | 2 | 0 | 1 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 2 | 0 | 1 | 1 |
Try replacing it with this.
SET NOCOUNT ON
IF OBJECT_ID('TestTable1') IS NOT NULL
DROP TABLE TestTable1
GO
CREATE TABLE TestTable1 ([idnumber] INT, [DataTypeId] INT)
GO
INSERT INTO TestTable1 VALUES
(1, 108),(1, 108),(1, 108),(2, 108),(2, 108),
(3, 108),(1, 109),(1, 109),(1, 110),(2, 110),
(1, 111),(4, 108),(4, 108),(4, 110),(4, 111)
DECLARE
#AllColumns NVARCHAR(MAX)
SELECT #AllColumns = ''
SELECT #AllColumns = #AllColumns +
'[' + CAST(DataTypeId as NVARCHAR)+'],'
FROM TestTable1
GROUP BY DataTypeId
SET #AllColumns = LEFT(#AllColumns,LEN(#AllColumns)-1)
PRINT #AllColumns

Resources