Changing money to numeric breaks this dynamic SQL approach - sql-server

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)

Related

How to Pass Tablename, Fieldnames, Values as perameters in Stored Procedure using CI?

I am developing a custom application using CodeIgniter and MSSQL Server. Here i am using stored procedures.
Now i am wondering to implement codeigniter query type functionality where i can create a universal stored procedure in SQL Server and at the time of using i can pass tablename, array of fields and values.
It can work for both insert and update.
Something like we do in CodeIgniter to execute the query,
$data = array('fieldname1' => 'value1',
'fieldname2' => 'value2');
$this->db->insert($tablename,$data);
Just like this if we can pass the table name and array of the data to stored procedure and stored procedure automatically execute it.
If this can be done, it can save lots n lots of man hours. If anyone have already done i will be very much happy to see the solution.
You need to make string very specific in this case.
Figure out your table name, Column name, Column values for insert. For update 2 more parameters are required Id column name and its value.
GO
---- exec InsertUpdate 'tablename', 'col1, col2, col3', 'val1, val2, val3', 'idcol', 'idval'
GO
Create proc InsertUpdate
( #TableName nvarchar(500),
#ColName nvarchar(max),
#ColValues nvarchar(max),
#IDColName nvarchar(100) = '', --- for update only otherwise null
#IdColValue nvarchar(Max) = '' --- for update only otherwise null
)
As
Begin
declare #Query nvarchar(max)
if (#IdColValue = '')
Begin
set #Query = ' Insert into ' + #TableName + ' (' + #ColName + ') values (' + #ColValues + ')'
End
Else
Begin
;with CtColumn as (
select ROW_NUMBER() over (order by (select 1000)) as Slno, * from Split(#ColName,',') )
, CtValue as (
select ROW_NUMBER() over (order by (select 1000)) as Slno, * from Split(#ColValues, ','))
, CTFinal as (
select CCOl.Slno, CCOl.Items as ColName, CVal.Items as ColValue from CtColumn as CCOl inner join CtValue as CVal on CCOl.Slno=CVal.Slno )
select #Query = 'update ' + #TableName + ' set ' +
stuff ( (select ',' + ColName + '=' + ColValue from CTFinal for xml path ('')) ,1,1,'') +
' where ' + #IDColName + '=' + #IdColValue
End
exec sp_executesql #Query
End
Go

Storing FOR XML PATH results into a variable

Below code works fine and convert table to HTML. It gives the results as HTML tables but I want to assign this to a variable
How can we assign the output to a variable in below code.
CREATE PROC dbo.usp_ConvertQuery2HTMLTable (
#SQLQuery NVARCHAR(3000))
AS
BEGIN
DECLARE #columnslist NVARCHAR (1000) = ''
DECLARE #restOfQuery NVARCHAR (2000) = ''
DECLARE #DynTSQL NVARCHAR (3000)
DECLARE #FROMPOS INT
DECLARE #out table
(
out nvarchar(max)
)
SET NOCOUNT ON
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'')'
)
PRINT #DynTSQL
EXEC (#DynTSQL)
SET NOCOUNT OFF
END
Generally, you have 2 options.
Via an intermediate temporary table (table variable).
By itself, exec() returns nothing when a literal or variable is executed, but you can use the rowset produced by it as a source for an insert statement:
-- Option 1
declare #t table (X xml);
declare #Ret xml;
insert into #t (X)
exec('select top 1 * from sys.objects o for xml raw(''TR''), elements, type;');
select top (1) #Ret = t.X from #t t;
select #Ret as [Option1];
go
Switching to the sys.sp_executesql
As Peter has suggested in the comments, you can switch from exec to the sp_executesql system stored procedure, which provides an additional functionality of output parameters:
-- Option 2
declare #s nvarchar(max) = N'set #A = (select top 1 * from sys.objects o for xml raw(''TR''), elements, type);';
declare #Ret xml;
exec sys.sp_executesql #s, N'#A xml = null output', #A = #Ret output;
select #Ret as [Option2];
go

SQL - Remove (Eliminate) columns which have no data

I wonder that;
in SQl, is it possible to not bring the columns which have no data (or zero value)?
Select * from PLAYER_TABLE where PLAYER_NAME='cagri'
it is bringing just 1 row. because there is only one player which PLAYER_NAME is "cagri".
And there are 30 columns for example statistics.
Score-Rebound-PlayedMinutes-Fauls etc....
Score=2
Rebound=0
PlayedMinutes=2
Fauls=0
and I want to see only [Score] and [PlayedMinutes] columns when call my query.
is it possible?
You can use this logic over a stored procedure in SQL
DDL
create table usr_testtable
(player varchar(30),col1 float, col2 float, col3 float, col4 float)
insert into usr_testtable
values ('Jordan',10,20,3,0)
Convert to Stored Proc
declare #playername varchar(30) = 'Jordan' --- pass this value
declare #ctr smallint = 2 -- start from ordinal 2
declare #maxctr smallint = (SELECT max(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'usr_testTable')
declare #columns varchar(max) = ''
declare #columnswithvalues varchar(max) = ''
declare #coltocheck varchar(30)
declare #mysql nvarchar(max)
declare #coloutput varchar(30)
while #ctr <= #maxctr
begin
SELECT #coltocheck = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'usr_testTable'
and ORDINAL_POSITION = #ctr
set #mysql = N'select #output = ' + #coltocheck + ' from usr_testTable where player =''' + #playername + ''' and cast(' + #coltocheck +' as float) > 0'
EXECUTE sp_executesql #mysql,N'#output int OUTPUT',#output = #coloutput OUTPUT;
if #coloutput > 0
begin
set #columns = coalesce(#columns + ',' + #coltocheck,#columns)
set #columnswithvalues = coalesce(#columnswithvalues + char(13) + char(10) + #coltocheck + ' : ' + #coloutput,#columnswithvalues) --- text form
end
set #coloutput = ''
set #ctr= #ctr + 1
end
-- final result in table format
set #mysql = N'select player' + #columns + ' from usr_testTable where player =''' + #playername + ''' '
EXECUTE sp_executesql #mysql
-- final result in text format appended with columnname
select #columnswithvalues -- format to display in text
First create dynamic SQL to select all columns names in the table PLAYER_TABLE except PLAYER_NAME, then unpivot data from PLAYER_TABLE into table PLAYER_TABLE1, then you can search values <> 0 and select this column in second dynamic SQL.
DROP TABLE PLAYER_TABLE1
DECLARE #Player NVARCHAR(50);
DECLARE #columns NVARCHAR(max);
DECLARE #sql NVARCHAR(max);
DECLARE #columns2 NVARCHAR(max);
DECLARE #sql2 NVARCHAR(max);
SET #player='cagri'
SET #columns = Stuff((SELECT ','
+ Quotename(Rtrim(Ltrim(x.columns)))
FROM (SELECT COLUMN_NAME as columns FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME= 'PLAYER_TABLE' and COLUMN_NAME<>'PLAYER_NAME' ) AS x
ORDER BY X.columns
FOR xml path('')), 1, 1, '');
SET #sql = N' SELECT
PLAYER_NAME, Un_Pivot.Field, Un_Pivot.Value
INTO PLAYER_TABLE1
FROM
(
SELECT * FROM PLAYER_TABLE
) Data
UNPIVOT
(
Value FOR Field IN ('+#columns+')
) AS Un_Pivot';
EXECUTE sp_executesql #sql;
SET #columns2 = Stuff((SELECT ','
+ Quotename(Rtrim(Ltrim(y.Field)))
FROM (SELECT Field FROM PLAYER_TABLE1 WHERE VALUE<>0 AND PLAYER_NAME=#Player) AS y
ORDER BY y.Field
FOR xml path('')), 1, 1, '');
SET #sql2 = N'SELECT PLAYER_NAME,'+#columns2+'FROM PLAYER_TABLE WHERE PLAYER_NAME='+char(39)+#Player+char(39);
EXECUTE sp_executesql #sql2

How to replace while loop?

I've tried to simplify following sql query, it takes more time due to the while loop.
DECLARE #TEMP TABLE
(
ID NUMERIC(18,0) IDENTITY (1, 1) PRIMARY KEY NOT NULL,
NAME VARCHAR(1000),
CATEGORY_ID INT,
TYPE1 VARCHAR(100),
VALUE VARCHAR(6000),
TYPE2 VARCHAR(100)
)
INSERT INTO #TEMP
SELECT
NAME ,
A.CATEGORY_ID,
A.TYPE1,
A.VALUE,
A.TYPE2
FROM
DBO.TABLE1 A,
DBO.TABLE2 B
WHERE
A.CATEGORY_ID=B.CATEGORY_ID
ORDER BY A.CATEGORY_ID
DECLARE #ROWCNT INT=1 , #ROWS INT=0 , #NAME VARCHAR(100),#TYPE1 VARCHAR(100)
,#STAT CHAR(1)='Y'
, #VALUE VARCHAR(6000)
,#COND VARCHAR(8000)
, #TYPE2 VARCHAR(100)
SELECT #COND='SELECT * FROM TABLE3 '
SELECT #ROWCNT = #ROWCNT , #ROWS = (#ROWCNT-1)+ COUNT(1) FROM #TEMP
WHILE #ROWCNT <= #ROWS
BEGIN
SELECT #NAME = NAME ,
#TYPE1 = LTRIM(RTRIM(TYPE1)) ,
#VALUE = VALUE,
#TYPE2 = ISNULL(TYPE2,'')
FROM #TEMP WHERE ID = #ROWCNT
IF #STAT='Y'
BEGIN
IF #TYPE1 = 'SQL'
BEGIN
SELECT #COND=#COND + ' A'+'.'+(#NAME)+' '+#TYPE1+ ' ('''+#VALUE+''') '+#TYPE2+' '+CHAR(13)
END
ELSE
BEGIN
SELECT #COND=#COND + ' A'+'.'+(#NAME)+' '+#TYPE1+ ''''+#VALUE+''' '+#TYPE2+' '++CHAR(13)
END
END
SELECT #ROWCNT=#ROWCNT+1;
END
PRINT (#COND)
the important thing to replace while loop instead of CTE or something else.
can any one sort up this problem.Thanks in advance
It looks like you need something like the below.
DECLARE #COND VARCHAR(8000) ='SELECT * FROM TABLE3 '
+ (SELECT ' A' + '.' + NAME + ' ' + LTRIM(RTRIM(TYPE1))
+ CASE
WHEN LTRIM(RTRIM(TYPE1)) = 'SQL' THEN ' (''' + VALUE + ''') '
ELSE VALUE
END
+ ISNULL(TYPE2, '') + ' ' + CHAR(13)
FROM #TEMP
ORDER BY ID
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(8000)');
PRINT #COND
The string generated isn't valid SQL though

TSQL transform row values into one column table

Source Table
Col1 |Col2 |Col3 |Col4 | Col5
----------------------------------
hi | this | is | a | test
Destination Table
RowValues|
----------
hi
this
is
a
test
I am using Dynamic SQL.
Any help ?
This is my code , just change table name and the Id in the where clause to what suits you
DECLARE #sql nVARCHAR(max), #TableName nvarchar(100), #where nvarchar(max)
set #TableName = 'stockItems'
set #where= ' where id = 2'
select #sql ='Select '
select #sql = #sql+ + ' '''+ [name] +' = ''+ cast(' + [name] + ' as nvarchar(10)) as '+[name]+', '
from sys.columns where object_name (object_id) = #TableName
set #sql = stuff(#sql, len(#sql), 1, '') + ' From '+#TableName+ #where
print #sql
set #sql = REPLACE(#sql,', From',' From')
set #sql = #sql + ' '
print #sql
exec(#sql)
Now I need to create a new table that has one column that hold holds each value as a row
Thanks to #Mahmoud-Gamal
The solution should be something like below
declare #cols nvarchar(max)
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(column_name)
FROM information_schema.columns
WHERE table_name = 'vehicles'
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
declare #statement nvarchar(max)
set #statement ='
SELECT
ColumnName, Value
FROM
Vehicles
UNPIVOT
(
Value
FOR ColumnName
IN
(
'+#cols+'
)
)
AS A'
execute(#statement)
Please change the "vehicle" table name to any table on your database that has columns from different types (datetime, int and nvarchar) and the below error is shown
Any help ?
The type of column "Description" conflicts with the type of other columns specified in the UNPIVOT list.
Use the UNPIVOT table operator:
SELECT col AS RowValues
FROM table1 AS t
UNPIVOT
(
col
FOR value IN([col1],
[col2],
[col3],
[col4],
[col5])
) AS u;
SQL Fiddle Demo
This will give you:
| ROWVALUES |
|-----------|
| hi |
| this |
| is |
| a |
| test |
Update:
In case you don't know the names of the columns, and you want to do this dynamically, you have to do this using dynamic SQL.
But the problem is how to get the columns names?
You can get the columns names from the information_schema.columns, then concatenate them in one sql, then replace the columns' names in the UNPIVOT with this string, and execute that statement dynamically like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(column_name)
FROM information_schema.columns
WHERE table_name = 'Table1'
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = ' SELECT col AS RowValues
FROM table1 AS t
UNPIVOT
(
val
FOR col IN ( ' + #cols + ' )
) AS u;';
EXECUTE(#query);
Updated SQL Fiddle Demo
I believe you want this
Select Col1 + Col2 + Col3 + Col4 + Col5 From Table
Or may be following
Select Col1 From Table1
union Select Col2 From Table1
union Select Col3 From Table1
union Select Col4 From Table1
union Select Col5 From Table1 ;

Resources