Related
This article has a "query to html" approach which seems clean and elegant. But if I change the money data type to numeric, I get an error:
converting data type varchar to numeric
My version:
CREATE OR ALTER PROC dbo.usp_ConvertQuery2HTMLTable
AS
BEGIN
DECLARE #columnslist NVARCHAR (1000) = ''
DECLARE #restOfQuery NVARCHAR (2000) = ''
DECLARE #DynTSQL NVARCHAR (3000)
DECLARE #FROMPOS INT
SET NOCOUNT ON
CREATE TABLE #Products
(
Product [varchar](50) NULL,
UnitPrice [numeric](20, 6) NULL
)
INSERT INTO #Products
SELECT 'Nougat Creme', 14.00
UNION
SELECT 'Gorgonzola', 12.00
UNION
SELECT 'Ale', 14.00
UNION
SELECT 'Chocolate', 10.00
UNION
SELECT 'Lager', 12.00
UNION
SELECT 'Bread', 9.00
DECLARE #SQLQuery NVARCHAR(3000) = 'SELECT Product, cast(UnitPrice as varchar) as UnitPrice FROM #Products';
SELECT #columnslist += 'ISNULL (' + NAME + ',' + '''' + ' ' + '''' + ')' + ','
FROM sys.dm_exec_describe_first_result_set(#SQLQuery, NULL, 0)
SET #columnslist = left (#columnslist, Len (#columnslist) - 1)
SET #FROMPOS = CHARINDEX ('FROM', #SQLQuery, 1)
SET #restOfQuery = SUBSTRING(#SQLQuery, #FROMPOS, LEN(#SQLQuery) - #FROMPOS + 1)
SET #columnslist = Replace (#columnslist, '),', ') as TD,')
SET #columnslist += ' as TD'
SET #DynTSQL = CONCAT (
'SELECT (SELECT '
, #columnslist
,' '
, #restOfQuery
,' FOR XML RAW (''TR''), ELEMENTS, TYPE) AS ''TBODY'''
,' FOR XML PATH (''''), ROOT (''TABLE'')'
)
EXEC (#DynTSQL)
--PRINT #DynTSQL;
SET NOCOUNT OFF
END
GO
I think this procedure is overly complicated, and uses dynamic SQL where it is simply not warranted.
We can do this much easier, by taking a query, adding FOR XML RAW, ELEMENTS, TYPE to it, and passing it to the procedure, then using XQuery to generate the table.
CREATE OR ALTER PROC dbo.usp_ConvertQuery2HTMLTable
#xml xml
AS
SELECT #xml.query('
<TABLE><TBODY>
{ for $row in /row
return element TR {
for $col in $row/*
return element TD {
data($col)
}
}
}
</TBODY></TABLE>
');
You execute it like this:
DECLARE #xml xml = (
SELECT Product, UnitPrice FROM #Products
FOR XML RAW, ELEMENTS, TYPE
);
EXEC usp_ConvertQuery2HTMLTable #xml = #xml;
You can even add column headers. And it's probably even better as a Table Valued function.
CREATE OR ALTER FUNCTION dbo.ConvertQuery2HTMLTable
( #xml xml )
RETURNS TABLE
AS RETURN
SELECT HtmlTable = #xml.query('
<TABLE><TBODY>
<TR>
{
for $colName in /row[1]/*
return element TD { element b {local-name($colName)} }
}
</TR>
{
for $row in /row
return element TR {
for $col in $row/*
return element TD {
data($col)
}
}
}
</TBODY></TABLE>
');
You then execute very simply like this (note the double parenthesis)
SELECT *
FROM ConvertQuery2HTMLTable((
SELECT Product, UnitPrice FROM #Products
FOR XML RAW, ELEMENTS, TYPE
));
db<>fiddle
Instead of cast(UnitPrice as varchar), you should use FORMAT (docs here) to convert the numeric to a currency string. It is even flexible enough to let you pick the appropriate locale formatting as well.
FORMAT(UnitPrice, 'C', 'en-us') AS 'UnitPrice'
First, I had pasted a version of query where I had tried another fix ( that didn't work) - the CAST(). I did fix it by chaging ISNULL() to COALESCE()
DECLARE #columnslist NVARCHAR (1000) = ''
DECLARE #restOfQuery NVARCHAR (2000) = ''
DECLARE #DynTSQL NVARCHAR (3000)
DECLARE #FROMPOS INT
IF OBJECT_ID('tempdb..#Products') IS NOT NULL
DROP TABLE #Products
CREATE TABLE #Products
(
Product [varchar](50) NULL,
UnitPrice [numeric](20, 6) NULL
)
INSERT INTO #Products
SELECT 'Nougat Creme', 14.00
UNION
SELECT 'Gorgonzola', 12.00
UNION
SELECT 'Ale', 14.00
UNION
SELECT 'Chocolate', 10.00
UNION
SELECT 'Lager', 12.00
UNION
SELECT 'Bread', 9.00
DECLARE #SQLQuery NVARCHAR(3000) = 'SELECT Product, UnitPrice FROM #Products';
SELECT #columnslist += 'COALESCE(' + NAME + ',' + '''' + ' ' + '''' + ')' + ','
FROM sys.dm_exec_describe_first_result_set(#SQLQuery, NULL, 0)
SET #columnslist = left (#columnslist, Len (#columnslist) - 1)
SET #FROMPOS = CHARINDEX ('FROM', #SQLQuery, 1)
SET #restOfQuery = SUBSTRING(#SQLQuery, #FROMPOS, LEN(#SQLQuery) - #FROMPOS + 1)
SET #columnslist = Replace (#columnslist, '),', ') as TD,')
SET #columnslist += ' as TD'
SET #DynTSQL = CONCAT (
'SELECT (SELECT '
, #columnslist
,' '
, #restOfQuery
,' FOR XML RAW (''TR''), ELEMENTS, TYPE) AS ''TBODY'''
,' FOR XML PATH (''''), ROOT (''TABLE'')'
)
PRINT (#DynTSQL)
EXEC (#DynTSQL)
CREATE FUNCTION dbo.KeyValuePairs( #inputStr VARCHAR(MAX))
RETURNS #OutTable TABLE
(KeyName VARCHAR(MAX), KeyValue VARCHAR(MAX))
AS
BEGIN
DECLARE #separator CHAR(1), #keyValueSeperator CHAR(1)
SET #separator = ','
SET #keyValueSeperator = ':'
DECLARE #separator_position INT , #keyValueSeperatorPosition INT
DECLARE #match VARCHAR(MAX)
SET #inputStr = #inputStr + #separator
WHILE PATINDEX('%' + #separator + '%' , #inputStr) <> 0
BEGIN
SELECT #separator_position = PATINDEX('%' + #separator + '%' , #inputStr)
SELECT #match = LEFT(#inputStr, #separator_position - 1)
IF #match <> ''
BEGIN
SELECT #keyValueSeperatorPosition = PATINDEX('%' + #keyValueSeperator + '%' , #match)
IF #keyValueSeperatorPosition <> -1
BEGIN
INSERT #OutTable
VALUES (LEFT(#match,#keyValueSeperatorPosition -1),
RIGHT(#match,LEN(#match) - #keyValueSeperatorPosition))
END
END
SELECT #inputStr = STUFF(#inputStr, 1, #separator_position, '')
END
RETURN
END
GO
when input is '1:10,2:20'
This gives the output as string parse
KeyName KeyValue
1 10
2 20
I need additional logic on top of it: I will send
'Stadium','1:10,2:20' as input to a function then
output should be
StadiumA StadiumB
10 20
i.e: in 1:10,2:20' key 1 refers to A AND append 'Stadium' to it .
key 2 refers to B and append 'Stadium' to it an so on continues
I have tried joining with below
ALTER FUNCTION [dbo].[ParseDeviceTopology](#Type NVARCHAR(255),#Value NVARCHAR(MAX))
RETURNS #Parsed TABLE (Topology NVARCHAR(50),Value NVARCHAR(50))
AS
BEGIN
INSERT INTO #Parsed(Topology,Value)
SELECT #Type + m.Topology + 'Version' AS Topology,p.[1] AS [Value]
FROM (
SELECT j.[key] AS [ID],i.[key],i.value
FROM OPENJSON('["' + REPLACE(#Value,',','","') + '"]') j
-- CROSS APPLY OPENJSON('[' + REPLACE(j.[value],':',',') + ']') i
CROSS APPLY OPENJSON('["' + REPLACE(j.[value],':','","') + '"]') i
) a
PIVOT(MAX(a.value) FOR a.[key] IN ([0],[1])) p
INNER JOIN ( VALUES` ` INNER JOIN ( VALUES
(2,'B')
,(1,'A')
,(3,'C')
)` `m(ID, Topology) ON m.ID = p.[0];
But I was getting the output as required in my local machine 2016 SQL but this logic uses OPENJSON which is incompatible in SQL 2014 where I need to deploy. Please help me out
I honestly wouldn't do this in SQL, instead, parse the key value pairs into an object in your external code.
If you absolutely have to do this in SQL I would suggest looking at using a CLR string splitter function as detailed here https://sqlperformance.com/2012/07/t-sql-queries/split-strings
In your case you would need an initial pass to split by comma to return a single column of tabular data, then a pass of that column to split by the semi colon.
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!
I'm trying to achieve a advanced search functionality for my application in which i have a SQL Table Valued Parameter in the following structure,
ColumnName Operator Keyword
------------------------------------
Name StartsWith Ram
City Equals Chennai
My SQL table,
Name City CreatedDate
-----------------------------------
Ram Chennai 10/10/2014
Ramachan Kovai 02/03/2015
How can i loop thorough this TVP so that i can build the WHERE clause and can append it to the SELECT query which is faster since i have some 10 rows of search values(criteria).
The filters are associated with AND operator.
List of operators used:
Equals
Not equals
Starts with
Ends with
From(Date)
To(Date)
You can create a dynamic filtered expression like below and use it in your SQL. You need to be very careful when adding editing filters in your TVP and verifying it against respective datatypes as well
Create Type and Base Table with Data
/*
CREATE TYPE FilterTVP AS TABLE
(
ColumnName VARCHAR(30), Operator VARCHAR(30), Keyword VARCHAR(30)
);
GO
CREATE TABLE myTable
(
Name VARCHAR(50),
City VARCHAR(50),
CreatedDate DATE
)
INSERT INTO myTable VALUES('Ram','Chennai','10/10/2014'),('Ramachan','Kovai','02/03/2015')
*/
Query
DECLARE #Param FilterTVP
INSERT INTO #Param VALUES('Name','StartsWith','Ram'),('City','Equals','Chennai'),('CreatedDate','From','2014-05-05')
DECLARE #FilterExp NVARCHAR(MAX)
SELECT #FilterExp =
(SELECT
' AND ' + QUOTENAME(ColumnName,'[') + ' ' +
CASE Operator
WHEN 'Equals'
THEN '='
WHEN 'Not equals'
THEN '<>'
WHEN 'StartsWith'
THEN 'LIKE'
WHEN 'Endswith'
THEN 'LIKE'
WHEN 'From'
THEN '>='
WHEN 'To'
THEN '<='
END + ' ' +
CASE
WHEN Operator = 'Startswith' THEN QUOTENAME(Keyword + '%','''')
WHEN Operator = 'Endswith' THEN QUOTENAME('%' + Keyword ,'''')
ELSE QUOTENAME(Keyword,'''')
END
FROM #Param
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')
SET #FilterExp = 'SELECT * FROM myTable WHERE 1=1 ' + ISNULL(#FilterExp,'')
PRINT #FilterExp
EXEC sp_executeSQL #FilterExp
Output
SQL Fiddle
Name City CreatedDate
--------------------------
Ram Chennai 2014-10-10
Build your statement and then execute it like for example:
CREATE TABLE f
(
ColumnName NVARCHAR(MAX) ,
Operator NVARCHAR(MAX) ,
KeyWord NVARCHAR(MAX)
)
CREATE TABLE t
(
Name NVARCHAR(MAX) ,
City NVARCHAR(MAX)
)
INSERT INTO f
VALUES ( 'Name', 'StartsWith', 'Ram' ),
( 'City', 'Equals', 'Chennai' )
INSERT INTO t
VALUES ( 'Ram', 'Chennai' ),
( 'Ramachan', 'Kovai' )
DECLARE #op NVARCHAR(MAX) ,
#v NVARCHAR(MAX)
DECLARE #statement NVARCHAR(MAX) = 'SELECT * FROM t WHERE Name '
SELECT #op = Operator ,
#v = KeyWord
FROM f
WHERE ColumnName = 'Name'
SET #statement = #statement + CASE #op
WHEN 'StartsWith' THEN 'LIKE ''' + #v + '%'''
ELSE ' = ''' + #v + ''''
END + ' AND City'
SELECT #op = Operator ,
#v = KeyWord
FROM f
WHERE ColumnName = 'City'
SET #statement = #statement + CASE #op
WHEN 'StartsWith' THEN 'LIKE ''%' + #v + '%'''
ELSE ' = ''' + #v + ''''
END
EXEC(#statement)
Output:
Name City
Ram Chennai
I need to write a stored procedure for which the input is a string.
The input string contains variable names and their values separated by pipeline delimiter like this:
Name =Praveen | City=Hyderabad | Mobile=48629387429| Role=User| etc
In the stored procedure I have declared variables like #x, #y, #z, #t to obtain values as
#x=Praveen (Name value)
#y=Hyderabad (City Value)
#z=48629387429(Mobile Value)
#t=User(Role Value)
Also input string can have the values in any order like
City=Hyderabad | Mobile=48629387429 | Role=User | Name =Praveen |etc
Once I parse the values into #x, #y, #z, #t etc I have to use these values in the stored procedure.
Kindly let me how I can parse the input string to obtain the values of Name, City, Mobile, Role into #x, #y, #z and #t respectively.
One possible solution is use XML
DECLARE #text VARCHAR(1000)
,#xml xml
SELECT #text = 'City=Hyderabad | Mobile=48629387429 | Role=User | Name =Praveen'
SELECT #text = REPLACE(#text,'|','"')
,#text = REPLACE(#text,'=','="')
,#text = '<row ' + #text + '"/>'
SELECT #xml = CAST(#text AS XML)
select
line.col.value('#Name[1]', 'varchar(100)') AS Name
,line.col.value('#City[1]', 'varchar(100)') AS City
,line.col.value('#Mobile[1]', 'varchar(100)') AS Mobile
,line.col.value('#Role[1]', 'varchar(100)') AS Role
FROM #xml.nodes('/row') AS line(col)
I definitely recommend doing your string parsing on the program side as opposed to the data side. That being said, if you absolutely must you can try doing something similar to this:
DECLARE #String [nvarchar](256) = 'Name=Praveen | City=Hyderabad | Mobile=48629387429 | Role=User |'
DECLARE #name [nvarchar](256) = (SELECT SUBSTRING(#String, CHARINDEX('Name=', #String)+5, CHARINDEX('|', #String)))
DECLARE #city [nvarchar](256) = (SELECT SUBSTRING(#String, CHARINDEX('City=', #String)+5, CHARINDEX('|', #String)))
DECLARE #mobile [nvarchar](256) = (SELECT SUBSTRING(#String, CHARINDEX('Mobile=', #String)+7, CHARINDEX('|', #String)))
DECLARE #role [nvarchar](256) = (SELECT SUBSTRING(#String, CHARINDEX('Role=', #String)+5, CHARINDEX('|', #String)))
SELECT RTRIM(LTRIM(LEFT(#name, CHARINDEX('|', #name)-1))) AS Name,
RTRIM(LTRIM(LEFT(#city, CHARINDEX('|', #city)-1))) AS City,
RTRIM(LTRIM(LEFT(#mobile, CHARINDEX('|', #mobile)-1))) AS Mobile,
RTRIM(LTRIM(LEFT(#role, CHARINDEX('|', #role)-1))) AS Role
This returns:
Name | City | Mobile | Role
________________________________________________
Praveen | Hyderabad | 48629387429 | User
Note that the length being addedfrom the CHARINDEX in the initial queries are equal to the search string.
"Name=" is equal to 5 characters so we add 5 to move the index past the = sign,
"Mobile=" is equal to 7 so we add 7.
Similarly in the end SELECT query we are subtracting 1 from each CHARINDEX to remove the | symbol.
Sources:
SUBSTRING
CHARINDEX
LEFT
LTRIM
RTRIM
Let's assume your input param is called #Text.
DECLARE #Text varchar(255),
#x varchar(255)
SET #Text = 'Name=Praveen | City=Hyderabad | Mobile=48629387429| Role=User'
-- Added to show how to account for non-trailing |
SET #Text = #Text + ' | ';
SET #x = LTRIM(RTRIM(substring(
#Text,
charindex('Name=', #Text) + LEN('Name='),
charindex(' | ', #Text, charindex('Name=', #Text)) - LEN('Name=')
)))
SELECT #x
Then just repeat this for #y, #z, #t change Name= to whatever your break is.
Here's a fun way using a loop for string manipulation. Note how we are defining our #x, #y, etc. variable to grab a particular value.
-- Simulate proc parameter
declare #input nvarchar(max) = 'Name =Praveen | City=Hyderabad | Mobile=48629387429| Role=User'
-- OP's preferred destination vars
declare #x nvarchar(max) = 'Name'
declare #y nvarchar(max) = 'City'
declare #z nvarchar(max) = 'Mobile'
declare #t nvarchar(max) = 'Role'
-- The key/value delimiters we are expecting
declare #recordDelim nchar(1) = '|'
declare #valueDelim nchar(1) = '='
-- Temp storage
declare #inputTable table (
name nvarchar(128) not null primary key
, value nvarchar(max) null
)
-- Get all key/value pairs
while ltrim(rtrim(#input)) != '' begin
insert into #inputTable (name) select ltrim(rtrim(replace(left(#input, isnull(nullif(charindex(#recordDelim, #input), 0), len(#input))), #recordDelim, '')))
select #input = ltrim(rtrim(right(#input, len(#input) - isnull(nullif(charindex(#recordDelim, #input), 0), len(#input)))))
end
-- Separate keys and values
update #inputTable
set name = ltrim(rtrim(left(name, isnull(nullif(charindex(#valueDelim, name) - 1, 0), len(name)))))
, value = ltrim(rtrim(right(name, len(name) - isnull(nullif(charindex(#valueDelim, name), 0), len(name)))))
-- Populate the variables
-- If any are null, then this key/value wasn't present
set #x = (select value from #inputTable where name = #x)
set #y = (select value from #inputTable where name = #y)
set #z = (select value from #inputTable where name = #z)
set #t = (select value from #inputTable where name = #t)
Also, from the irregular spacing in your input, I'm guessing you want to trim everything coming in (which is why this proc does that all over the place).