Creating a decision table with accompanied procedure in SQL Server - sql

I'm trying to think of the best way to write a procedure to evaluate a decision table in SQL Server.
The way I've begun doing it (see VERY simplified example below) would work fine except for the rows where that particular parameter doesn't matter. So, any one claim could fit the scenario in Row 4, provided it's of the type medical.The query, having found a match doesn't even evaluate the other rows. Is there a better way to do this?
I was thinking I could rank the rows of the table from most complicated scenario to least and then select the top row that fits, and that'd probably work, but in future, many more rows will be added to the table and each one would require an analysis of where it would fall in the table. Some situations might even conflict.
I want the best match for the scenario.
I currently have something that does something like this:
create table TTable( RowID varchar(max)
, ClaimType varchar(max)
, Insurance varchar(max)
, Eligibilty varchar(max)
, Result varchar(max)
, Assignment varchar(max)
, Admission varchar(max))
Insert into TTable (RowID, ClaimType, Insurance, Eligibility, Result ,Assignment, Admission)
values ('1' , 'Medical' , 'Insured' , 'Eligible' , 'Approval', 'AssignNurse' , 'Admit')
,('2' , 'Medical' , 'Uninsured' , 'N/A' , 'Denial' , 'N/A' ,'N/A')
,('3' , 'Medical' , 'N/A' , 'IncomeEligible' , 'Pend' , 'AssignReviewer' , 'Pend')
,('4' ,' Medical' , 'N/A' , 'N/A' , 'Pend' , 'Pend' , 'Pend')
create procedure getdecision( #ClaimID uniqueidentifier)
declare #Type
declare #Insurance
declare #Eligibility
Select #Type = Type
From claims
where claimid = #ClaimID
select #Insurance = Insurance
#Eligibility = Eligibility
from MemberInsurance
where membemberid = (select memberid from claims where claimid = #claimid)
select RowID,
Result,
Assignment,
Admission
From ttable t
WHERE #Type = ClaimType
and Case when t.Insurance = 'N/A'
then #Type
Else t.ClaimType
End
and Case when t.Eligible = 'N/A'
then #Eligibility
else t.Eligibilty
end

Related

Pass multiple values as parameter into a stored procedure

I have the stored procedure to which I pass the parameters. These parameters are indicated by another tool. One of the parameter has a list of entities like C200, C010 etc.
But the requirement is that the person who will run the stored procedure from another tool (Fluence) should be able to call by each entity but also to retrieve the data related to all the entities.
I have the SQL code shown here, which perfectly works if you choose one entity at a time. In the Where clause, I filter it based on the #Entitygroup which is Declared. From another tool to fetch the all the Entity is passed at Total_group parameter name.
ALTER PROCEDURE [DW].[SP_Fetch_Data]
#par_FiscalCalendarYear varchar(10),
#par_Entity AS varchar (10)
AS
BEGIN
/*
BALANCE ACCOUNTS
*/
DECLARE #FiscalCalendarYear int = SUBSTRING(#par_FiscalCalendarYear,1,4) /* 2022 */
, #FiscalCalendarMonth int = SUBSTRING (#par_FiscalCalendarYear,7,10) /* 11 */;
DECLARE #FiscalCalendarPeriod int = #FiscalCalendarYear * 100 + #FiscalCalendarMonth
DECLARE #Entitygroup varchar = #par_Entity
SELECT UPPER([GeneralJournalEntry].SubledgerVoucherDataAreaId) as [Entity]
, CONCAT(#FiscalCalendarYear, ' P', #FiscalCalendarMonth) as [Date]
, ISNULL(ConsolidationMainAccount, '') as [Account]
, [GeneralJournalAccountEntry].TransactionCurrencyCode as [Currency]
, SUM([GeneralJournalAccountEntry].TransactionCurrencyAmount) as [Amount]
, 'Import' as [Audit]
, 'TCUR' as [DataView]
, ISNULL([CostCenter].[GroupDimension], 'No Costcenter') as [CostCenter]
, 'No Group' as [Group]
, ISNULL([Intercompany].[DisplayValue], 'No Intercompany') as [Intercompany]
, 'Closing' as [Movement]
, ISNULL([ProductCategory].[GroupDimension], 'No ProductCategory') as [ProductCategory]
, ISNULL([Region].[GroupDimension], 'No Region') as [Region]
, ISNULL([SalesChannel].[GroupDimension], 'No SalesChannel') as [SalesChannel]
, 'Actual' as [Scenario]
FROM [D365].[GeneralJournalAccountEntry]
LEFT JOIN [D365].[GeneralJournalEntry] ON [GeneralJournalAccountEntry].GENERALJOURNALENTRY = [GeneralJournalEntry].[RECID]
AND [GeneralJournalAccountEntry].[PARTITION] = [GeneralJournalEntry].[PARTITION]
LEFT JOIN [D365].[FiscalCalendarPeriod] ON [GeneralJournalEntry].FiscalCalendarPeriod = FiscalCalendarPeriod.FiscalCalendarPeriod
LEFT JOIN [DW].[MainAccounts] ON [GeneralJournalAccountEntry].MainAccount = [MainAccounts].[RECID]
LEFT JOIN [DW].[Intercompany] ON [GeneralJournalAccountEntry].[RECID] = [Intercompany].[RECID]
LEFT JOIN [DW].[ProductCategory] ON [GeneralJournalAccountEntry].[RECID] = [ProductCategory].[RECID]
LEFT JOIN [DW].[Region] ON [GeneralJournalAccountEntry].[RECID] = [Region].[RECID]
LEFT JOIN [DW].[SalesChannel] ON [GeneralJournalAccountEntry].[RECID] = [SalesChannel].[RECID]
LEFT JOIN [DW].[CostCenter] ON [GeneralJournalAccountEntry].[RECID] = [CostCenter].[RECID]
WHERE [EnumItemName] IN ('Revenue', 'Expense', 'BalanceSheet', 'Asset', 'Liability')
AND [FiscalCalendarPeriod].FiscalCalendarPeriodInt <= #FiscalCalendarPeriod
AND [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup
GROUP BY UPPER([GeneralJournalEntry].SubledgerVoucherDataAreaId)
, ISNULL(ConsolidationMainAccount, '')
, [GeneralJournalAccountEntry].TransactionCurrencyCode
, ISNULL([CostCenter].[GroupDimension], 'No Costcenter')
, ISNULL([Intercompany].[DisplayValue], 'No Intercompany')
, ISNULL([ProductCategory].[GroupDimension], 'No ProductCategory')
, ISNULL([Region].[GroupDimension], 'No Region')
, ISNULL([SalesChannel].[GroupDimension], 'No SalesChannel')
(would be a mess as a comment)
Do you mean if #par_entity is not null and has a value other than '' then use that else go on as if it is not there at all? Then you can change your code:
DECLARE #Entitygroup varchar = #par_Entity
To:
DECLARE #Entitygroup varchar = case
when #par_Entity is not null then #par_Entity
else ''
end;
And:
AND [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup
To:
AND (#Entitygroup = '' OR [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup)
PS: Performance wise it wouldn't be optimal.
EDIT: You might also set it to the possible max value when it is not passed. ie:
DECLARE #Entitygroup varchar = case
when #par_Entity is null or #par_entity = '' then 'zzzzzz'
else #par_Entity
end;
AND [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup

Declare a list of countries and render values depending on the country

I have two questions. I have a large table for a marketing campaign. I want to create a table that renders tariff numbers for Buyer Countries. AS I have large and quarterly changing lists of countries, I want to put them into a declare / Set statement.
How do you have to use string values in the Declare statement? Is there a better way? I tried all sorts of things, with commas , single quotes, no quotes in this list: ( ' ”AU” , ”GB” , ”NZ” ').
Is there a better way while using more Dynamic SQL in a Select statement?
This is what I tried so far:
DECLARE #tariff_1 varchar(100)
SET #tariff_1 = ( ' ”AU” , ”GB” , ”NZ” ')
DECLARE #tariff_2 varchar(100)
SET #tariff_2 = ( '"GB" , "US" , "GB" ')
DECLARE #tariff_3 varchar(100)
SET #tariff_3 = ( '"CH" , "US" , "JE" ')
SELECT
a.[Product_1]
,a.[Product_2]
,a.[Product_3]
,a.[Product_4]
,a.[Product_5]
,CASE WHEN a.[Product_1] IN (#tariff_1) THEN '3' ELSE '999' END AS 'Buyer_country1'
,CASE WHEN a.[Product_2] IN (#tariff_2) THEN '3.5' ELSE '999' END AS 'Buyer_country2'
,CASE WHEN a.[Product_3] IN (#tariff_2) THEN '4' ELSE '999' END AS 'Buyer_country3'
FROM [IDW_stage_DEV].[dbo].[table] a
The table looks as below:
Product_1 Product_2 Product_3 Product_4 Product_5 Buyer_country1 Buyer_country2
-------------------------------------------------------------------------------
AU GB NULL NULL NULL 999 999
GB US JE NULL NULL 999 999
AU GB US NULL NULL 999 999
'Buyer_country1' should return 3 in all 3 instances.
'Buyer_country2' should return 3.5 in all 3 instances.
'Buyer_country3'should should return 999 in the first column and then 4 in the remaining two columns.
Additionally, and dependent on the result above, I also need to create a variable, that would give me the country with the highest tariff value.
For Line 1
the newly created variable "highest_tariff_paid" should render: GB
For Line 2
the newly created variable "highest_tariff_paid" should render: JE
For Line 3
the newly created variable "highest_tariff_paid" should render: US
Can you do that without breaking up the select query? How would that work?
Use a table varaible:
DECLARE #tariff_1 TABLE (Code VARCHAR(100));
INSERT INTO #tariff_1 (Code) VALUES ('AU'), ('GB'), ('NZ');
DECLARE #tariff_2 TABLE (Code VARCHAR(100));
INSERT INTO #tariff_2 (Code) VALUES ('GB'), ('US'), ('GB');
DECLARE #tariff_3 TABLE (Code VARCHAR(100));
INSERT INTO #tariff_3 (Code) VALUES ('CH'), ('US'), ('JE');
SELECT
a.[Product_1]
, a.[Product_2]
, a.[Product_3]
, a.[Product_4]
, a.[Product_5]
, CASE WHEN a.[Product_1] IN (SELECT Code from #tariff_1) THEN '3' ELSE '999' END AS 'Buyer_country1'
, CASE WHEN a.[Product_2] IN (SELECT Code from #tariff_2) THEN '3.5' ELSE '999' END AS 'Buyer_country2'
, CASE WHEN a.[Product_3] IN (SELECT Code from #tariff_2) THEN '4' ELSE '999' END AS 'Buyer_country3'
FROM [IDW_stage_DEV].[dbo].[table] a

Using SQL Server CASE statement in WHERE

I want to select records from a table in a stored procedure. Given parameters can be empty or a string including some keys separated by comma (1, 2, etc)
I want to manage that when a parameter is an empty string, "WHERE" ignore searching.
I'm using this code:
where (CASE when #PatientID <> 0 then ( dental.ID_Sick in (1,2)) else (1=1) end)
Something like that is working in W3School. I mean:
SELECT * FROM Customers
WHERE (case when 1=1 then (Country IN ('Germany', 'France', 'UK')) else 1=1 end);
What is the problem in my query that does not work? SQLServerManagementStudio is giving error on "IN" statement.
Solution:
The best way to handle such optional parameters is to use dynamic SQL and built the query on the fly. Something like....
CREATE PROCEDURE myProc
#Param1 VARCHAR(100) = NULL
,#Param2 VARCHAR(100) = NULL
,#Param3 VARCHAR(100) = NULL
,#ListParam VARCHAR(100) = NULL
--, etc etc...
AS
BEGIN
SET NOCOUNT ON;
Declare #Sql NVARCHAR(MAX);
SET #Sql = N' SELECT *
FROM TableName
WHERE 1 = 1 '
-- add in where clause only if a value was passed to parameter
+ CASE WHEN #Param1 IS NOT NULL THEN
N' AND SomeColumn = #Param1 ' ELSE N'' END
-- add in where clause a different variable
-- only if a value was passed to different parameter
+ CASE WHEN #Param2 IS NOT NULL THEN
N' AND SomeOtherColumn = #Param3 ' ELSE N'' END
-- List Parameter used with IN clause if a value is passed
+ CASE WHEN #ListParam IS NOT NULL THEN
N' AND SomeOtherColumn IN (
SELECT Split.a.value(''.'', ''VARCHAR(100)'') IDs
FROM (
SELECT Cast (''<X>''
+ Replace(#ListParam, '','', ''</X><X>'')
+ ''</X>'' AS XML) AS Data
) AS t CROSS APPLY Data.nodes (''/X'') AS Split(a) '
ELSE N'' END
Exec sp_executesql #sql
, N' #Param1 VARCHAR(100), #Param2 VARCHAR(100) ,#Param3 VARCHAR(100) ,#ListParam VARCHAR(100)'
, #Param1
, #Param2
,#Param3
, #ListParam
END
Problem with Other approach
There is a major issue with this other approach, you write your where clause something like...
WHERE ( ColumnName = #Parameter OR #Parameter IS NULL)
The Two major issues with this approach
1) you cannot force SQL Server to check evaluate an expression first like if #Parameter IS NULL, Sql Server might decide to evaluate first the expression ColumnName = #Parameterso you will have where clause being evaluated even if the variable value is null.
2) SQL Server does not do Short-Circuiting (Like C#), even if it decides to check the #Parameter IS NULL expression first and even if it evaluates to true, SQL Server still may go ahead and evaluating other expression in OR clause.
Therefore stick to Dynamic Sql for queries like this. and happy days.
SQL Server does not have a Bool datatype, so you can't assign or return the result of a comparison as a Bool as you would in other languages. A comparison can only be used with IF-statements or WHERE-clauses, or in the WHEN-part of a CASE...WHEN but not anywhere else.
Your specific example would become this:
SELECT * FROM Customers
WHERE 1=1 OR Country IN ('Germany', 'France', 'UK')
It would be better readable to rewrite your statement as follows:
WHERE #PatientID = 0
OR dental.ID_Sick in (1,2)
Referring to your actual question, I'd advise to read the linked question as provided by B House.
May be this straight way will work for you
IF (#PatientID <> 0)
BEGIN
SELECT * FROM Customers
WHERE Country IN ('Germany', 'France', 'UK')
END
try this:
WHERE 1=(CASE WHEN #PatientID <>0 AND dental.ID_Sick in (1,2) THEN 1
WHEN #PatientID =0 THEN 1
ELSE 0
END)

Inserting records in temporary table in sql server?

In SQL Server, I declare one table and trying to insert records, but it is taking so much time to insert. This is my temp table :
declare #totalAprovals Table(
apptype varchar(max)
, Id varchar(max)
, empno varchar(max)
, empname varchar(max)
, AppliedDate varchar(max)
, rstatus varchar(max)
, LeaveType varchar(max)
, fromdate varchar(max)
, todate varchar(max)
, finyear varchar(max)
, noofdays varchar(max)
, perdate varchar(max)
, pertype varchar(max)
, TotMin varchar(max)
, FrmTime varchar(max)
, ToTime varchar(max)
, ConDate varchar(max)
, Amount varchar(max)
, MaterialDesc varchar(max)
, EstValue varchar(max)
, FromYear varchar(max)
, ToYear varchar(max)
, AvailedFrom varchar(max)
, AvailedTo varchar(max)
, Purpose varchar(max)
, FromPlace varchar(max)
, ToPlace varchar(max)
, ICode varchar(max)
, IDesc varchar(max)
, MgrId varchar(max)
)
and my insert statement :
insert into #totalAprovals
SELECT DISTINCT 'LEAVE' AppType
, CRS.applicationId ID
, CRS.EmpId EmpNo
, ISNULL((
SELECT FirstName
FROM Tbl_Emp_M
WHERE EmpId=CRS.EmpId
)
, CRS.EmpId) EmpName
, CONVERT(VARCHAR(10),LA.LeaveDate,103) AppliedDate
, (CASE ISNULL((
SELECT top 1 CurStatus
FROM Tbl_CRS_Leave_AppHis_T
WHERE stepno=CRS.StepNo-1
and applicationId=CRS.applicationId
AND Status=1
order by StepNo desc),'0')
WHEN '0' THEN 'Applied'
WHEN '1' THEN 'Recommended'
WHEN '2' THEN 'Approved'
END) Rstatus
, LT.LeaveName LeaveType
, CONVERT(VARCHAR(10),LA.FromDate,103) FromDate
, CONVERT(VARCHAR(10),LA.ToDate,103) ToDate
, '' FinYear
, '' NoOfDays
, '' PerDate
, '' PerType
, '' TotMin
, '' FrmTime
, '' ToTime
, '' ConDate
, 0 Amount
, '' MaterialDesc
, 0 EstValue
, '' FromYear
, '' ToYear
, ''AvailedFrom
, '' AvailedTo
, '' Purpose
, '' FromPlace
, '' ToPlace
, '' ICode
, '' IDesc
, CRS.MgrId
FROM Tbl_Leave_App_T LA
, Tbl_CRS_Leave_App_T CRS
, Tbl_Leave_Typ_M LT
, Tbl_Emp_ServiceDetails_T EMS
WHERE CRS.applicationId = LA.ApplicationId
AND LA.LeaveTypeId = LT.LeaveTypeId
and crs.EmpId = ems.EmpId
AND CRS.Status = 1
AND LA.Status = 1
AND LT.Status = 1
and ems.Status = 1
AND CRS.CurStatus IN ('0')
AND YEAR(LA.LeaveDate) = YEAR(GETDATE())
AND la.LeaveTypeId not in (9,12)
AND -- LA.ApplicationId LIKE '%LEV%' AND
CRS.EmpId = EMS.EmpId
and ems.LocationCode IN ('101','102','103','104','AHUP')
and crs.MgrId ='xxxxx'
It is taking 2 to 3 minutes to execute this. What could be the reason? Am I writing wrong process to insert records?
You have a performance problem so investigate it as a performance problem. Use a methodology like Waits and Queues. Follow the SQL Server PErformance Flowchart.
When you post here, always add the exact DDL used to create those tables, including all indexes, and capture and link the execution plans.
Most likely is not the INSERT the problem, but the SELECT. DISTINCT is always a code smell indicating a poorly understood join. The WHERE clause is full of non-sargable predicates.
I got the solution, actually i replace the declaring the table like
"declare #totalAprovals Table" to "create table #totalAprovals now it is working superb. Thank you for replying all.

How to insert unique value into non-identity field

I'm trying to do an insert into an established table which has a primary key fields and another field (call it field1) that is unique (this other unique field has a unique constraint preventing my inserts). Field1 is not an identity field, so it does NOT autonumber. Unfortunately I can't change the table. Existing inserts are made using code to increment and all involve looping/cursors. Something like SELECT MAX(field1) + 1
So, is there anyway to do this insert without looping/cursor? This field means nothing to me, but there are already 500,000+ records using their silly numbering scheme, so I must respect that.
This is simplified (ReceiptNumber is the field I want to insert unique), but:
SET XACT_ABORT ON
Begin Transaction TransMain
Declare #nvErrMsg nvarchar(4000)
--Insert inventory receipts
Insert Into Avanti_InventoryReceipts (
ReceiptNumber , ItemNumber , ReceiptDate , OrderNumber , JobNumber , Supplier ,
LineNumber , MultiLineNumber , [Status] , QtyOrdered , QtyReceived , QtyToReceive ,
QtyBackOrdered , Cost , Wholesale , LastCost , QtyToInvoice , QtyUsed ,
ReferenceNumber , [Description] , SupplierType , Processed , DateExpected , DateReceived ,
AccountNumber , Reference2 , EmployeeCode , ExtraCode , Location , RollNumber ,
QtyIssues , Notes , NumPackages , BundleSize , ConsignmentUnitPrice , RecFromProduction ,
QtyCommitted )
SELECT ( SELECT MAX(ReceiptNumber) + 1 FROM Avanti_inventoryReceipts ) , CR.ItemNumber , Convert(char(8), GETDATE(), 112) , PONum , 'FL-INV' , PH.POVendor ,
0 , 0 , 'O' , CR.QtyOrdered , QtyReceivedToday , QtyReceivedToday ,
Case #closePO
When 'N' Then Case When ( QtyOrdered - QtyReceivedToday ) < 0 Then 0 Else ( QtyOrdered - QtyReceivedToday) End
When 'Y' Then 0
Else 0 End
, PD.TransCost * QtyReceivedToday , IH.PriceWholeSale , IH.CostLast , QtyReceivedToday , 0 ,
'' , PODetailDescription , '' , '' , '' , Convert(char(8), GETDATE(), 112) ,
'' , '' , #employeeCode , '' , 'F L E X O' , '' ,
0 , 'Flexo Materials' , 0 , 0 , 0 , '' , 0
FROM FI_CurrentReceiptData CR
LEFT JOIN Avanti_PODetails PD ON CR.PONum = PD.PONumber
LEFT JOIN Avanti_POHeader PH ON CR.PONum = PH.PONumber
LEFT JOIN Avanti_InventoryHeader IH ON CR.ItemNumber = IH.ItemNumber
IF ##ERROR <> 0
Begin
Select #nvErrMsg = 'Error entering into [InventoryReceipts] -' + [description]
From master..sysmessages
Where [error] = ##ERROR
RAISERROR ( #nvErrMsg , 16, 1 )
Goto Err_
End
Commit Transaction TransMain
Goto Exit_
Err_:
Rollback Transaction TransMain
Exit_:
SET XACT_ABORT OFF
You could do this:
insert into mytable (field1, field2, ...)
values (( SELECT MAX(field1) + 1 from mytable), 'value2', ...);
Why not looping? It should be quite efficient.
Since you already have a UNIQUE constraint on the field, you can:
Simply try to insert MAX(field1) + 1. Since there is index on UNIQUE field, MAX is fast.
If its passes, great you are done.
If it fails (which will typically be manifested as an exception in your client code), just try again until you succeed.
Most of the time, the INSERT will succeed right away. In rare instances where a concurrent user tries to insert the same value, you'll handle that gracefully by trying the "next" value.
I added an autonumber starting from 0 in client code and passed that in. Now I'm adding that value to the max receiptnumber to get a unique one. Also, I realized I already had an identity column in FI_CurrentReceiptData, but I didn't want to use that one because it won't start at 0 for each receipt set, and reseeding the identity each time seems like a waste of processor time.