I have a situation where I might have a date to search on and I might not. I have a variable that creates a where clause: where field1 = date, if there is a date otherwise i create a blank varable. the problem is adding this to the end of my sql statement.
Select * from table + #where
Select # from table & #where
neither work
msg 102, Level 15, State 1, Line 65
Incorrect syntax near '+'
The best practice would be a procedure with code that allows having a NULL argument. Dynamic SQL can get injected if done poorly or become hard to maintain if you have conditional branches to add more joins, clauses in the WHERE, etc.
CREATE PROCEDURE your_proc
#search_date DATETIME
AS
BEGIN
SELECT *
FROM your_table
WHERE your_date_col >= ISNULL(#search_date, '9999-12-31')
END
GO
Now, if you have a variable, you can call your procedure with it:
DECLARE #variable DATETIME = '2018-01-01'
EXEC your_proc #variable
Or, you can leave it NULL and run the same code:
EXEC your_proc NULL
you can alter the where clause so it will only use the variable when it is not empty,
like this for example
declare #SearchValue date = null -- set a date here if you want to use this variable in the where clause
select t.*
from YourTable t
where (#SearchValue is null or t.YourDateColumn = #SearchValue)
when the variable #SearchValue is empty, then there will be no check done because the expression between the brackets will already be true
Like this you can avoid using dynamic sql
Related
I've created a dynamic SQL script to generate statements to replace empty strings with NULL for every column in every table in my database. I've stored the script to the variable #SQL.
When I run EXEC #SQL, it generates the following results:
(No column name)
UPDATE [TableX] SET [ColumnA] = NULL WHERE [ColumnA] =''
UPDATE [TableX] SET [ColumnB] = NULL WHERE [ColumnB] =''
UPDATE [TableX] SET [ColumnC] = NULL WHERE [ColumnC] =''
UPDATE [TableY] SET [ColumnA] = NULL WHERE [ColumnA] =''
UPDATE [TableY] SET [ColumnB] = NULL WHERE [ColumnB] =''
UPDATE [TableY] SET [ColumnB] = NULL WHERE [ColumnB] =''
And so on... (there is an inconsistent/unknown number of columns and tables, and therefore an inconsistent/unknown number of results).
My problem is that rather than simply returning these statements as results, I want to actually execute all of the individual statements. I would be very grateful if someone could advise me on the easiest way to do so.
Thanks in advance!
EXEC #SQL will not return the results in your question unless #SQL is the name of a stored procedure. It seems you are actually using EXEC (#SQL) to execute a batch of SELECT statements, each of which returns a single column, single row result with a column containing an UPDATE statement.
Below is example code you can add to the end of your existing script to concatenate the multiple result sets into a single batch of statements for execution.
DECLARE #UpdateStatementsBatch nvarchar(MAX);
DECLARE #UpdateStatementsTable TABLE(UpdateStatement nvarchar(MAX));
INSERT INTO #UpdateStatementsTable
EXEC(#SQL);
SELECT #UpdateStatementsBatch = STRING_AGG(UpdateStatement,N';') + N';'
FROM #UpdateStatementsTable;
EXEC (#UpdateStatementsBatch);
It's probably better to modify your existing code to build the batch of update statements rather than concatenate after the fact but I can't provide an example without seeing your existing code.
Here's a suggestion for something you can try to build a single update statement per table.
Obviously I have no idea what you've built to construct your existing sql but you should be able to tweak to your requirements.
Basically concatenate all columns for all tables where they are a varchar datatype. If they are an empty string, update to null, otherwise retain current value.
I don't know what version of Sql server you have so I've used for xml for compatability, replace with string_agg if supported. Add any additional filtering eg for only specific tables.
with c as (
select c.table_name, cols=Stuff(
(select concat(',',QuoteName(column_name),' = ','iif(', QuoteName(column_name), '='''', NULL, ', QuoteName(column_name), ')',Char(10))
from information_schema.columns c2
where data_type in ('varchar','nvarchar') and c2.table_name=c.table_name
for xml path('')
),1,1,'')
from information_schema.columns c
group by c.table_name
)
select concat('update ', QuoteName(c.table_name), ' set ', cols,';')
from c
where cols is not null
As this is presumably a one-off data fix you can just cut and paste the resulting sql into SSMS and run it.
Alternatively you could add another level of concatenation and exec it if you wanted to make it something you can repeat.
In T-SQL, I can create a table variable using syntax like
DECLARE #table AS TABLE (id INT, col VARCHAR(20))
For now, if I want to create an exact copy of a real table in the database, I do something like this
SELECT *
FROM INFOMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'MY_TABLE_NAME'
to check the column datatype and also max length, and start to create the #table variable, naming the variable, datatype and max_length one by one which is not very effective. May I know if there is any simpler way to do it like
DECLARE #table AS TABLE = SOME_REAL_TABLE_IN_DATABASE
Furthermore, is there any way to retrieve the column name, data type and max length of the column and use it directly in the declaration like
DECLARE #table AS TABLE (#col1_specs)
Thank you in advance.
EDIT:
Thanks for the answers and comments, we can do that for #table_variable but only in dynamic SQL and it is not good for maintainability. However, we can do that using #temp_table.
Based on the answer by Ezlo, we can do something like this :
SELECT TABLE.* INTO #TEMP_TABLE FROM TABLE
For more information, please refer to this answer.
Difference between temp table and table variable (stackoverflow)
Difference between temp table and table variable (dba.stackexchange)
Object names and data types (tables, columns, etc.) can't be parameterized (can't come from variables). This means you can't do the following (which would be required to copy a table structure, for example):
DECLARE #TableName VARCHAR(50) = 'Employees'
SELECT
T.*
FROM
#TableName AS T
The only workaround is to use dynamic SQL:
DECLARE #TableName VARCHAR(50) = 'Employees'
DECLARE #DynamicSQL VARCHAR(MAX) = '
SELECT
T.*
FROM
' + QUOTENAME(#TableName) + ' AS T '
EXEC (#DynamicSQL)
However, variables (scalar and table variables) declared outside the dynamic SQL won't be accessible inside as they lose scope:
DECLARE #VariableOutside INT = 10
DECLARE #DynamicSQL VARCHAR(MAX) = 'SELECT #VariableOutside AS ValueOfVariable'
EXEC (#DynamicSQL)
Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "#VariableOutside".
This means that you will have to declare your variable inside the dynamic SQL:
DECLARE #DynamicSQL VARCHAR(MAX) = 'DECLARE #VariableOutside INT = 10
SELECT #VariableOutside AS ValueOfVariable'
EXEC (#DynamicSQL)
Result:
ValueOfVariable
10
Which brings me to my conclusion: if you want to dynamically create a copy of an existing table as a table variable, all the access of your table variable will have to be inside a dynamic SQL script, which is a huge pain and has some cons (harder to maintain and read, more prone to error, etc.).
A common approach is to work with temporary tables instead. Doing a SELECT * INTO to create them will inherit the table's data types. You can add an always false WHERE condition (like WHERE 1 = 0) if you don't want the actual rows to be inserted.
IF OBJECT_ID('tempdb..#Copy') IS NOT NULL
DROP TABLE #Copy
SELECT
T.*
INTO
#Copy
FROM
YourTable AS T
WHERE
1 = 0
The answer for both questions is simple NO.
Although, I agree with you that T-SQL should change in this way.
In the first case, it means having a command to clone a table structure.
Of course, there is a possibility to make your own T-SQL extension by using SQLCLR.
I wrote a stored procedure to insert values into a table-valued parameter.
CREATE PROCEDURE insert_timezone_table
#TZ NVARCHAR(10)
AS
BEGIN
DECLARE #Table CT_sp;
IF type_id('[dbo].[COUNTRY_TIMEZONE_sp]') IS NOT NULL
DROP TYPE [dbo].[COUNTRY_TIMEZONE_sp];
CREATE TYPE COUNTRY_TIMEZONE_sp as TABLE (country_code NVARCHAR(2))
INSERT #Table (country_code)
SELECT country_code
FROM Timezones
WHERE fo_timezone = #TZ
END
Then I want to pass this as input for another stored procedure, using in the end in the WHERE clause like this:
WHERE country_code IN (SELECT country_code FROM #TimezoneTable)
This is not yielding what I want, so I have two questions:
Is it possible to make a simple select on the table-valued parameter to see exactly what fell inside there on the 1st procedure?
I reckon this can be a problem of missing quotes on the strings being passed in the IN clause.. How can I solve this problem? Anyway I can in the first stored procedure add the results of the other table concatenated with '' ?
WHERE country_code IN (SELECT country_code FROM #TimezoneTable)
I guess it is doing something like IN ( DE,PL) so it retrieves blanks, and it should be doing IN ('DE','PL') but I do not know how to put the country_codes in quotations
Thank you!
i created trigger called trgInsteadofdeleteEmp
and i just want to alter it, i wrote the following SQL code
alter trigger trgInsteadofdeleteEmp on Emp
instead of delete
as
begin
declare #id int , #name nvarchar(100)
select #id =id from deleted
select #name = name from deleted
insert into EmployeeAudit values (#id ,#name + 'tried to delete at' + GETDATE() as varchar(50))
end
and have the following output:
Msg 156, Level 15, State 1, Procedure trgInsteadofdeleteEmp, Line 8 Incorrect syntax near the keyword 'as'.
can someone point me in the direction of how to find the error
Thanks.
No, no, no, no, no.
Don't make the mistake of assuming that inserted and deleted have only one row. You are just putting errors in your code that are going to pop up at an unexpected time. I actually wish that SQL Server flagged this usage when creating the trigger.
Instead:
alter trigger trgInsteadofdeleteEmp on Emp
instead of delete
as
begin
insert into EmployeeAudit(id, name)
select id,
name + ' tried to delete at ' + convert(varchar(255), GETDATE(), 121) )
from deleted d;
end;
Your error is caused by the as. There seems to be a missing cast() function. But that is not the right fix. With date/times, use convert() or format() along with the desired format.
Other suggestions:
Always include the column names when doing an insert. In fact, an audit table really should have an identity id column, createdBy, and createdAt columns, all with default values.
Look at the strings that will be produced and be sure they are readable.
Use semicolons to end statements.
Don't rely on default formatting for date/time values.
This problem has bugged me so many times and i have now decided to try and find the proper solution for it, instead of my long-winded/dirty/horrible way I always revert to (copying the sql statement)
The table has a date column with a default value of NULL
Now what i need to do is pass a value (-1,0,1) to an sql statement which determines what to pull back
-1 - should bring back all rows
0 - should bring back rows with a NULL date value
1 - should bring back rows with a valid date value
I have tried using CASE statements, but the logical operator would need to change depending on the value being passed to the query.
Lets just call the table Quotes and the column completed, so...
CREATE TABLE 'Quotes'
(
completed DATETIME default(NULL)
)
Solution needs to be for SQL Server 2000, and i'm working in stored procedures not dynamic SQL. So it really needs to be all in one statement.
Thanks
Dan
Something like this in the WHERE clause
WHERE (#param = -1)
OR (#param = 0 AND completed IS NULL)
OR (#param = 1 AND completed IS NOT NULL)
Try this:
declare #param int
set #param=-1
declare #sql varchar(2000)
set #sql='select * from quotes '+
case #param when 0 then 'where completed is null'
when 1 then 'where completed is not null'
when -1 then ''
end
exec(#sql)
Raj