SQL Server Default Column value from function - sql

I want to set default value to Id column in Person table with a function
that goes like this:
Function:
IF OBJECT_ID ( 'GetLastId','FN') IS NOT NULL
DROP function GetLastId;
GO
CREATE FUNCTION [dbo].[GetLastId]
(#TableName nvarchar(max))
RETURNS int
AS
BEGIN
DECLARE #LastId int;
DECLARE #sql nvarchar(max);
SET #sql = 'SELECT #LastId = ISNULL(MAX(Id), 0) + 1 FROM ' + #TableName + ';'
EXECUTE sp_executesql #sql, N'#LastId int output', #LastId = #LastId output;
RETURN #LastId
END
and then:
UPDATE Person
SET Id = dbo.GetLastId('Person')
Running this code throws an error:
Only functions and some extended stored procedures can be executed from within a function.
So how to fix this and make it work as a default value?
And please do not say "Use triggers..." as I intend to use it with Entity Framework Core as default value for primary keys.
Thanks

You want a stored procedure, not a function:
create procedure [dbo].[GetLastId] (
#TableName nvarchar(max),
#LastId int output
) as
begin
declare #sql nvarchar(max);
set #sql = 'select #LastId = ISNULL(MAX(Id), 0) + 1 from ' + #TableName + ';'
EXECUTE sp_executesql #sql,
N'#LastId int output',
#LastId=#LastId output;
end;
You should also use quotename() around the table name to prevent unexpected things from happening.
Then you would call this as:
declare #lastId int;
exec dbo.GetLastId('Person', #lastid output);
update Person
set Id = #lastId;

You need to create stored procedure instead of function
create procedure [dbo].[GetLastId] (
#TableName nvarchar(max),
#ColumnName nvarchar(200),
#LastId int output
) as
begin
declare #sql nvarchar(max);
set #sql = 'select #LastId = ISNULL(MAX('+ #ColumnName +'), 0) + 1 from ' + #TableName + ';'
EXECUTE sp_executesql #sql,
N'#LastId int output',
#LastId=#LastId output;
end;
Then you can execute sp like below
declare #lastId int
exec dbo.GetLastId 'Person', 'Id' , #lastid output;
select #lastId
update Person
set Id = #lastId;

Related

T sql - How to store results from a dynamic query using EXEC or EXECUTE sp_executesql

I am trying to write a dynamic query. Let's say i have a table like below, which represents the hierarchy level of a sales agent:
AgentNumber Level1Agent Level2Agent Level3Agent Level4Agent Level5Agent
1122334455 1122334499 1122334488 1122334477 1122334466 1122334455
I want to be able to dynamically select a level based on a specified agent. My EXECUTE statement seems to work correctly, but how do I get the result stored in a variable I can use later? Every answer I have found seems to only get me a success return variable, not the actual query result.
Below is my code:
DECLARE #level INT = 1;
DECLARE #agent CHAR(10) = 1122334455;
DECLARE #colname NVARCHAR(11) = CONCAT('Level',#level,'Agent');
DECLARE #whereclause NVARCHAR(35) = CONCAT('WHERE AgentNumber = ',#agent);
DECLARE #qry NVARCHAR(300) = 'SELECT ' + #colname + ' FROM dbo.TABLE ' + #whereclause;
DECLARE #up NVARCHAR(10);
EXECUTE sp_executesql #qry, #up OUT
SELECT #up
The output of #up is NULL. If I change the last two lines to:
EXECUTE #up = sp_executesql #qry
SELECT #up
Now the output of #up is 0.
I want the output of 1122334499 and I need it stored in a variable that can later be used and inserted into a table.
Here is a fully functional example of how you can do this. Notice this is using a parameterized where clause and quotename around the column name in the dynamic sql to prevent sql injection.
if OBJECT_ID('tempdb..#Agents') is not null
drop table #Agents
create table #Agents
(
AgentNumber char(10)
, Level1Agent char(10)
, Level2Agent char(10)
, Level3Agent char(10)
, Level4Agent char(10)
, Level5Agent char(10)
)
insert #Agents
select '1122334455', '1122334499', '1122334488', '1122334477', '1122334466', '1122334455'
DECLARE #level INT = 3;
DECLARE #agent CHAR(10) = 1122334455;
DECLARE #colname NVARCHAR(11) = CONCAT('Level',#level,'Agent');
declare #agentout char(10)
DECLARE #qry NVARCHAR(300) = 'SELECT #agent_out = ' + quotename(#colname) + ' FROM #Agents WHERE AgentNumber = #agentin';
EXECUTE sp_executesql #qry, N'#agentin char(10), #agent_out char(10) output', #agentin = #agent, #agent_out = #agentout output
select #agentout
You can try this :
DECLARE #level INT = 1;
DECLARE #agent CHAR(10) = 1122334455;
DECLARE #colname NVARCHAR(11) = CONCAT('Level',#level,'Agent');
DECLARE #whereclause NVARCHAR(35) = CONCAT('WHERE AgentNumber = ',#agent);
DECLARE #qry NVARCHAR(300) = 'SELECT #agentout=' + #colname + ' FROM dbo.TABLE ' + #whereclause;
DECLARE #up NVARCHAR(10);
EXECUTE sp_executesql #qry, N'#agentout NVARCHAR(10) OUTPUT', #agentout=#up OUTPUT
SELECT #up
Create a variable table and makes your query insert the results you want there. Something like this:
declare #results table(field1 varchar(max), field2 varchar(max));
declare #sqlStatement varchar(max);
set #sqlStatement = 'insert into #results(field1, field2) select field1, field2 from table';
EXECUTE #sqlStatement;
select * from #results; --It will print the results from your sql statement!

Dynamic SQL output of a query to a variable

I would like to output the result of the dynamic SQL into a variable called #Count but not sure what the syntax or even the code should like to accomplish this.
The code looks as follows:
declare #tab nvarchar(255) = 'Person.person'
declare #Count int
declare #SQL nvarchar(max) = 'select count(*) from '+ #tab
exec(#SQl)
select #Count
thank you
Here's another way to do it that also safely addresses the SQL Injection isuues:
/* Counts the number of rows from any non-system Table, *SAFELY* */
-- The table name passed
DECLARE #PassedTableName as NVarchar(255) = 'Person.Person';
-- Make sure this isn't a SQL Injection attempt
DECLARE #ActualTableName AS NVarchar(255)
SELECT #ActualTableName = TABLE_SCHEMA + '.' + TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = PARSENAME(#PassedTableName,1)
AND TABLE_SCHEMA = PARSENAME(#PassedTableName,2)
-- make a temp table to hold the results
CREATE TABLE #tmp( cnt INT );
-- create the dynamic SQL
DECLARE #sql AS NVARCHAR(MAX)
SELECT #sql = 'SELECT COUNT(*) FROM ' + #ActualTableName + ';'
-- execute it and store the output into the temp table
INSERT INTO #tmp( cnt )
EXEC(#SQL);
-- Now, finally, we can get it into a local variable
DECLARE #result AS INT;
SELECT #result = cnt FROM #tmp;
You can utilize sp_executesql to execute your count() query, and output it #Count.
Try this:
-- Set the table to count from
declare #tab nvarchar(255) = 'Person.person'
-- Assign the SQL query
declare #SQL nvarchar(255) = N'SELECT count(*) FROM ' + #tab
-- Pepare for sp_executesql
declare #Count int
declare #Params nvarchar(100) = N'#Count int output'
-- Set the count to #Count
exec sp_executesql #SQL, #Params, #Count=#Count output
-- Output #Count
select #Count
One last thing: Person.person looks like you might be trying to reference a person column from a Person table. But the above query is a literal representation of what it looks like you're trying to achieve in your question.
The below question is pretty much identical to what you are asking here.
sp_executeSql with output parameter
DECLARE #retval int
DECLARE #sSQL nvarchar(500);
DECLARE #ParmDefinition nvarchar(500);
DECLARE #tablename nvarchar(50)
SELECT #tablename = N'products'
SELECT #sSQL = N'SELECT #retvalOUT = MAX(ID) FROM ' + #tablename;
SET #ParmDefinition = N'#retvalOUT int OUTPUT';
EXEC sp_executesql #sSQL, #ParmDefinition, #retvalOUT=#retval OUTPUT;
SELECT #retval;

SP_EXECUTESQL and Output Parameter

I would like get the ID from the query, but I am getting a NULL, where is my mistake?
DECLARE #TblZimz NVARCHAR(256)
DECLARE #IdModul INTEGER
DECLARE #Id INTEGER
SET #TblZimz = '_ZIMZ000001'
SET #IdModul = 1
--SET #Id = -1
EXECUTE [InsertZimz] #TblZimz, #IdModul, #Id OUTPUT
ALTER PROCEDURE [InsertZimz]
#TblZimz NVARCHAR(256)
, #IdModul NVARCHAR(256)
, #Id INTEGER OUTPUT
DECLARE #SqlQuery NVARCHAR(MAX)
SET #SqlQuery = 'SELECT TOP (1) ([ID]) FROM ' + #TblZimz + ' WHERE [ModulId] = ' + #IdModul
EXEC SP_EXECUTESQL #SqlQuery, N'#Id INTEGER OUTPUT', #Id OUTPUT
why the #Id Paramter is alwasy null? I cant see my mistake?
First, select the desired id in an output variable using #Id = ([ID]) then assign this #Id OUTPUT value in the #Id variable using #Id = #Id OUTPUT. Also, you should pass data in where clause using a variable to avoid sql injection problem like [ModulId] = #IdModul (i.e. you should not concatenate it like [ModulId] = ' + #IdModul). try this :
DECLARE #SqlQuery NVARCHAR(MAX)
SET #SqlQuery = 'SELECT TOP (1) #Id = ([ID]) FROM '
+ #TblZimz + ' WHERE [ModulId] = #IdModul'
EXEC SP_EXECUTESQL
#SqlQuery,
N'#Id INT OUTPUT, #IdModul INT',
#IdModul = #IdModul,
#Id = #Id OUTPUT
Check details of SP_EXECUTESQL here
Like Deepak's answer, but easier:
EXEC SP_EXECUTESQL #SqlQuery,
N'#Id INT OUTPUT, #IdModul INT',
#IdModul OUTPUT, #Id
Here's an example of returning a dynamically defined proc's return value (rather than select results):
CREATE PROC p AS return 3
GO
DECLARE #proc varchar(30) = 'p', #retval int
DECLARE #qry nvarchar(max) = 'EXEC #i = ' + #proc --plus params as needed
EXEC sp_executesql #qry, N'#i INT OUTPUT', #retval OUTPUT
select #retval --returns 3
(Use case: to do a full "data run," I cursor through a list of procs I run, each of which returns a row count for logging purposes. Chased my tail quite a while before cracking this.)

Can I use variable in condition statement as value with where condition that test that value

I have the following stored procedure:
ALTER proc [dbo].[insertperoll] #name nvarchar(50) , #snum int , #gnum int
as
DECLARE #value nvarchar(10)
SET #value = 's'+CONVERT(nvarchar(50),#snum)
DECLARE #sqlText nvarchar(1000);
DECLARE #sqlText2 nvarchar(1000);
DECLARE #sqlText3 nvarchar(1000);
declare #g nvarchar(50) = '''g1'''
SET #sqlText = N'SELECT ' + #value + N' FROM dbo.GrideBtable'
SET #sqlText2 = ' where Gnumber = '+#g --here is the problem it error invalid column name -- the #g is value from the table condition
set #sqlText3 = #sqlText+#sqlText2
Exec (#sqlText3) -- here how can i save the result of the exec into varibale
declare #sal nvarchar(50) = #sqlText3
insert employ (name,Snumber,Gnumber,Salary) values(#name,#snum,#gnum,#sal)
QUESTION: How to put in condition variable gets value from the table when i exec it it think that the #g is column but its not its a value from the table to test it so i display one value after the exec the other QUESTION is how to save the result from the exec in variable and then use that value
I'm using SQL Server 2008 (9.0 RTM)
This will be a stored procedure
Thanks in advance
Not sure why you would go through all the loops to insert into the table where you can have a simple insert query like ..
ALTER PROC dbo.[insertperoll] #name nvarchar(50) , #snum int , #gnum int
AS
insert employ (name, Snumber, Gnumber, Salary)
select #name
, #sum
, #gnum
, case when #snum = 1 then s1
when #snum = 2 then s2
when #snum = 3 then s3
when #snum = 4 then s4
end as Salary
from dbo.GrideBtable
where Gnumber = #gnum
If your intent is to have the proc retrieve a salary value from a column determined from the parameter snum and then make an insert into employ using the values passed as parameters and the salary retrieved I think you could refactor your procedure to this:
CREATE proc [dbo].[insertperoll] #name nvarchar(50) , #snum int , #gnum int AS
DECLARE #g NVARCHAR(50) = 'g1'
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'INSERT employ (name,Snumber,Gnumber,Salary) '
SET #sql += N'SELECT ' + QUOTENAME(#name, '''')
SET #sql += N', ' + CAST(#snum AS NVARCHAR(50))
SET #sql += N', ' + CAST(#gnum AS NVARCHAR(50))
SET #sql += N', s' + CAST(#snum AS NVARCHAR(50))
SET #sql += N' FROM dbo.GrideBtable'
SET #sql += N' WHERE Gnumber = ' + QUOTENAME(#g, '''')
EXEC (#sql)
Of course you could add the #g variable to the procedure parameters instead of having it hard coded in the procedure and call it as:
EXEC insertperoll #name='john', #snum=10, #gnum=100, #g='g1'
Sample SQL Fiddle (with some assumptions made about table structure)
You could do this using sp_executesql instead of exec() since this will allow you to use parameters, you can use an output parameter to get the value from the query:
DECLARE #SQL NVARCHAR(MAX) = N'SELECT #val = ' + CONVERT(NVARCHAR(10),#snum) +
N' FROM dbo.GrideBtable WHERE Gnumber = #G1';
DECLARE #val INT; -- NOT SURE OF DATATYPE REQUIRED
EXECUTE sp_executesql #SQL, N'#G1 VARCHAR(20), #val INT OUT', 'G1', #val OUT;

Dynamic sql statement to update a variable

my sql statement is something like this below
DECLARE #OLD_NAV_VALUE AS INT
DECLARE #FINAL AS INT
SELECT #OLD_NAV_VALUE = [col1] from TBL_BA where DATE = #id_Date
SET #FINAL = #OLD_NAV_VALUE * 50
But the problem i am haveing here is that the column name in the select statement which is given as [col1] is a dynamic value. So i am trying something like this below.
DECLARE #OLD_NAV_VALUE AS INT
DECLARE #FINAL AS INT
EXEC('SELECT #OLD_NAV_VALUE = [' + #DYNAMIC_COL_NAME + '] from TBL_BA where DATE = ' + #id_Date)
SET #FINAL = #OLD_NAV_VALUE * 50
this gives an error that #OLD_NAV_VALUE has to be declared. So i tried declaring #OLD_NAV_VALUE inside the EXEC statement. But if i do this i am not able to use the same outside the EXEC statement.
Please let me know how to do this.
You can also use the sp_executesql statement with an output parameter:
declare #field nvarchar(50);
set #field = N'FieldToSelect';
declare #sql nvarchar(3000);
declare #parmDefinition nvarchar(500);
SET #parmDefinition = N'#returnValueOUT nvarchar(50) OUTPUT';
set #sql = N'SELECT #ReturnValueOUT = ' + #Field + ' FROM [TableName] WHERE [SomeCondition]'
declare #returnValue nvarchar(50);
EXECUTE sp_executesql #sql, #parmDefinition, #returnValueOut = #returnValue OUTPUT;
SELECT #returnValue
First, I'd suggest that you do a Google on "Erland dynamic SQL" and read his white paper on the subject.
Your design is probably not the best if it requires that you use a dynamic column name like this.
The reason that you can't do what you're trying to do is that everything in the EXEC is entirely in its own scope. If you absolutely have to do it this way though then you could use a table (either a normal table, or a global temporary table) to store the value for use outside of the EXEC.
We've used sp_executesql. Here's another example of a parameterized record count:
DECLARE #sql AS nvarchar(MAX)
SET #sql = N'SELECT #RecordCount = COUNT(*) FROM [{#SchemaName}].[{#TableName}]'
SET #sql = REPLACE(#sql, '{#SchemaName}', #SchemaName)
SET #sql = REPLACE(#sql, '{#TableName}', #TableName)
DECLARE #RecordCount AS int
EXEC sp_executesql
#query = #sql,
#params = N'#RecordCount INT OUTPUT',
#RecordCount = #RecordCount OUTPUT
This worked for me.
I declared a temp table and used it to receive the values from the select statement.
Something like below.
declare #i int
declare #v int
create table #t (val int)
insert into #t
exec ('declare #i int set #i = 0 select #i+1')
select * from #t