Conversion of PL/SQL script to TSQL - sql-server

I am having issues converting a portion of my script that works in Oracle over for use in SQL Server. The portion of my script uses arrays and I have not been able to find the equivalent in TSQL (I dont believe one exists?). My main issues occur when trying to match values v_measure_map(i).v_upload_code = b.UPLOAD_CODE. Any tips or suggestions?
SELECT SCOPE_KEY,
ENRICHED_DATE,
ENRICHED_TIME,
BENCHMARK_DATE,
PROSDEALID
INTO v_xs_scope
FROM xaction_scope
WHERE TXN_ID = V_TXN_ID ;
<<loop2>> FOR i IN 1..v_measure_map.count --count returns the number of rows in the table (sy_enrich_measure_map)
LOOP
<<loop3>> FOR j IN 1..v_xs_scope.count
LOOP
SELECT COUNT(*)
INTO #v_sql_count
FROM xaction_Level_info b
WHERE v_measure_map(i).v_upload_code = b.UPLOAD_CODE
AND v_xs_scope(j).v_scope_key = b.SCOPE
AND

Use table variables, for example:
DECLARE
#v_xs_scope TABLE (
RowNum INT IDENTITY,
SCOPE_KEY INT,
ENRICHED_DATE DATE,
ENRICHED_TIME TIME,
BENCHMARK_DATE DATE,
PROSDEALID INT
)
DECLARE
#v_measure_map TABLE (
RowNum INT IDENTITY,
v_upload_code INT
)
INSERT INTO #v_xs_scope (
SCOPE_KEY, ENRICHED_DATE, ENRICHED_TIME, BENCHMARK_DATE, PROSDEALID
)
SELECT
SCOPE_KEY, ENRICHED_DATE, ENRICHED_TIME, BENCHMARK_DATE, PROSDEALID
FROM xaction_scope
WHERE
TXN_ID = V_TXN_ID;
INSERT INTO #v_measure_map(v_upload_code)
SELECT v_upload_code
FROM v_measure_map
DECLARE
#l1_CurrentRowNum INT,
#l1_MaxRowNum INT,
#l2_CurrentRowNum INT,
#l2_MaxRowNum INT,
#v_upload_code INT,
#v_scope_key INT
SELECT
#l1_CurrentRowNum = 1,
#l1_MaxRowNum = MAX(RowNum)
FROM #v_measure_map
WHILE #l1_CurrentRowNum <= #l1_MaxRowNum
BEGIN
SELECT #v_upload_code = v_upload_code
FROM #v_measure_map
WHERE
RowNum = #l1_CurrentRowNum
SELECT
#l2_CurrentRowNum = 1,
#l2_MaxRowNum = MAX(RowNum)
FROM #v_xs_scope
WHILE #l2_CurrentRowNum <= #l2_MaxRowNum
BEGIN
SELECT #v_scope_key = scope_key
FROM #v_xs_scope
WHERE
RowNum = #l2_CurrentRowNum
SELECT #v_sql_count = COUNT(*)
FROM xaction_Level_info b
WHERE
b.UPLOAD_CODE = #v_upload_code
AND b.SCOPE = #v_scope_key
SET #l2_CurrentRowNum = #l2_CurrentRowNum + 1
END
SET #l1_CurrentRowNum = #l1_CurrentRowNum + 1
END
or cursors, for example:
DECLARE
#v_upload_code INT,
#v_scope_key INT
DECLARE curs_1 CURSOR FOR
SELECT
v_upload_code
FROM v_measure_map
OPEN curs_1;
FETCH curs_1 INTO #v_upload_code;
WHILE (##FETCH_STATUS<>-1)
BEGIN
DECLARE curs_2 CURSOR FOR
SELECT
SCOPE_KEY
FROM xaction_scope
WHERE
TXN_ID = V_TXN_ID
OPEN curs_2;
FETCH curs_2 INTO #v_scope_key;
WHILE (##FETCH_STATUS<>-1)
BEGIN
SELECT #v_sql_count = COUNT(*)
FROM xaction_Level_info b
WHERE
b.UPLOAD_CODE = #v_upload_code
AND b.SCOPE = #v_scope_key
FETCH curs_2 INTO #v_scope_key;
END
CLOSE curs_2;
DEALLOCATE curs_2;
FETCH curs_1 INTO #v_upload_code;
END
CLOSE curs_1;
DEALLOCATE curs_1;

Related

Subquery returned more than 1 value. this is not permitted when the subquery follows = or when the subquery is used as an expression

I want to update on my 'MakinelerVeParcalar' table with this query ;
UPDATE MakinelerVeParcalar SET Durum = 'Montaj' WHERE ID = 161
And I got this error;
Msg 512, Level 16, State 1, Procedure trgSureUpdate, Line 31 [Batch Start Line 0]
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
My trgSureUpdate(Trigger)
ALTER TRIGGER [dbo].[trgSureUpdate]
ON [dbo].[MakinelerVeParcalar]
AFTER UPDATE
AS
BEGIN
DECLARE #count INT
DECLARE #idinserted INT
DECLARE #duruminserted VARCHAR(50)
DECLARE #max INT
DECLARE #iddeleted INT
DECLARE #durumdeleted VARCHAR(50)
DECLARE #datediff INT
DECLARE #durumbilgisi varchar(50)
DECLARE #sureinserted INT
DECLARE #suredeleted INT
DECLARE #diffdate INT
SELECT #idinserted = ID from inserted <-- Line 31
SELECT #duruminserted = Durum from inserted
SELECT #iddeleted = ID from deleted
SELECT #durumdeleted = Durum from deleted
SET #count = (SELECT count(*) FROM Sure WHERE ID = #idinserted and Asama = #duruminserted)
SET #max = (SELECT max(SiraNo) FROM Sure WHERE ID = #idinserted)
SET #durumbilgisi = (SELECT DurumBilgisi FROM DurumBilgisi WHERE ID = #idinserted)
SET #sureinserted = (SELECT Sure FROM Sure WHERE ID = #idinserted and Asama = #duruminserted)
SET #suredeleted = (SELECT Sure FROM Sure WHERE ID = #iddeleted and Asama = #durumdeleted)
IF #duruminserted != #durumdeleted
BEGIN
IF #durumbilgisi != 'Bitti'
BEGIN
UPDATE Sure Set Cikis = GETDATE() WHERE Asama = #durumdeleted and ID = #idinserted and SiraNo = #max
SET #diffdate = DATEDIFF (SECOND,(SELECT Giris FROM Sure WHERE ID = #idinserted and SiraNo = #max),(SELECT Cikis FROM Sure WHERE ID = #idinserted and SiraNo = #max))
UPDATE Sure SET Sure = (#diffdate) WHERE ID = #idinserted and SiraNo = #max
END
INSERT INTO Sure (ID,Asama,Giris,Cikis,Sure,SiraNo) VALUES(#idinserted,#duruminserted,GETDATE(),NULL,0,(#max+1))
UPDATE DurumBilgisi SET DurumBilgisi = 'Devam Ediyor' WHERE ID = #idinserted
END
END
I think you might be confused on the line numbers. You have this marked as line 31, but it will NOT throw your error even if there are multiple rows on INSERTED:
SELECT #idinserted = ID from inserted
However, this WILL throw your error if the SELECT returns more than 1 row:
SET #durumbilgisi = (SELECT DurumBilgisi FROM DurumBilgisi WHERE ID = #idinserted)
There are differences between the select #var = col from table syntax and the set #var = (select col from table) syntax. You need to examine the SET queries.

How to loop over select result?

I have this select's result
I'm trying to use this code, but I don't have an int id.
DECLARE #LoopCounter INT = 1, #MaxLAB INT ,
        lib NVARCHAR(100)
 set #MaxLAB=(select count(LAB_UM) from STARE_LAB_DEMANDE )
WHILE(#LoopCounter <= #MaxLAB)
BEGIN
   SELECT lib = LAB_UM
   FROM Mytable WHERE Id = #LoopCounter
 
   PRINT lib 
   SET #LoopCounter  = #LoopCounter  + 1       
END
I want to loop over this records. The id is lab_um.
You can loop through in LAB_UM order easily enough:
declare #lib varchar(50)
set #lib=''
while exists(select * from table where lab_um>#lib)
begin
set #lib=(select min(lab_um) from table where lab_um>#lib)
/*do stuff here */
end

Foreach update with parameter separated with a delimiter in SQL Server stored procedure

UPDATE SampleTable
SET Schemaname = #SchemaName,
SchemaCode = #SchemaCode,
ForeignKeyColumn = #ForeignKeyColumn,
IsChildSchema = #IsChildSchema,
ModifiedBy = #ModifiedBy,
ModifiedDate = #ModifiedDate
WHERE
DataSchemaID = #DataSchemaId
My #ForeignKeyColumn parameter is
2233^SITE_CLM_NUMBER,2236^SITE_ID_N,
Can anyone help me in updating ForeignKeyColumn='SITE_CLM_NUMBER' where DataSchemaID=2233 and ForeignKeyColumn='SITE_ID_N' where DataSchemaID=2236
It's easy to pass multiple parameter values to a query, using a Table Valued Parameter. These are available in all versions of SQL Server since 2008.
First, you need to create a Table type with the fields you want:
CREATE TYPE dbo.KeyValueType AS TABLE
( Key int, Value nvarchar(50) )
This allows you to specify a parameter of type KeyValueType with the Key/Value combinations you want, eg #updatedColumns.
You can join the target table with the TVP to update rows with matching DataSchemaID values:
Create Procedure UpdateSchemas(...., #updatedColumns dbo.KeyValueType)
UPDATE SampleTable
SET
Schemaname=#SchemaName
,SchemaCode=#SchemaCode
,ForeignKeyColumn=t.Value
,IsChildSchema=#IsChildSchema
,ModifiedBy=#ModifiedBy
,ModifiedDate=#ModifiedDate
FROM SampleTable
INNER JOIN #updatedColumns t
ON t.ID=DataSchemaID
You can add an SplitString function, like this one :
How to Split String by Character into Separate Columns in SQL Server
CREATE FUNCTION [dbo].[Split]
(
#String varchar(max)
,#Delimiter char
)
RETURNS #Results table
(
Ordinal int
,StringValue varchar(max)
)
as
begin
set #String = isnull(#String,'')
set #Delimiter = isnull(#Delimiter,'')
declare
#TempString varchar(max) = #String
,#Ordinal int = 0
,#CharIndex int = 0
set #CharIndex = charindex(#Delimiter, #TempString)
while #CharIndex != 0 begin
set #Ordinal += 1
insert #Results values
(
#Ordinal
,substring(#TempString, 0, #CharIndex)
)
set #TempString = substring(#TempString, #CharIndex + 1, len(#TempString) - #CharIndex)
set #CharIndex = charindex(#Delimiter, #TempString)
end
if #TempString != '' begin
set #Ordinal += 1
insert #Results values
(
#Ordinal
,#TempString
)
end
return
end
Now you can easily extract each part of your input parameter.
declare #I int;
declare #TMP nvarchar(255);
set #I = 1;
set #TMP = null;
set #TMP = (select StringValue from Split(#ForeignKeyCoumn, ',') where Ordinal = 1);
while #TMP <> null
begin
set #ForeignKeyColumn = (select StringValue from Split(#TMP, '^') where Ordinal = 1);
set #DataSchemaID = (select StringValue from Split(#TMP, '^') where Ordinal = 2);
-- Update here your table with #ForeignKeyColumn and #DataSchemaID values
set #I = #I + 1;
set #TMP = null;
set #TMP = (select StringValue from Split(#ForeignKeyCoumn, ',') where Ordinal = #I);
end
PS: If your are using SQL Server 2016 it already includes an SplitString function, so you won't need to add your own. https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Struggling to convert my sql query to be set based instead of using while loops

My code is:
Declare #Users table(Names nvarchar(50) not null, Flag int);
Declare #ValidUsers table(Names nvarchar(50) not null);
Declare #Office int;
Declare #NumberOfRecords int;
Declare #Count int;
Declare #IntCount int;
Declare #Binary AS nvarchar(16);
Declare #bit as nvarchar(1);
Declare #PermissionSub as nvarchar(1);
Declare #Permission as nvarchar(16);
Declare #ShouldContinue as bit;
set #ShouldContinue = 1;
set #Permission = '0001111111111111'; /* going to pass this value */
set #Count = '1';
set #IntCount = '1';
set #Office = '3'; /* going to pass this value */
Insert into #Users
Select
dbUser.usrFullName, udFeeEarnerLicence.purchaseFlag
From
[OMSBB].[dbo].[udFeeEarnerLicence]
Inner Join
[OMSBB].[dbo].[dbUser] ON udFeeEarnerLicence.feeUsrId = dbUser.usrID
Where
dbUser.brId = #Office;
select #NumberOfRecords = COUNT(Flag) from #Users;
DECLARE #Flag AS int;
select #Flag = Flag from #Users;
while(#Count <= #NumberOfRecords)
begin
WITH CTE AS
(
SELECT
Flag, ROW_NUMBER() OVER (ORDER BY Flag) AS RwNr
FROM
#Users
)
SELECT TOP(1) #Flag = Flag -- this TOP(1) is just a fail-safe
FROM CTE
WHERE RwNr = #Count;
WITH A AS
(
SELECT 0 AS ORD, #Flag AS NUMBER, CAST('' AS VARCHAR(20)) AS BITS
UNION ALL
SELECT ORD+1, NUMBER/2, CAST(BITS+CAST(NUMBER%2 AS VARCHAR(20)) AS VARCHAR(20))
FROM A
WHERE NUMBER > 0
)
SELECT #Binary = RIGHT('000000000000000'+ CASE WHEN BITS='' THEN '0' ELSE REVERSE(BITS) END,16)
FROM A
WHERE NUMBER = 0;
WHILE (#IntCount <= 16)
BEGIN
select #bit = SUBSTRING(#Binary, #IntCount, #IntCount + 1);
select #PermissionSub = SUBSTRING(#Permission, #IntCount, #IntCount + 1);
if(#PermissionSub = '1' and #bit != '1') /* if Permission selection is required and user does not have permission*/
begin
SET #ShouldContinue = 0;
break;
end
end
Set #IntCount = 0;
if(#ShouldContinue = 0)
begin
continue;
end
; WITH CTE AS
(
SELECT Names, ROW_NUMBER() OVER (ORDER BY Flag) AS RwNr
FROM #Users
)
INSERT INTO #ValidUsers
SELECT Names
FROM CTE
WHERE RwNr = #Count;
end
select * from #ValidUsers
I will be adapting this code to use it inside of an SSRS report so that's why there are comments on some parameters saying that I will be passing the parameters. This code at its basics finds all users who are from a specified office and have the specified permissions. The permission a user has are set in 5 flags in this example I'm using the purchaseFlag. This value is an int and it calculated by creating an order of permissions and set their bit values to create a string of 0's and 1's and then converting that binary number into a decimal for example '8191' which the binary value of would be '0001111111111111'. I use two while loops in this one to go through the users and the other to go through each of the 16 characters in the permissions. My issue is that this I'm almost certain that this query works but it takes so long to run that I haven't seen the result of it yet and people have recommended that I use sets instead.

Why a loop inside a tsql function executes only once?

Given a CustomerInvoiceId, this function should returns a string of concatenated project-numbers. But, I don't understand why it returning only a comma, I guess maybe because it's executing only once.
ALTER FUNCTION [dbo].[ConcatProductNumbers]
(
#CustomerInvoiceId INT = 0
)
RETURNS VARCHAR
AS
BEGIN
-- Declare the return variable here
DECLARE #count INT = 0,
#i INT = 1,
#strProjectNumber VARCHAR(MAX) = '';
DECLARE #Ci_ProjectNumber TABLE
(
Id INT NOT NULL IDENTITY(1, 1),
ProjectNumber VARCHAR(50)
)
-- Add the T-SQL statements to compute the return value here
INSERT INTO #Ci_ProjectNumber (ProjectNumber)
SELECT pa.ProjectNumber
FROM ProjectsActive pa
JOIN ProjectsActiveInvoicing pai
ON pa.OrderID = pai.OrderID
WHERE pai.CustomerInvoiceId = #CustomerInvoiceId;
SELECT #count = COUNT(*) FROM #Ci_ProjectNumber
WHILE #count > #i
BEGIN
SELECT #strProjectNumber = (#strProjectNumber + ', ' + ci.ProjectNumber)
FROM #Ci_ProjectNumber ci
WHERE ci.Id = #i;
SET #i = #i + 1;
END
-- Return the result of the function
RETURN #strProjectNumber
END
However, when I remove the defintion of the function keeping only the statements inside and replacing RETURN #strProjectNumber with PRINT #strProjectNumber, it works. Any reason why?
Thanks for helping
If the goal is to get a comma separated list of Project Numbers you could consider using COALESCE instead of a loop.
SELECT #strProjectNumber = COALESCE(#strProjectNumber+',' ,'') + ProjectNumber
FROM ProjectsActive pa
JOIN ProjectsActiveInvoicing pai
ON pa.OrderID = pai.OrderID
WHERE pai.CustomerInvoiceId = #CustomerInvoiceId;
RETURN #strProjectNumber

Resources