Oracle function exchange for SQL Server - sql-server

I do an Oracle to SQL Server project,now I have a function that I can not rebuild,I don't know what is wrong,
The function is used to fetch the Oracle sequenes,the result is wrong.
Oracle function code
CREATE FUNCTION [test].[GETWORKORDERID] (numberPre varchar2)
return varchar2 is
PRAGMA AUTONOMOUS_TRANSACTION;
findId number(8); --maxid
nowNumber varchar2(50);
n_count number(8);
n_count2 number(8);
nowNumber2 varchar2(50);
begin
nowNumber := to_char(sysdate, 'yyyymmdd');
nowNumber2 := to_char(sysdate - 1, 'yyyymmdd');
select count(1)
into n_count2
from user_sequences t
where t.sequence_name = 'SEQ_' || numberPre || '_' || nowNumber2;
if n_count2 > 0 then
execute immediate 'drop sequence SEQ_' || numberPre || '_' ||
nowNumber2;
end if;
select count(1)
into n_count
from user_sequences t
where t.sequence_name = 'SEQ_' || numberPre || '_' || nowNumber;
if n_count = 0 then
execute immediate 'create sequence SEQ_' || numberPre || '_' ||
nowNumber ||
' minvalue 10000 maxvalue 99999999 start with 10000 increment by 1 NOCYCLE NOCACHE';
end if;
execute immediate 'select SEQ_' || numberPre || '_' || nowNumber ||
'.nextval from dual'
into findId;
commit;
return numberPre || nowNumber || findId;
end;**
I changed it for SQL Server to:
CREATE FUNCTION [test-CSR].[GETWORKORDERID]
(#numberPre nvarchar(max))
RETURNS TABLE
AS
BEGIN
DECLARE #findId bigint;
DECLARE #nowNumber bigint;
DECLARE #n_count bigint;
DECLARE #n_count2 bigint;
DECLARE #nowNumber2 bigint;
DECLARE #sql1 nvarchar(max);
DECLARE #sql2 nvarchar(max);
DECLARE #sql3 nvarchar(max);
DECLARE #sql4 nvarchar(max);
DECLARE #result nvarchar(max);
SET #nowNumber = CONVERT(varchar(30), GETDATE(), 20)
SET #nowNumber2 = CONVERT(varchar(30), GETDATE(), 20)
SELECT COUNT(1)
INTO n_count2
FROM user_sequences t
WHERE t.sequence_name = 'SEQ_' + numberPre + '_' + nowNumber2;
IF n_count2 > 0
BEGIN
SET #sql1 = 'drop sequence SEQ_' + numberPre + '_' + nowNumber2;
EXECUTE (#sql1)
END;
SELECT COUNT(1)
INTO n_count
FROM user_sequences t
WHERE t.sequence_name = 'SEQ_' + #numberPre + '_' + #nowNumber;
IF n_count = 0
BEGIN
SET #sql2 = 'create sequence SEQ_ ' + #numberPre + '_' + #nowNumber +
' minvalue 10000 maxvalue 99999999 start with 10000 increment by 1 NOCYCLE NOCACHE';
EXECUTE (#sql2)
END;
SET #sql3 = 'select SEQ_ ' + #numberPre + '_' + #nowNumber + '.NEXT VALUE FOR user_sequences';
SET #findId = #sql3;
SET #sql4 = #numberPre + #nowNumber + #findId;
INSERT INTO #result
EXEC (#sql4)
END;
Now I rebuild the function in SQL Server,but when SQL Server runs, the result is wrong, I don't know what the mistake is?

In functions you can not use EXECUTE. You try to create sequence. Functions in SQL Server are pure. They can not change objects and data in the database. I can suggest you to use a PROCEDURE instead of a FUNCTION.
If you declare a FUNCTION, it should be defined like this:
CREATE FUNCTION [test-CSR].[GETWORKORDERID]
(#numberPre nvarchar(max))
RETURNS #result TABLE (...)
AS
BEGIN
...
RETURN;
END
You should save count into variable like this:
SELECT #n_count2 = COUNT(1)
FROM user_sequences t
WHERE t.sequence_name = 'SEQ_' + numberPre + '_' + nowNumber2;
And then write:
IF #n_count2 > 0 ...
At the end you should remove INSERT INTO #result. Should be presented only EXEC (#sql4)
Hope, It can help you.

You'll have to change this to a procedure, if at all possible. There are some not-so-nice ways to run side-effecting functions in SQL Server, but they should be avoided. Check out the automatic translation that SQL Server Migration Assistant for Oracle for gory details.
Normally you should migrate to do things the "SQL Server way" when porting code.
Anyway, here's a idiomatic SQL Server translation of that procedure. But the whole process of dropping sequences is pretty strange.
--create schema [test-CSR]
go
CREATE or alter procedure [test-CSR].[GETWORKORDERID] #numberPre nvarchar(max), #result nvarchar(max) out
AS
/*
declare #result nvarchar(max)
exec [test-CSR].[GETWORKORDERID] N'foo', #result out
print #result
*/
BEGIN
DECLARE #findId bigint;
DECLARE #nowNumber bigint = CONVERT(varchar(30), GETDATE(), 112);
DECLARE #nowNumber2 bigint = CONVERT(varchar(30), GETDATE()-1, 112);
DECLARE #sql nvarchar(max);
declare #seq sysname = concat('SEQ_', #numberPre, '_', #nowNumber);
declare #seq2 sysname = concat('SEQ_', #numberPre, '_', #nowNumber2);
IF exists (select * from sys.sequences where name = #seq2)
BEGIN
SET #sql = concat('drop sequence ', quotename(#seq2));
--print #sql;
EXECUTE (#sql);
END;
IF not exists (select * from sys.sequences where name = #seq)
BEGIN
SET #sql = concat('create sequence ', quotename(#seq),
' start with 10000 increment by 1 minvalue 10000 maxvalue 99999999 NO CYCLE NO CACHE');
--print #sql;
EXECUTE (#sql)
END;
SET #sql = concat('select #findId = NEXT VALUE FOR ', quotename(#seq)) ;
--print #sql;
exec sp_executesql #sql, N'#findId bigint out', #findId = #findId out;
set #result = concat(#numberPre, #nowNumber, #findId);
END;
If you can make the change I would probably have a single sequence object, and pass the next value for that into a function like this:
create sequence WORKORDERID_seq start with 10000 increment by 1 minvalue 10000 maxvalue 99999999 NO CYCLE NO CACHE
go
create function [test-CSR].[GETWORKORDERID2] (#nowNumber nvarchar(max), #nv int)
returns nvarchar(max)
as
begin
return concat(CONVERT(varchar(30), GETDATE(), 112),#nowNumber,#nv)
end
and run a batch job to reset the sequence value every night.

Related

Merging more than one table into one existing table

This is the table creation and insertion query
If not exists(select * from sysobjects where name='hrs')
Create table hrs(hr int)
declare #cnt int =1
while #cnt <= 12
begin
insert into hrs values(#cnt)
set #cnt=#cnt+1
end
The above code gives the output like
but I just want that
declare #cnt1 int = 1
while #cnt1<=12
begin
EXEC('select he'+#cnt1+' = case when hr = 1 then '+#cnt1+' end from hrs')
set #cnt1=#cnt1+1
end
The above code returns the 12 different table but i just want the all records in one table (without creating any new table).
So, how can i do this?
Please help me.
Thanks.
Here the all column are created dynamically through loop
Here are the full query
declare #s varchar(MAX)=''
declare #j int = 1
while #j<=12
begin
if #j = 12
Set #s = #s+'he'+convert(varchar,#j)+'=MAX(case when hr='+convert(varchar,#j)+' then '+convert(varchar,#j)+' end)'
else
set #s = #s+'he'+convert(varchar,#j)+'=MAX(case when hr='+convert(varchar,#j)+' then '+convert(varchar,#j)+' end),'
set #j=#j+1
end
set #s = 'select '+#s+' from hrs'
exec(#s)
Your query doesn't make a lot of sense, but you can build a list of columns and then exec that:
declare #columns nvarchar(max)
declare #cnt int = 1
while #cnt <= 12
begin
set #columns = isnull(#columns + ', ', '') + 'He' + cast(#cnt as nvarchar) +
' = sum(case when hr = ' + cast(#cnt as nvarchar) + ' then hr end)'
end
declare #sql nvarchar(max) = 'select ' + #columns ' + from hr'
exec (#sql)

Challenge with dynamic SQL

I have a procedure that generates dynamic SQL that creates an insert into statement while querying an excel spreadsheet.
The resulting print from the messages screen can be pasted into an ssms window and executes. When I try to execute the SQL from within the stored procedure I get a syntax error as follows:
'SELECT * into TestClient FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0', 'Excel 12.0;HDR=YES;Database=G:\CustomerETL\Employee\PendingETL\ETLEmployeexls.xls;', [Sheet1$])'
Msg 102, Level 15, State 1, Line 15
Incorrect syntax near 'SELECT * into TestClient FROM OPENROWSET('.
Below is the entire stored procedure. I know the problem is in the ticks (within the SET blocks that create the dynamic SQL I just can't figure out where the missing ticks are.
Here is the proc:
USE [ETL]
GO
/****** Object: StoredProcedure [dbo].[ImportExcelSheetForCustomerEmployeeUpdate2] Script Date: 12/19/2017 4:03:05 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ImportExcelSheetForCustomerEmployeeUpdate2](#BatchID int)
as
--EXEC ImportExcelSheetForCustomerEmployeeUpdate 2
/* -- TRUNCATE TABLE FilesToImport
UPDATE FilesToImport
SET StatusID = 1
*/
-- Jeffery Williams
-- 12/18/2017
DECLARE #FileID int
,#ETLFilename varchar(250)
,#ClientName varchar(100)
,#FileType varchar(5)
,#ColumnCount int
,#RowsToETL int
,#StatusID int
,#Processed bit = 0
,#Count int
,#SQL nvarchar(4000)
,#Sheetname varchar(50) = '[Sheet1$]'
,#CMDSQL as varchar(4000)
,#SQLCmd NVARCHAR(MAX)
SELECT *
FROM FilesToImport
BEGIN
SELECT #Count = count(*)
FROM FilesToImport
WHERE BatchID = #BatchID
AND StatusID = 1
END
PRINT 'Count of records to process: ' + cast(#Count as varchar)
WHILE #Count > 0
BEGIN
BEGIN
SELECT TOP 1 #FileID = FileID, #ETLFilename = ETLFilename, #ClientName = ClientName
,#FileType = FileType, #ColumnCount = ColumnCount, #RowsToETL = RowsToETL
FROM FilesToImport
WHERE StatusID = 1
AND BatchID = #BatchID
END
-- Rename the file
set #CMDSQL = 'rename G:\CustomerETL\Employee\PendingETL\' + #ETLFilename + ' ETLEmployeexls.xls'
exec master..xp_cmdshell #CMDSQL
--PRINT cast(#cmdsql as varchar(4000))
-- Ciode below generates our select. Need to add an INTO clause and create a staging table for each import. Prior to this step we need to rename the file.
SET #SQL = ''''
SET #SQL = #SQL + 'SELECT * into ' + coalesce(#ClientName, 'TestClient') + ' FROM OPENROWSET('
SET #SQL = #SQL + ''''
SET #SQL = #SQL + '''' + 'Microsoft.ACE.OLEDB.12.0' + '''' --+ ', '
-- Excel 12.0;HDR=NO;Database=g:\UnZip\ImportSampleXLSX.xlsx;' + ''
SET #SQL = #SQL + '''' + ', '
SET #SQL = #SQL + '''' + '''Excel 12.0;HDR=YES;Database=G:\CustomerETL\Employee\PendingETL\ETLEmployeexls.xls;''' + '''' + ', ' + #Sheetname + ')'
SET #SQL = #SQL + ''''
PRINT cast(#SQL as varchar(8000))
EXEC sp_executesql #SQL
set #CMDSQL = 'rename G:\CustomerETL\Employee\PendingETL\ETLEmployeexls.xls ' + #ETLFilename
exec master..xp_cmdshell #CMDSQL
UPDATE FilesToImport
SET StatusID = 2
WHERE FileID = #FileID
/* -- TRUNCATE TABLE FilesToImport
UPDATE FilesToImport
SET StatusID = 1
*/
SET #Count = (#Count - 1)
CONTINUE
END
I am posting this as an answer but it should be comment. When I tried adding this as a comment StackOveflow kept thinking that I was trying to add #count as an email target.
In your code:
WHILE #Count > 0
BEGIN
BEGIN
SELECT TOP 1 #FileID = FileID, #ETLFilename = ETLFilename, #ClientName = ClientName
,#FileType = FileType, #ColumnCount = ColumnCount, #RowsToETL = RowsToETL
FROM FilesToImport
WHERE StatusID = 1
AND BatchID = #BatchID
END
you are not updating the value of #count. This will either never loop or loop forever. You probably want to add a statement (right before the end) such as this:
Set #count= ##rowcount;
Ben

SQL query create stored procedure error 'Incorrect syntax'

I use sql to create the stored procedures , but I get an error " Incorrect syntax near ' = ' "
And here is my code
CREATE PROC [dbo].[sp_T_CATEGORY_GetByTop]
#top nvarchar(10),
#where nvarchar(200),
#order nvarchar(200)
AS
DECLARE #SQL AS NVARCHAR(500)
SELECT #SQL = 'SELECT TOP ('+#top+') * FROM [T_CATEGORY]'
IF LEN(#top) = 0
BEGIN
SELECT #SQL = 'SELECT * FROM T_CATEGORY'
END
IF LEN(#where) > 0
BEGIN
SELECT #SQL = #SQL + 'WHERE' + #where
END
IF LEN(#order) > 0
BEGIN
SELECT #SQL = #SQL + 'ORDER BY' + #order
END
EXEC(#SQL)
thanks everyone.
You need space before and after where and order by
IF LEN(#where) > 0
BEGIN
SELECT #SQL = #SQL + ' WHERE ' + #where
END
IF LEN(#order) > 0
BEGIN
SELECT #SQL = #SQL + ' ORDER BY ' + #order
END

Mssql procedure doesn't return records

I wrote simply procedure, that should return some data.
When I simply replace execute query and put SELECT 1 It is returning 1. But when i paste EXECUTE query, it is return nothing. What should I do?
ALTER PROCEDURE [dbo].[PTC_Repor]
#camp VARCHAR(50),
#StartTime DATETIME,
#EndTime DATETIME
AS
BEGIN
EXECUTE('
SELECT ID,NAME
FROM [hpsdb].[dbo]. [' + #camp + ']
WHERE IS_CLEAR = 0
AND SUCCESS_COUNT = 0
AND DATA_STATUS = 5
AND CALL_TIME > ''' + #StartTime + '''
AND CALL_TIME <= ''' + #EndTime + '''
')
END
GO
You could try using sp_executesql so that you can pass the datetime values across directly and not force them to become strings:
declare #Parms nvarchar(max)
set #Parms = '#StartTime datetime, #EndTime datetime'
declare #SQL nvarchar(max)
set #SQL = 'SELECT ID,NAME
FROM [hpsdb].[dbo]. [' + #camp + ']
WHERE IS_CLEAR = 0
AND SUCCESS_COUNT = 0
AND DATA_STATUS = 5
AND CALL_TIME > #StartTime
AND CALL_TIME <= #EndTime'
EXEC sp_executesql #SQL,#Prams,#StartTime,#EndTime

How do I dynamically build a like clause in an executable sql stored procedure that uses EXEC sp_executesql?

The following stored procedure works correctly execpt when I pass in the #NameSubstring parameter. I know I am not dynamically building the like clause properly. How can I build the like clause when this parameter also needs to be passed as a parameter in the EXEC sp_executesql call near the bottom of the procedure?
ALTER PROCEDURE [dbo].[spGetAutoCompleteList]
(
#AutoCompleteID int,
#StatusFlag int,
#NameSubstring varchar(100),
#CompanyID int,
#ReturnMappings bit,
#ReturnData bit
)
AS
DECLARE #ErrorCode int,
#GetMappings nvarchar(500),
#Debug bit,
#Select AS NVARCHAR(4000),
#From AS NVARCHAR(4000),
#Where AS NVARCHAR(4000),
#Sql AS NVARCHAR(4000),
#Parms AS NVARCHAR(4000)
SET #ErrorCode = 0
SET #Debug = 1
BEGIN TRAN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF #AutoCompleteID IS NOT NULL OR #StatusFlag IS NOT NULL OR #NameSubstring IS NOT NULL
BEGIN
SET #Select = '
SELECT ac.AutoCompleteID,
ac.AutoCompleteName,
ac.CompanyID,
ac.StatusFlag,
ac.OwnerOperID,
ac.CreateDT,
ac.CreateOperID,
ac.UpdateDT,
ac.UpdateOperID,
ac.SubmitOperID,
ac.SubmitDT,
ac.ReviewComments'
SET #GetMappings = '
Select ac.AutoCompleteID'
IF #ReturnData = 1
BEGIN
SET #Select = #Select + '
, ac.AutoCompleteData'
END
SET #From = '
FROM tbAutoComplete ac'
SET #Where = '
WHERE 1=1'
IF #AutoCompleteID IS NOT NULL
BEGIN
SET #Where = #Where + '
AND ac.AutoCompleteID = CAST(#AutoCompleteID AS nvarchar)'
END
IF #StatusFlag IS NOT NULL
BEGIN
SET #Where = #Where + '
AND ac.StatusFlag = CAST(#StatusFlag AS nvarchar)'
END
IF #NameSubstring IS NOT NULL
BEGIN
SET #Where = #Where + '
AND ac.AutoCompleteName like #NameSubstring' + '%'
END
SET #Where = #Where + '
AND ac.CompanyID = + CAST(#CompanyID AS nvarchar)'
SET #Sql = #Select + #From + #Where
SET #Parms = '
#AutoCompleteID int,
#StatusFlag int,
#NameSubstring varchar(100),
#CompanyID int'
EXEC sp_executesql #Sql,
#Parms,
#AutoCompleteID,
#StatusFlag,
#NameSubstring,
#CompanyID
IF #ReturnMappings = 1
BEGIN
SET #GetMappings = 'Select * FROM tbAutoCompleteMap acm WHERE acm.AutoCompleteID IN(' + #GetMappings + #From + #Where + ')'
--EXEC sp_executesql #GetMappings
END
IF #Debug = 1
BEGIN
PRINT #GetMappings
PRINT #Sql
END
END
SELECT #ErrorCode = #ErrorCode + ##ERROR
IF #ErrorCode <> 0
BEGIN
SELECT '<FaultClass>1</FaultClass><FaultCode>1</FaultCode>'
+ '<FaultDesc>Internal Database Error.</FaultDesc>'
+ '<FaultDebugInfo>(spGetAutoCompleteList): There was an error while trying to SELECT from tbAutoComplete.</FaultDebugInfo>'
ROLLBACK TRAN
RETURN
END
COMMIT TRAN
#NameString needs to be outside of the quotes. To get #NameString% enclosed in quotes, you use two single quotes to escape the quote character as a literal.
SET #Where = #Where + '
AND ac.AutoCompleteName like ''' + #NameSubstring + '%'''
To avoid SQL injection, do not use concatenation when adding the parameter to your SQL statement. I strongly recommend that you use this format:
IF #NameSubstring IS NOT NULL BEGIN
SET #Where += 'AND ac.AutoCompleteName LIKE #NameSubstring + char(37)'
END
By using char(37) instead of '%' you avoid having to escape the apostrophes around the string literal
If you wanted to put a wildcard at either side, then you would use
IF #NameSubstring IS NOT NULL BEGIN
SET #Where += 'AND ac.AutoCompleteName LIKE char(37) + #NameSubstring + char(37)'
END
-----------------------------------------------------------------------------
In case someone believes I am wrong, here's proof that concatenation is a risk.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestInjection]') AND type in (N'U')) BEGIN
create table TestInjection(ID int, Value nvarchar(10))
insert into TestInjection (ID,Value)
Values
(1,'Tom'),
(2,'Fred'),
(3,'Betty'),
(4,'Betty2'),
(5,'Betty3'),
(6,'George')
END
declare #NameSubstring nvarchar(1000) = 'Bet'
--declare #NameSubstring nvarchar(1000) = 'Bet%'';delete from TestInjection;select * from TestInjection where value = ''x'
declare #ID int = 2
Declare #sql nvarchar(1000) = 'select * from TestInjection where ID > #ID '
SET #sql +=' AND [Value] like ''' + #NameSubstring + '%'''
Declare #params nvarchar(100) = '#ID int'
exec sp_executesql #sql, #params, #ID
select * from TestInjection
Run it the first time and you will get a resultset with 3 records, and another with all 6 records.
Now swap the declaration of #NameSubstring to the alternative, and re-run. All data in the table has been deleted.
If on the other hand you write your code like:
declare #NameSubstring nvarchar(1000) = 'Bet'
--declare #NameSubstring nvarchar(1000) = 'Bet%'';delete from TestInjection;select * from TestInjection where value = ''x'
declare #ID int = 2
Declare #sql nvarchar(1000) = 'select * from TestInjection where ID > #ID '
SET #sql +=' AND [Value] LIKE #NameSubstring + char(37)'
Declare #params nvarchar(100) = '#ID int, #NameSubstring nvarchar(1000)'
exec sp_executesql #sql, #params, #ID, #NameSubstring
select * from TestInjection
Then you still get the 3 records returned the first time, but you don't lose your data when you change the declaration.
SET #Where = #Where + 'AND ac.AutoCompleteName like ''%' + #NameSubstring + '%'''
So, you are asking how to specify parameters when you use dynamic queries and sp_executesql ?
It can be done, like this:
DECLARE /* ... */
SET #SQLString = N'SELECT #LastlnameOUT = max(lname) FROM pubs.dbo.employee WHERE job_lvl = #level'
SET #ParmDefinition = N'#level tinyint, #LastlnameOUT varchar(30) OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql #SQLString, #ParmDefinition, #level = #IntVariable, #LastlnameOUT=#Lastlname OUTPUT
You can read more about it here: http://support.microsoft.com/kb/262499
Perhaps this wouldn't be an issue if you weren't using dynamic SQL. It looks to me like a vanilla query would work just as well and be much more straightforward to read and debug. Consider the following:
SELECT ac.AutoCompleteID,
ac.AutoCompleteName,
ac.CompanyID,
ac.StatusFlag,
ac.OwnerOperID,
ac.CreateDT,
ac.CreateOperID,
ac.UpdateDT,
ac.UpdateOperID,
ac.SubmitOperID,
ac.SubmitDT,
ac.ReviewComments
FROM tbAutoComplete ac
WHERE ((ac.AutoCompleteID = CAST(#AutoCompleteID AS nvarchar) OR (#AutoCompleteID IS NULL))
AND ((ac.StatusFlag = CAST(#StatusFlag AS nvarchar)) OR (#StatusFlag IS NULL))
AND ((ac.AutoCompleteName like #NameSubstring + '%') OR (#NameSubstring IS NULL))
AND ((ac.CompanyID = CAST(#CompanyID AS nvarchar)) OR (#CompanyID IS NULL))
This is much simpler, clearer etc. Good luck!

Resources