Equivalent of LINQ let clause in SQL Server - sql-server

I have the following code in linq:
(from emp in db.EMPLOYEES
from tab in db.CATEGORY.Where(o => o.Id == 19).DefaultIfEmpty()
on emp.Id = tab.EMP_ID
let url = (!tab.PAGE.StartsWith("/") && !tab.PAGE.StartsWith("#"))
? tab.PAGE + "(" + emp.EMP_VALUE + ")"
: tab.PAGE.Contains("?")
? tab.PAGE + "&Id=" + emp.EMP_VALUE + "&fromSSR=" + BooleanStr.True
: tab.PAGE + "?Id=" + emp.EMP_VALUE + "&fromSSR=" + BooleanStr.True
select new EmployyeModel
{
Id = emp.Id,
RedirectURL = tab.NOT_CH_APPROVAL == BooleanStr.True ? url + "&userCanApprove=1" : url,
}
I need to write the above query in sql server and need help concerning the "let url" part.
How do I translate let in sql server and use it in the select?
Thanks for any help.

Create a user-defined function:
CREATE FUNCTION fnBuildUrl (
#page varchar(100),
#emp_value varchar(100)
)
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #url varchar(1000)
SELECT #url = CASE WHEN LEFT(#page, 1) = '/' THEN
#page + '(' + #emp_value + ')'
ELSE
CASE WHEN CHARINDEX('?', #page) > 0 THEN
#page + '&Id=' + #emp_value + '&fromSSR=T'
ELSE #page + '?Id=' + #emp_value + '&fromSSR=T' END
END
RETURN #url
END
GO
Then call it in your SELECT:
SELECT emp.Id,
CASE WHEN tab.NOT_CH_APPROVAL = 'T' THEN dbo.fnBuildUrl(tab.PAGE, emp.EMP_VALUE) + '&userCanApprove=1'
ELSE dbo.fnBuildUrl(tab.PAGE, emp.EMP_VALUE) END AS RedirectURL
FROM EMPLOYEES emp
LEFT OUTER JOIN CATEGORY tab
ON tab.EMP_ID = emp.Id
AND tab.Id = 19

Related

How to escape comma character in a javascript procedure in wherescape, snowflake?

I have a table with 2 columns:
Tablename
Fieldname
tbl
name
tbl
LastName
I need to display one row, having Fieldname values concatenated:
tbl --> name, lastName
So, I am using Listagg fct to concatenate the values :
listagg (fieldname,',') as newCol.
Running this command in Snowflake, works fine and displays want i need.
But, because we are using Wherescape to create procedures, adding this function in a javascript procedure, fails, because of COMMA separator used in listagg fct.
I tried with different escape characters, but nothing worked. (/,,/[])..
If I change the character in &,* it works, but I have to use comma.
Creating the newCol with listagg with & instead of ",", works and if I use a replace fct to change it to ",", it fails again :(
What should I use to have COMMA between the values for the newCol.
It is mandatory to have COMMA, because the column will be used in an update statement.
EDIT:
CREATE OR REPLACE PROCEDURE sp_myprocedure_test()
RETURNS VARCHAR(16777216)
language javascript
execute as CALLER
as
$$
const generateUpdStmt = ` SELECT 'UPDATE tbl SET ' || c.col_update AS STMT_SQL
,c.col_update
FROM (
select TABLE_NAME
, listagg (concat(field_name, ' = ''#tst''' ), ' , ') within group (order by TABLE_NAME, field_name asc) as col_update
from tbl
group by TABLE_NAME
) c
;`;
try {
var return_value = "Init: ";
var stmt_update = snowflake.createStatement({sqlText: generateUpdStmt});
var result_stmt = stmt_update.execute();
while (result_stmt.next())
{
var result_stmt_value_2 = result_stmt.getColumnValue(2);
return_value += result_stmt_value_2 + "\n";
}
result = " Status: Succeeded. \n";
}
catch (err) {
result = "Failed: Code:" + err.code + "\n State: " + err.state;
result += "\n Message: " + err.message;
result += "\nStack Trace:\n" + err.stackTraceTxt;
return result;
}
return return_value;
$$
EDIT 2:
Returning result in wherescape with "&" instead of "," delimiter:
enter image description here
Returning result in wherescape with "," delimiter:
enter image description here
I created a minimal tbl table and when I run this:
SELECT 'UPDATE tbl SET ' || c.col_update AS STMT_SQL
,c.col_update
FROM (
select TABLE_NAME
, listagg (concat(field_name, ' = ''#tst''' ), ' , ') within group (order by TABLE_NAME, field_name asc) as col_update
from tbl
group by TABLE_NAME
) c ;
I get the following result:
+--------------------------------------------------+-----------------------------------+
| STMT_SQL | COL_UPDATE |
+--------------------------------------------------+-----------------------------------+
| UPDATE tbl SET LastName = '#tst' , name = '#tst' | LastName = '#tst' , name = '#tst' |
+--------------------------------------------------+-----------------------------------+
I see your JS returns the same values. I edited it to return both columns as one string:
CREATE OR REPLACE PROCEDURE sp_myprocedure_test()
RETURNS VARCHAR(16777216)
language javascript
execute as CALLER
as
$$
const generateUpdStmt = ` SELECT 'UPDATE tbl SET ' || c.col_update AS STMT_SQL
,c.col_update
FROM (
select TABLE_NAME
, listagg (concat(field_name, ' = ''#tst''' ), ' , ') within group (order by TABLE_NAME, field_name asc) as col_update
from tbl
group by TABLE_NAME
) c
;`;
try {
var return_value = "";
var stmt_update = snowflake.createStatement({sqlText: generateUpdStmt});
var result_stmt = stmt_update.execute();
while (result_stmt.next())
{
return_value += result_stmt.getColumnValue(1) + " | " + result_stmt.getColumnValue(2);
}
}
catch (err) {
result = "Failed: Code:" + err.code + "\n State: " + err.state;
result += "\n Message: " + err.message;
result += "\nStack Trace:\n" + err.stackTraceTxt;
return result;
}
return return_value;
$$
call sp_myprocedure_test();
+--------------------------------------------------------------------------------------+
| SP_MYPROCEDURE_TEST |
+--------------------------------------------------------------------------------------+
| UPDATE tbl SET LastName = '#tst' , name = '#tst' | LastName = '#tst' , name = '#tst' |
+--------------------------------------------------------------------------------------+
Can you tell me what is expected?

Use variable in WHERE clause

My SQL statement in SQL Server looks like this:
DECLARE #forecastYear AS varchar(5)
SET #forecastYear = '2020'
DECLARE #versionName AS varchar(25)
SET #versionName = '20201113_wk'
DECLARE #currentMonth AS varchar(2)
SET #currentMonth = (SELECT current_fc_month FROM tbl_current_month)
SELECT f.record_id
, u.it_ops_j_level_abbr_nm
, f.owner_nm
, f.unit_cd
, f.tbm_cd
, f.tower_nm
, f.description_txt
, f.comment_txt
, f.cost_pool_nm
, f.glac_nr
, f.glac_nm
, f.initiative_nm
, f.priority_nm
, f.growth_nm
, f.it_vendor_nm
, f.jan_amt
, f.feb_amt
, f.mar_amt
, f.apr_amt
, f.may_amt
, f.jun_amt
, f.jul_amt
, f.aug_amt
, f.sep_amt
, f.oct_amt
, f.nov_amt
, f.dec_amt
FROM tbl_new_forecast f
INNER JOIN tbl_unit_tree u
ON f.unit_cd = u.dept_id
WHERE f.version_nm = #versionName
AND f.status_cd = 'Approved'
AND f.entry_type = 'Forecast'
AND f.forecast_yr_id = #forecastYear
AND ABS(f.nov_amt)+ABS(f.dec_amt) <> 0
What I want to do is change the last statement in the WHERE clause based on the value in #currentMonth.
Therefore, if #currentMonth = '3' then the last statement would read
AND ABS(f.mar_amt)+ABS(f.apr_amt)+ABS(f.may_amt) <> 0
If #currentMonth = '7' then it would read
AND ABS(f.jul_amt)+ABS(f.aug_amt)+ABS(f.sep_amt) <> 0
I'm having a hard time figuring out how to accomplish this, because I get a SQL error with this syntax:
AND CASE
WHEN #currentMonth = '10' THEN ABS(f.oct_amt)+ABS(f.nov_amt)+ABS(f.dec_amt) <> 0
END
Any help is appreciated!
If you need a solution, a complex WHERE clause is an option. Note, that in T-SQL CASE is an expression, not a statement:
AND (
((#currentMonth = 1) AND (ABS(f.jan_amt) + ABS(f.feb_amt) + ABS(f.mar_amt) <> 0)) OR
((#currentMonth = 2) AND (ABS(f.feb_amt) + ABS(f.mar_amt) + ABS(f.apr_amt) <> 0)) OR
...
((#currentMonth = 10) AND (ABS(f.oct_amt) + ABS(f.nov_amt) + ABS(f.dec_amt) <> 0)) OR
((#currentMonth = 11) AND (ABS(f.nov_amt) + ABS(f.dec_amt) <> 0)) OR
((#currentMonth = 12) AND (ABS(f.dec_amt) <> 0))
)
Use dynamic SQL
DECLARE #month_dependent varchar (500)=' ';
DECLARE #main_query varchar(1000)=' ';
DECLARE #forecastYear AS varchar(5)
SET #forecastYear = '2020'
DECLARE #versionName AS varchar(25)
SET #versionName = '20201113_wk'
DECLARE #currentMonth char(2)
SET #currentMonth = (SELECT current_fc_month FROM tbl_current_month)
If #currentMonth = '3'
BEGIN set #month_dependent=' AND ABS(f.mar_amt)+ABS(f.apr_amt)+ABS(f.may_amt) <> 0 '; END
If #currentMonth = '7'
BEGIN set #month_dependent=' AND ABS(f.jul_amt)+ABS(f.aug_amt)+ABS(f.sep_amt) <> 0 '; END
set #main_query varchar(1000)=' SELECT f.record_id' +
' , u.it_ops_j_level_abbr_nm ' +
-- ' and all the rest of it! ' +
' FROM tbl_new_forecast f '+
' INNER JOIN tbl_unit_tree u '+
' ON f.unit_cd = u.dept_id ' +
' WHERE f.version_nm = '''+ #versionName + ''' '+
' AND f.status_cd = ''Approved'' '+
' AND f.entry_type = ''Forecast'' '+
' AND f.forecast_yr_id = ''' + #forecastYear + ''' '+
#month_dependent
EXECUTE sp_executesql #main_query ;
You can make this really complex by using a WHERE clause with a lot of ors in it:
AND ({first month condition} OR {Second month condition} OR {third month condition})
Etc. Another option is to place this into a stored procedure and use the month as the trigger to determine which statement to run. Depending on how you are running this, it might be a preferred method, as it can abstract out the details from the application (something you will want if you ever decide to normalize this data).
As for trying to use CASE in a WHERE clause, you have it wrong. The CASE WHEN has to equal something. The correct syntax is like:
AND SomeValue = CASE WHEN ...
You cannot simply use case, as a where is looking for equality (=), inequality (<>), and fuzzy values (LIKE). Thus, this does not work.
AND CASE WHEN ...
As an example, this shows something that fires back 1 to get equivalent rows. But you would need all of your conditions in here, which means the WHEN on month and the ABS() would be the entire condition. You then return 1 to indicate "found it". But you are running this as a monthly query, so filtering by the month and then determining the CASE ... WHEN is where you go.

Get name from variable using index in T-SQL

Using the following two queries
Query 1:
DECLARE #ContentColumnNamesSRC NVARCHAR(4000) = NULL,
SELECT
#ContentColumnNamesSRC = COALESCE(#ContentColumnNamesSRC + ', ', '') + '[' + name + ']'
FROM
tempdb.sys.columns
WHERE
1 = 1
AND object_id = OBJECT_ID('tempdb..#tempTable')
AND column_id < 9 -- First 8 columns are ID data, which is what I am after
Query 2:
DECLARE #ContentColumnNamesDST NVARCHAR(4000) = NULL,
SELECT
#ContentColumnNamesDST = COALESCE(#ContentColumnNamesDST + ', ', '') + '[' + name + ']'
FROM
tempdb.sys.columns
WHERE
1 = 1
AND object_id = OBJECT_ID('Import.dbo.ContentTable')
AND column_id < 9 -- First 8 columns are ID data, which is what I am after
I can get the first 8 columns from each table into a variable.
What I would like to do is find a way to get the values out of the variable, such that I can match the column names.
They should be identical in each table, and I need it to be able to create a dynamic merge statement, such that the columnsnames from each variable
#ContentColumnNamesSRC
and
#ContentColumnNamesDST
line up, so I can use it in a merge statement.
The point of this is to be able to use it in a loop, and all i would have to do is change which tables it looks at and the merge statements would still work.
Ideally, id like to end up with something like the following:
SELECT #StageSQLCore = N'USE Staging;
BEGIN TRANSACTION
MERGE '+#StageTableCore+' AS DST
USING '+#ImportTableCore+' AS SRC
ON (SRC.[Key] = DST.[Key])
WHEN NOT MATCHED THEN
INSERT ('+#StageTableCoreColumns+')
VALUES (
'+#ImportTableCoreColumns+',GETDATE())
WHEN MATCHED
THEN UPDATE
SET
DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.[ETLDate] = GETDATE()
;
COMMIT'
EXEC (#StageSQLCore)
You can generate Merge SQL like this if both the ordinal are matching
DECLARE #MergeSQL NVARCHAR(4000) = NULL
SELECT --*--,
#MergeSQL = COALESCE(#MergeSQL + ', DST.=', '') + QUOTENAME(bc.column_name) + ' = SRC.' + QUOTENAME(bc.COLUMN_NAME) + char(13)
FROM
test.INFORMATION_SCHEMA.COLUMNS tc
inner join testb.INFORMATION_SCHEMA.COLUMNS bc
on tc.TABLE_NAME = bc.TABLE_NAME
and tc.ORDINAL_POSITION = bc.ORDINAL_POSITION
and tc.TABLE_NAME = 'History'
WHERE
tc.ORDINAL_POSITION < 5 -- First 8 columns are ID data, which is what I am after
and bc.ORDINAL_POSITION < 5
select #MergeSQL

Maximum 2 semi join sub-selects are allowed (Salesforce)

Fairly new to APEX (Salesforce development) and i found an issue with one of SOQL statements...
This is the code that is creating the problem.. i did some research and i found that i need to create a set of id's instead of using another select statement for OpportunityId and then just reference it on the query...
String sumAvgQuery = 'SELECT StageName, COUNT(ID) opps, SUM(Amount) total, AVG(SVC_Svc0StageAge__c) Svc0, AVG(SVC_Svc1StageAge__c) Svc1, ' +
'AVG(SVC_Svc2StageAge__c) Svc2, AVG(SVC_Svc3StageAge__c) Svc3, AVG(SVC_Svc4StageAge__c) Svc4, ' +
'AVG(SVC_Svc5StageAge__c) Svc5, AVG(SVC_Svc6StageAge__c) Svc6, AVG(SVC_Svc7StageAge__c) Svc7, ' +
'AVG(SVC_Svc8StageAge__c) Svc8, AVG(SVC_Svc9StageAge__c) Svc9 ' +
'FROM Opportunity ' +
'WHERE StageName in (' + BTG_Utility.OPPORTUNITY_STAGES + ') ' +
'AND ID in (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = \'' + String.escapeSingleQuotes(userId) + '\') ' +
((cluster != null && cluster != '') ? 'AND SVC_AccountCluster__c = \'' + String.escapeSingleQuotes(cluster) + '\' ' : '') +
((region != null && region != '') ? 'AND SVC_AccountRegion__c = \'' + String.escapeSingleQuotes(region) + '\' ' : '') +
((country != null && country != '') ? 'AND CARE_AccountCountry__c = \'' + String.escapeSingleQuotes(country) + '\' ' : '') +
((product != null && product != '') ? ' AND Id in (SELECT OpportunityId FROM OpportunityLineItem Where Product2.Name = \'' + String.escapeSingleQuotes(product) + '\' and Opportunity.IsClosed = FALSE) ' : '') +
'GROUP BY StageName)';
Can you please help me on how to do this ? Really appreciate the help !
If you want to use a collection like a Set in Apex you need to populate the set first, then you can use the variable name in your SOQL query with a : in front of it.
So for example:
// First populate the Set with ID's using SOQL
set<Account> inputSet = new set<Account>([SELECT Id FROM Account LIMIT 5]);
//Alternatively manually populate the set
// set<String> inputSet = new set<String>();
// List<Account> accounts = new List<Account>([SELECT custom_id__c FROM Account LIMIT 5]);
// for (Account acc : accounts) {
// inputSet.add(acc.custom_id__c);
// }
System.debug('inputSet: ' + inputSet);
// Use the set with the data
List<Contact> contacts = [SELECT id FROM Contact where custom_id__c in :inputSet];
System.debug('contacts: ' + contacts);
Hope that helps.
Mohamed Imran

SQLCLR running too slow

I have 1.6 million rows in a table. there are 100,000 rows which has some information missing. To locate that information without duplicate references. I have written a SQL CLR procedure.
It is executing at very slower speed. Only 5000 Rows are processed out on 100,000 in 30 minutes time.
Can below code can be replaced with Inline SQL.
var paymentSql =String.Format("select PaymodeId,StdLedgerId,BaseAmount,RegNo/*,REfInstno,RefStdLedgerId,RefPaymodeId*/ from vw_Payment_Ledger_Matching_Other {0} {1}" ,(condition.Equals("") ? "" : " where " + condition) ," order by CenterId,Ledgerdate,RecptKey ");
var payment = new SqlCommand(paymentSql, conn1) { CommandTimeout = 600 };
using (SqlDataReader payments = payment.ExecuteReader())
{
while (payments.Read())
{
var paymentPaymodeId = payments["PaymodeId"];
var paymentStdLedgerId = payments["StdLedgerId"];
var paymentAmount = payments["BaseAmount"];
var paymentRegNo = payments["RegNo"];
//var paymentRefInstNo = payments["RefInstNo"];
//var paymentRefStdLedgerId = payments["RefStdLedgerId"];
//var paymentRefPayModeId = payments["RefPayModeId"];
//if (Convert.ToInt32(paymentRefInstNo) == 0 && Convert.ToInt32(paymentRefStdLedgerId) == 0 && paymentRefPayModeId.Equals("0"))
{
var ledgerSql = String.Format("select paymodeId,StdLedgerId,Instno,Concession,LumpSump,ConcessionDtl,LumpSumpDtl from vw_Payment_Ledger_Matching_inst a where a.regno='{0}' and abs(a.BaseAmount) between abs({1})-5 and abs({1})+5 and Isnull(a.refInstno,0)=0 and a.insttype<>'O'" +
"and (cast(a.StdLedgerID as varchar(10))+cast(InstNo as varchar(1))) not in ( select cast(b.refStdLedgerID as varchar(10))+cast(b.refInstNo as varchar(1)) from vw_Payment_Ledger_Matching_inst b"
+" where b.regno='{0}' and (b.BaseAmount) between ({1})-5 and ({1})+5 and b.Insttype='O' )"
+" order by a.CenterId,a.RecptKey,a.LedgerDate ",paymentRegNo,paymentAmount );
var Ledger = new SqlCommand(ledgerSql, conn2) { CommandTimeout = 600 };
SqlDataReader ledger = Ledger.ExecuteReader();
if (ledger.Read())
{
var ledgerPayModeId = ledger["PayModeID"];
var ledgerStdLedgerId = ledger["StdLedgerId"];
var ledgerInstNo = ledger["InstNo"];
var ledgerConcession = ledger["Concession"];
var ledgerLumpsump = ledger["Lumpsump"];
var ledgerConcessionDtl = ledger["ConcessionDtl"];
var ledgerLumpsumpDtl = ledger["LumpsumpDtl"];
var updatesql = "update " + updateTable + " set RefInstno=" + ledgerInstNo
+ ", RefStdLedgerId=" + ledgerStdLedgerId + ""
+ ", RefPayModeId='" + ledgerPayModeId + "'"
+ ", RefConcession=" + ledgerConcession
+ ", RefLumpsump=" + ledgerLumpsump
+ ", RefConcessionDtl=" + ledgerConcessionDtl
+ ", RefLumpsumpDtl=" + ledgerLumpsumpDtl
+ " where stdLedgerId=" + paymentStdLedgerId
+ " and PayModeId='" + paymentPaymodeId + "'";
var ledgerUpdate = new SqlCommand(updatesql, conn3);
ledgerUpdate.ExecuteNonQuery();
}
}
}
}
The problem here is not SQLCLR. The problem is that SQLCLR is being used when there is absolutely no reason for doing so. Unless I am missing something, this operation is just a simple cursor, doing the SELECT FROM vw_Payment_Ledger_Matching_inst and the UPDATE {updateTable} per every row returned from the SELECT FROM vw_Payment_Ledger_Matching_Other query. And even if the 3 SqlConnections are using Context Connect = true;, it is still executing a non-parameterized query (as pointed out by #usr in a comment on the Question) and creating a new SqlDataReader (i.e. ledger) that is not being closed for each of those 100,000 rows. But again, there is no reason to use SQLCLR here.
Let's look at what this operation is trying to do. It is saying:
For each record in Query A
{
Get a row from Query B
Update a table via Query C, using the row from Query B
}
What you have in C# is semantically / operationally equivalent to the following T-SQL:
CREATE PROCEDURE DoStuffBetter
(
#Condition NVARCHAR(500),
#UpdateTable NVARCHAR(500)
)
AS
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N'
DECLARE #PaymentPayModeId NVARCHAR(50),
#PaymentStdLedgerId INT,
#PaymentAmount MONEY,
#PaymentRegNo NVARCHAR(50);
DECLARE payment CURSOR FOR
SELECT PayModeId, StdLedgerId, BaseAmount, RegNo
/*, RefInstNo, RefStdLedgerId, RefPayModeId*/
FROM vw_Payment_Ledger_Matching_Other
' + CASE WHEN #Condition <> '' THEN N' WHERE ' + #Condition ELSE '' END + N'
ORDER BY CenterId, LedgerDate, RecptKey;
OPEN payment;
FETCH NEXT
FROM payment
INTO #PaymentPayModeId, #PaymentStdLedgerId, #PaymentAmount, #PaymentRegNo;
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #LedgerPayModeId NVARCHAR(50),
#LedgerStdLedgerId INT,
#LedgerInstNo INT,
#LedgerConcession MONEY,
#LedgerLumpSump MONEY,
#LedgerConcessionDtl MONEY,
#LedgerLumpsumpDtl MONEY;
SELECT TOP 1
#LedgerPayModeId = PayModeId,
#LedgerStdLedgerId = StdLedgerId,
#LedgerInstNo = InstNo,
#LedgerConcession = Concession,
#LedgerLumpSump = LumpSump,
#LedgerConcessionDtl = ConcessionDtl,
#LedgerLumpsumpDtl = LumpSumpDtl
FROM vw_Payment_Ledger_Matching_inst a
WHERE a.RegNo = #PaymentRegNo
AND ABS(a.BaseAmount) BETWEEN ABS(#PaymentAmount) - 5
AND ABS(#PaymentAmount) + 5
AND ISNULL(a.RefInstNo, 0) = 0
AND a.InstType <> ''O''
AND (CAST(a.StdLedgerID AS VARCHAR(10)) + CAST(a.InstNo AS VARCHAR(1)))
NOT IN (
SELECT CAST(b.RefStdLedgerID AS VARCHAR(10)) +
CAST(b.RefInstNo AS VARCHAR(1))
FROM vw_Payment_Ledger_Matching_inst b
WHERE b.RegNo = #PaymentRegNo
AND (b.BaseAmount) BETWEEN (#PaymentAmount) - 5
AND (#PaymentAmount) + 5
AND b.InstType = ''O''
)
ORDER BY a.CenterId, a.RecptKey, a.LedgerDate;
IF (##ROWCOUNT > 0)
BEGIN
UPDATE ' + #UpdateTable + N'
SET RefInstNo = #LedgerInstNo,
RefStdLedgerId = #LedgerStdLedgerId,
RefPayModeId = #LedgerPayModeId,
RefConcession = #LedgerConcession,
RefLumpsump = #LedgerLumpSump,
RefConcessionDtl = #LedgerConcessionDtl,
RefLumpsumpDtl = #LedgerLumpsumpDtl
WHERE StdLedgerId = #PaymentStdLedgerId
AND PayModeId = #PaymentPayModeId;
END;
FETCH NEXT
FROM payment
INTO #PaymentPayModeId, #PaymentStdLedgerId, #PaymentAmount, #PaymentRegNo;
END;
CLOSE payment;
DEALLOCATE payment;
';
EXEC (#SQL);
The above should be much more efficient than the C# version, but it can still be improved upon to remove the CURSOR. The following set-based approach should be logically equivalent, but all done in a single query:
CREATE PROCEDURE DoStuffBest
(
#Condition NVARCHAR(500),
#UpdateTable NVARCHAR(500)
)
AS
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N'
;WITH Payment AS
(
SELECT PayModeId, StdLedgerId, BaseAmount, RegNo
/*, RefInstNo, RefStdLedgerId, RefPayModeId*/
FROM vw_Payment_Ledger_Matching_Other
' + CASE WHEN #Condition <> '' THEN N' WHERE ' + #Condition ELSE '' END + N'
ORDER BY CenterId, LedgerDate, RecptKey
), Ledger AS
(
SELECT
a.PayModeId,
a.StdLedgerId,
a.InstNo,
a.Concession,
a.LumpSump,
a.ConcessionDtl,
a.LumpSumpDtl,
Payment.PayModeId AS [PaymentPayModeId], -- passthrough for UPDATE
Payment.StdLedgerId AS [PaymentStdLedgerId], -- passthrough for UPDATE
ROW_NUMBER() OVER (PARTITION BY a.RegNo
ORDER BY a.CenterId, a.RecptKey, a.LedgerDate) AS [RowNumInGroup]
FROM vw_Payment_Ledger_Matching_inst a
INNER JOIN Payment
ON Payment.RegNo = a.RegNo
WHERE ABS(a.BaseAmount) BETWEEN ABS(Payment.BaseAmount) - 5
AND ABS(Payment.BaseAmount) + 5
AND ISNULL(a.RefInstNo, 0) = 0
AND a.InstType <> ''O''
AND (CAST(a.StdLedgerID AS VARCHAR(10)) + CAST(a.InstNo AS VARCHAR(1)))
NOT IN (
SELECT CAST(b.RefStdLedgerID AS VARCHAR(10)) +
CAST(b.RefInstNo AS VARCHAR(1))
FROM vw_Payment_Ledger_Matching_inst b
WHERE b.RegNo = Payment.RegNo
AND (b.BaseAmount) BETWEEN (Payment.BaseAmount) - 5
AND (Payment.BaseAmount) + 5
AND b.InstType = ''O''
)
ORDER BY a.CenterId, a.RecptKey, a.LedgerDate
)
UPDATE upd
SET upd.RefInstNo = Ledger.InstNo,
upd.RefStdLedgerId = Ledger.StdLedgerId,
upd.RefPayModeId = Ledger.PayModeId,
upd.RefConcession = Ledger.Concession,
upd.RefLumpsump = Ledger.LumpSump,
upd.RefConcessionDtl = Ledger.ConcessionDtl,
upd.RefLumpsumpDtl = Ledger.LumpsumpDtl
FROM ' + #UpdateTable + N' upd
INNER JOIN Ledger
ON Ledger.PaymentStdLedgerId = upd.StdLedgerId
AND Ledger.PaymentPayModeId = upd.PayModeId
WHERE Ledger.[RowNumInGroup] = 1; --ensure same behavior as TOP 1 within the CURSOR
';
EXEC (#SQL);

Resources