I am getting an error when I run this query because the data type money can't be implicitly converted to a varchar. However, I am using an if statemnt to make sure the data type is not money before I try the conversion. Clearly, the conversion is being executed anyways. Anyone know why?
table: BBH_NEW col: rebate2
datatype: money
if 'money'= 'money'
begin
if (select max([rebate2]) from [BBH_NEW]) = 0
and (select min([rebate2]) from [BBH_NEW]) = 0
print ' rebate2 '
print ' 1 '
end
if 'money'!= 'money'
begin
IF NOT EXISTS (SELECT top 1 * FROM [BBH_NEW] WHERE [rebate2] IS NOT NULL and
len([rebate2]) > 0 )
BEGIN
print ' rebate2 '
end
end
Error:
Msg 257, Level 16, State 3, Line 11
Implicit conversion from data type money to varchar is not allowed. Use the CONVERT function to run this query.
yes this code was generated. If it helps, this is the code which was used to produce it:
select #temp =
data_type FROM information_schema.columns
WHERE table_schema = 'dbo'
AND table_name = #tblname
AND column_name = #col
SELECT #hold =
'if '''+#temp+'''= ''money''
begin
if (select max(['+#col+']) from ['+#tblname+']) = 0
and (select min(['+#col+']) from ['+#tblname+']) = 0
print '' '+#col+' money''
end
if '''+#temp+'''!= ''money''
begin
IF NOT EXISTS (SELECT max([' + #col + ']) FROM ['+ #tblname + ']
WHERE len( [' + #col + ']) > 0 )
BEGIN
print '' ' + #col + ' ''
end
end'
As I understand it the column BBH_NEW.rebate2 is of type money when you get the error. In T-SQL you can't have a query that doesn't compile, and that is what you are encountering. Even though the query in the always-false if block won't run, it doesn't compile because the data types don't match.
First, a quick solution for you - use CONVERT or CAST to explicitly change the data type.
if 'money'!= 'money'
begin
IF NOT EXISTS (SELECT top 1 * FROM [BBH_NEW] WHERE [rebate2] IS NOT NULL and
len(CONVERT(VARCHAR(8000), [rebate2])) > 0 )
BEGIN
print ' rebate2 '
end
end
But, there has to be a better way to do whatever you are doing... When does that SQL get generated? If it is at runtime, can you just not generate the part that won't run? Maybe something like this?
SELECT #hold = CASE WHEN #temp = 'money' THEN
'if (select max(['+#col+']) from ['+#tblname+']) = 0
and (select min(['+#col+']) from ['+#tblname+']) = 0
print '' '+#col+' money'''
ELSE
'IF NOT EXISTS (SELECT max([' + #col + ']) FROM ['+ #tblname + ']
WHERE len( [' + #col + ']) > 0 )
BEGIN
print '' ' + #col + ' ''
end'
END
or maybe change the generation to this...
SELECT #temp = DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = #tblname
AND COLUMN_NAME = #col
IF(#temp = 'money')
SELECT #hold = 'IF(EXISTS(
SELECT 1
FROM ['+#tblname+']
HAVING MAX(['+#col+']) = 0
AND MIN(['+#col+']) = 0))
BEGIN
PRINT '' '+#col+' ''
END';
ELSE
SELECT #hold = 'IF(NOT EXISTS(
SELECT *
FROM ['+#tblname+']
WHERE ['+#col+'] IS NOT NULL
AND LEN(['+#col+']) > 0))
BEGIN
PRINT '' '+#col+' ''
END';
Some hints to optimize the generator
Rewrite
SELECT #hold =
'if '''+#temp+'''= ''money''
begin
...
end
as
if #temp = 'money'
begin
...
end
Second, I cannot think of a case when
[rebate2] IS NOT NULL
does not imply
len(CONVERT(VARCHAR(8000), [rebate2])) > 0
in other words, as soon as rebate2 is not NULL, its string length is greater 0
Related
I want to search GTIN option in admin product list. So, for that I am providing GTIN value in ProductLoadAllPaged store procedure. Now, when I search GTIN value from product list at that time throw datatable error and from console application get message that
System.Data.SqlClient.SqlException (0x80131904): Conversion failed when converting the nvarchar value 'abc' to data type int.
Here is store procedure added code,
ALTER PROCEDURE [dbo].[ProductLoadAllPaged]
(
#GTIN nvarchar(50) --AWAZ
)
AS
BEGIN
.....
--filter by vendor
IF #VendorId > 0
BEGIN
SET #sql = #sql + '
AND p.VendorId = ' + CAST(#VendorId AS nvarchar(max))
END
--AWAZ
IF #GTIN is not null
BEGIN
SET #sql = #sql + '
AND p.Gtin = ' + #GTIN
END
--filter by warehouse
IF #WarehouseId > 0
BEGIN
--we should also ensure that 'ManageInventoryMethodId' is set to 'ManageStock' (1)
--but we skip it in order to prevent hard-coded values (e.g. 1) and for better performance
SET #sql = #sql + '
AND
(
(p.UseMultipleWarehouses = 0 AND
p.WarehouseId = ' + CAST(#WarehouseId AS nvarchar(max)) + ')
OR
(p.UseMultipleWarehouses > 0 AND
EXISTS (SELECT 1 FROM ProductWarehouseInventory [pwi]
WHERE [pwi].WarehouseId = ' + CAST(#WarehouseId AS nvarchar(max)) + ' AND [pwi].ProductId = p.Id))
)'
END
.....
END
so seems like you need to chnage this part of code :
--AWAZ
IF #GTIN is not null
BEGIN
SET #sql = #sql + '
AND p.Gtin like ''%' + #GTIN + '%'''
END
How to select columns in a table that only contain a specific value for all the rows? I am trying to find these columns to do an update on those values with a NULL value. In my columns I have varied range of values including NA
I am using SQL Server 2012.
I've tried doing: thsi only gives me column names. Can i add to this condition for columns with value 'NA'?
SELECT COLUMN_NAME AS NAMES,COLUMN_DEFAULT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'ABC'
I am a beginner in SQL. Trying to figure out how to do this.
If min of column equals to max then that column contains same values:
Select
case when min(col1) = max(col1) then 1 else 0 end as Col1IsSame,
case when min(col2) = max(col2) then 1 else 0 end as Col2IsSame,
...
from Table
With dynamic query:
declare #s nvarchar(max) = 'select '
select #s = #s + 'case when min(' + COLUMN_NAME + ') = max(' +
COLUMN_NAME + ') then 1 else 0 end as ' + COLUMN_NAME + ','
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'Table'
Set #s = substring(#s, 1, len(#s) - 1) + ' from Table'
exec(#s)
TRY THIS QUERY
DECLARE #SQLQUERY NVARCHAR(MAX)
declare #tableName varchar(50)
DECLARE #NAME VARCHAR(50)
Declare #ParamDefinition AS NVarchar(2000)
Set #ParamDefinition = '#OIM VARCHAR(20)'
SELECT NAME
FROM sys.objects
WHERE [object_id]=#OIM
set #tableName= (SELECT NAME
FROM sys.objects
WHERE [object_id]=#OIM)
SET #NAME=(SELECT C.NAME
FROM sys.columns c
JOIN
sys.tables t ON c.object_id = t.object_id
WHERE c.name in (select distinct name
from sys.columns
where object_id=#OIM))
SET #SQLQUERY = ''
SELECT #SQLQUERY = #SQLQUERY + 'UPDATE ' + #tableName + ' SET ' + #NAME + ' = NULL WHERE ' + #NAME + ' = NA ; '
PRINT #SQLQUERY
Execute sp_Executesql #SQLQUERY , #ParamDefinition, #OIM
end
I'm a little new at SQL so please bear with me. I am attempting to write some a query that will allow me to loop through an entire table and find the number of times null values appear in each column. This is easy to do the hard way by typing the following:
Select
SUM(CASE COL_1 WHEN IS NULL THEN 1 ELSE 0 END) AS COL_1_NULLS
,SUM(CASE COL_2 WHEN IS NULL THEN 1 ELSE 0 END) AS COL_2_NULLS
FROM TABLE1
This is easy but it can become arduous if you want to do this for multiple tables or if a single table has a lot of columns.
I'm looking for a way to write a query that passes a table name into it and then loops through each column in the defined table (possibly pulling the column name by ordinance via a join to a metadata view?) and then sums the number of nulls in the column. Before anyone jumps on the nitpick bandwagon please keep in mind that this basic idea could be used for more than just finding nulls. Any assistance with this issue is greatly appreciated.
You need to use dynamic sql:
declare #custom_sql varchar(max)
set #custom_sql = 'SELECT null as first_row'
select
#custom_sql = #custom_sql + ', ' + 'SUM(CASE WHEN ' + COLUMN_NAME + ' IS NULL THEN 1 ELSE 0 END) as ' + COLUMN_NAME + '_NULLS'
from
INFORMATION_SCHEMA.COLUMNS where table_name = 'MYTABLE'
set #custom_sql = #custom_sql + ' FROM MYTABLE'
exec(#custom_sql)
You can also use the COALESCE term (just for a slightly different approach):
declare #custom_sql varchar(max)
select
#custom_sql = COALESCE(#custom_sql + ', ', '') + 'SUM(CASE WHEN ' + COLUMN_NAME + ' IS NULL THEN 1 ELSE 0 END) as ' + COLUMN_NAME + '_NULLS'
from
INFORMATION_SCHEMA.COLUMNS where table_name = 'users'
set #custom_sql = 'SELECT ' + #custom_sql
set #custom_sql = #custom_sql + ' FROM Users'
print #custom_sql
exec(#custom_sql)
I don't know how to make a generic query, but you can always generate the script like this
declare #sql nvarchar(max) = 'select 1 as dummy'
select #sql = #sql + '
, sum(case when [' + c.name + '] is null then 1 else 0 end) as [' + c.name + '_NULLS]'
from sys.columns c
join sys.tables t on t.object_id = c.object_id
where t.name = 'TABLE1'
set #sql = #sql + ' from TABLE1'
select #sql
Then you can execute the result eg. with exec sp_executesql #sql
For a cooler approach, you can use ISNULL to skip the first comma.
declare #sql nvarchar(max)
declare #tablename nvarchar(255) = 'xxxx'
Select #sql = ISNULL(#SQL + ',','') + ' ' + COLUMN_NAME + '_count = Sum(case when ' + COLUMN_NAME + ' is null then 1 else 0 end)' + char(13)
From information_schema.columns
where table_name = #tablename
set #sql = 'Select' + #sql + ' From ' + #tablename
print #sql
exec sp_executesql #sql
The following query returns the values of the table for each field in terms of null percentage . What I want is to get the sum of those percentages for a specific ProductID. Also, I would like to get a percentage (in an extra column) of the fields do not have value i.e. ="". Any ideas?
use AdventureWorks
DECLARE #TotalCount decimal(10,2), #SQL NVARCHAR(MAX)
SELECT #TotalCount = COUNT(*) FROM [AdventureWorks].[Production].[Product]
SELECT #SQL =
COALESCE(#SQL + ', ','SELECT ') +
'cast(sum (case when ' + QUOTENAME(column_Name) +
' IS NULL then 1 else 0 end)/#TotalCount*100.00 as decimal(10,2)) as [' +
column_Name + ' NULL %]
'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Product' and TABLE_SCHEMA = 'Production'
SET #SQL = 'set #TotalCount = NULLIF(#TotalCount,0)
' + #SQL + '
FROM [AdventureWorks].Production.Product'
print #SQL
EXECUTE SP_EXECUTESQL #SQL, N'#TotalCount decimal(10,2)', #TotalCount
You can use the following:
use AdventureWorks
DECLARE #colCount int;
DECLARE #nullCheck nvarchar(max) = N'';
DECLARE #emptyCheck nvarchar(max) = N'';
DECLARE #SQL NVARCHAR(MAX);
DECLARE #KeyToCheck int = 123; -- adapt as necessary
SELECT
#nullCheck += '
+ ' + 'count(' + QUOTENAME(column_Name) + ')'
,#emptyCheck += '
+ ' +
CASE
WHEN DATA_TYPE IN('bigint', 'int', 'smallint', 'tinyint', 'bit', 'money', 'smallmoney', 'numeric', 'decimal', 'float', 'real') THEN
-- check numeric data for zero
'sum(case when coalesce(' + QUOTENAME(column_Name) + ', 0) = 0 then 1 else 0 end)'
WHEN DATA_TYPE like '%char' or DATA_TYPE like '%text' THEN
--check character data types for empty string
'sum(case when coalesce(' + QUOTENAME(column_Name) + ', '''') = '''' then 1 else 0 end)'
ELSE -- otherwise, only check for null
'sum(case when ' + QUOTENAME(column_Name) + ' IS NULL then 1 else 0 end)'
END
,#colCount =
count(*) over()
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Product' and TABLE_SCHEMA = 'Production'
;
SET #SQL = 'SELECT case when count(*) > 0 then 100.00 - (' + #nullCheck + '
) * 100.00 / ' + cast(#colCount as nvarchar(max)) + '.00 / count(*) end as null_percent
, case when count(*) > 0 then (' + #emptyCheck + '
) * 100.00 / ' + cast(#colCount as nvarchar(max)) + '.00 / count(*) end as empty_percent
FROM Production.Product
WHERE ProductID = ' + cast(#KeyToCheck as nvarchar(max))
;
print #SQL;
EXECUTE (#SQL)
I simplified one of your expressions: Instead of sum (case when <column> IS NULL then 1 else 0 end), you can just use count(<column>). When using count with an expression instead of *, it counts the rows where this expression is non-null. As this is the opposite from what you need, I added the 100.00 - as the start of the SELECT.
For the "empty check", this would make the logic more complex to understand, hence I left the original logic there and extended it. There, I implemented an check for emptiness for numeric and character/text data types. You can easily extend that for date, binary data etc. with whichever logic you use to determine if a column is empty.
I also found it more simple to leave first + in the two variables #nullCheck and #emptyCheck, as it is valid SQL to start an expression wit this.
I also extended the statement so that if there would potentially be more than one record with ProductId = 123, it shows the average across all records, i. e. the total sum divided by the count of rows. And the outermost case expressions just avoid an division by zero error if count(*) would be zero, i. e. no record with ProductId = 123 found. In that case the return value is null.
You could use AVG function:
SELECT AVG(CASE WHEN value IS NULL THEN 100 ELSE 0 END) AS Percents
FROM Table
UPDATE:
Here is your script:
DECLARE #SQL NVARCHAR(MAX), #TABLE_NAME NVARCHAR(MAX), #TABLE_SCHEMA NVARCHAR(MAX), #PK NVARCHAR(MAX)
SET #TABLE_NAME = 'tblBigTable'
SET #TABLE_SCHEMA = 'dbo'
SET #PK = '8'
SELECT
#SQL = COALESCE(#SQL + ', ', 'SELECT ') +'AVG(CASE WHEN ' + COLUMN_NAME + ' IS NULL THEN 100 ELSE 0 END) AS [' + COLUMN_NAME +' NULL %]'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = #TABLE_SCHEMA AND
TABLE_NAME = #TABLE_NAME
SET #SQL = #SQL + ' FROM ' + #TABLE_NAME + ' WHERE pkId = ''' + #PK + ''''
print #SQL
EXECUTE SP_EXECUTESQL #SQL
I have been tasked with creating a service broker using Xquery to handle tracking changes on a collection of tables. I have figured out how to pass the messages (xml of column names and the updated and deleted tables for the statements). The aim is to get the list of column names and then compare the like column for each updated/deleted row and not a change.
Here is a sample of the XML:
<Update xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<TableName>
<ID>2414</ID>
<fkEvent>2664</fkEvent>
<fkType xsi:nil="true" />
<Description>Phil Test 3</Description>
<DTS>2011-04-04T14:01:36.533</DTS>
<uID>192204FA-612F-46F4-A6CB-1B4D53769A81</uID>
<VersionID xsi:nil="true" />
<UpdateDateTime>2011-04-04T14:04:31.013</UpdateDateTime>
<DeleteFlag>0</DeleteFlag>
<Updated>0</Updated>
<Owner>42</Owner>
<CreatedBy>42</CreatedBy>
</TableName>
</Update>
Generated by:
SET #xml1 = (SELECT * FROM TableName ORDER BY ID DESC FOR XML AUTO, ELEMENTS XSINIL, ROOT('MsgEnv'))
I have the following code:
WHILE #cnt <= #totCnt BEGIN
SELECT #child = #ColNames.query('/Columns/name[position()=sql:variable("#cnt")]')
SET #CurrentCol = REPLACE(REPLACE(CAST(#child AS VARCHAR(500)), '<name>', ''), '</name>', '')
PRINT #CurrentCol
WHILE #updateCnt <= #updateCntTotal BEGIN
SELECT #childUpdate = #xml1.query('/Update/TableName/sql:variable("#CurrentCol")')
PRINT CAST(#childUpdate AS VARCHAR(MAX))
WHILE #deleteCnt <= #deleteCntTotal BEGIN
SELECT #deleteCnt = #deleteCnt + 1
END
SET #deleteCnt = 1
SELECT #updateCnt = #updateCnt + 1
END
SET #updateCnt = 1
SELECT #cnt = #cnt + 1
END
The trouble I am having is dynamically setting the column name for this statement:
SELECT #childUpdate = #xml1.query('/Update/TableName/sql:variable("#CurrentCol")')
I have tried a few different variations using the sql:variable. Is it not possible to do this? I'd like to be able to do this dynamically as there are lots of tables we need to "audit" changes on.
Edit 1:
SELECT #childUpdate = #xml1.query('/Update/TableName/*[name() = sql:variable("#CurrentCol")]')
Yields this error (including the . in the () has a similar effect.
Msg 2395, Level 16, State 1, Line 34
XQuery [query()]: There is no function '{http://www.w3.org/2004/07/xpath-functions}:name()'
Your XQuery expression:
/Update/TableName/sql:variable("#CurrentCol")
It will call sql:variable() extension function for each /Update/TableName element.
If you want to select TableName's child with the same name as the string result of your extension function, then use:
/Update/TableName/*[name(.) = sql:variable("#CurrentCol")]
The previous answer didn't help at all but here is what I have found to work for this situation. The trigger will pass in 4 XML strings. The first contains the column information, the next two are the XML contents of the INSERTED and DELETED temporary tables, and the last is a Meta string (schema name, table name, updated by user, timestamp, etc).
Here is what the column XML code looks like:
DECLARE #ColNames XML
DECLARE #ColumnTypeInfo TABLE (
column_name varchar(100),
data_type varchar(100))
INSERT INTO #ColumnTypeInfo (column_name,data_type)
(
SELECT column_name 'column_name',
CASE WHEN
DATA_TYPE = 'datetime' OR DATA_TYPE = 'int' OR DATA_TYPE = 'bit' OR
DATA_TYPE = 'uniqueidentifier' OR DATA_TYPE = 'sql_variant'
THEN DATA_TYPE ELSE
CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN
data_type + '(' +
CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10))
+ ')'
ELSE
CASE WHEN NUMERIC_PRECISION IS NOT NULL AND NUMERIC_SCALE IS NOT NULL THEN
data_type + '(' +
CAST(NUMERIC_PRECISION AS VARCHAR(10))
+ ',' +
CAST(NUMERIC_SCALE AS VARCHAR(10))
+ ')'
ELSE
DATA_TYPE
END
END
END 'data_type'
FROM information_schema.columns WHERE table_name = 'tbl_ActivityPart'
)
SET #ColNames = (
SELECT * FROM #ColumnTypeInfo
FOR XML PATH ('Column'), ROOT('ColumnDef')
)
#ColNames is passed into the message queue.
This is the basis for the procedure that processes the queued messages:
WHILE #cnt <= #totCnt BEGIN
SET #CurrentCol = CAST(#ColNames.query('for $b in /ColumnDef/Column[position()=sql:variable("#cnt")]/column_name return ($b)') AS VARCHAR(MAX))
SET #CurrentCol = REPLACE(REPLACE(#CurrentCol, '<column_name>', ''), '</column_name>', '')
SET #DataType = CAST(#ColNames.query('for $b in /ColumnDef/Column[position()=sql:variable("#cnt")]/data_type return ($b)') AS VARCHAR(MAX))
SET #DataType = REPLACE(REPLACE(#DataType, '<data_type>', ''), '</data_type>', '')
SET #updateQuery = '/Update/Scheme.TableName/'+#CurrentCol
SET #SQL = 'SELECT #TmpXML = #UpdatedXML.query(''' + #updateQuery + ''')'
EXEC sp_executesql #SQL, N'#UpdatedXML xml, #TmpXML XML output', #UpdatedXML, #TmpXML output
SET #childUpdate = #TmpXML
SET #NewValue = REPLACE(REPLACE(CAST(#childUpdate AS VARCHAR(8000)), '<'+#CurrentCol+'>', ''), '</'+#CurrentCol+'>', '')
IF (CHARINDEX('xsi:nil="true"', CONVERT(VARCHAR(8000), #NewValue)) <> 0) BEGIN
SET #NewValue = NULL
END
SET #deleteQuery = '/Delete/Scheme.TableName/'+#CurrentCol
SET #SQL = 'SELECT #TmpXML = #DeletedXML.query(''' + #deleteQuery + ''')'
EXEC sp_executesql #SQL, N'#DeletedXML xml, #TmpXML XML output', #DeletedXML, #TmpXML output
SET #childDelete = #TmpXML
SET #OldValue = REPLACE(REPLACE(CAST(#childDelete AS VARCHAR(8000)), '<'+#CurrentCol+'>', ''), '</'+#CurrentCol+'>', '')
IF (CHARINDEX('xsi:nil="true"', CONVERT(VARCHAR(8000), #OldValue)) <> 0) BEGIN
SET #OldValue = NULL
END
IF #NewValue <> #OldValue BEGIN
INSERT INTO #Changes (SchemaName, TableName, FieldName, DTS,
[uID], OldValue, NewValue, ValueDataType, [User])
SELECT #Schema, #TableName, #CurrentCol, #TimeStamp,
CONVERT(UNIQUEIDENTIFIER, #CurrentUID), #OldValue, #NewValue, #DataType, #UpdateUserID
END
-- **********************************************************************************************************
SELECT #cnt = #cnt + 1
END
The contents of #Changes is then inserted into the permanent table (which is now on a separate disk volume from the rest of the tables in that database).