Slow performance in SQL Server

The application that we created has undergone significant changes in its database design, especially in how financial data is stored. We have functions that calculate the total amount of billing based on various scenarios; and this change causes huge performance problems when functions have to be executed many times in a row.

I will include an explanation, a function, and an appropriate schema, and I hope someone sees a much better way to write this function. This is SQL Server 2008.

First, the business foundation: think about a medical procedure. The health care provider performing the procedure sends one or more bills, each of which may have one or more items (BillItems).

This procedure is re-billed to the other side. The amount billed to a third party may be:

  • Supplier Billing Total
  • Provider billing amount plus Copay or
  • Fully separate amount (recharge amount)

The current function for calculating billing for the procedure looks in all three scenarios:

CREATE FUNCTION [dbo].[fnProcTotalBilled]  (@PROCEDUREID INT)
    RETURNS MONEY AS
BEGIN
DECLARE @billed MONEY
    SELECT @billed = (SELECT COALESCE((SELECT COALESCE(sum(bi.Amount),0)
    FROM BillItems bi INNER JOIN Bills b ON b.BillID=bi.BillID
        INNER JOIN Procedures p on p.ProcedureID=b.ProcedureID
    WHERE b.ProcedureID=@PROCEDUREID
    AND p.StatusID=3
    AND b.HasCopay=0
    AND b.Rebill=0),0))
-- the total of the provider billing, with no copay and not rebilled
    +
    (SELECT COALESCE((SELECT sum(bi.Amount) + COALESCE(b.CopayAmt,0)
    FROM BillItems bi INNER JOIN Bills b ON b.BillID=bi.BillID
        INNER JOIN Procedures p on p.ProcedureID=b.ProcedureID
    WHERE b.ProcedureID=@PROCEDUREID
    AND p.StatusID=3
    AND b.HasCopay=1
    GROUP BY b.billid,b.CopayAmt),0))
-- the total of the provider billing, plus a Copay amount
    +
    (SELECT COALESCE((SELECT sum(COALESCE(b.RebillAmt,0))
    FROM Bills b
        INNER JOIN Procedures p on p.ProcedureID=b.ProcedureID
    WHERE b.ProcedureID=@PROCEDUREID
    AND p.StatusID=3
    AND b.Rebill=1),0))
-- the Rebill amount, instead of the provider billing
    RETURN @billed
END

I will skip DDL for the procedure. It is enough to say that it must have a certain status (shown in the function as p.StatusID = 3).

Here is the DDL for bills and related BillItems:

CREATE TABLE dbo.Bills (
    BillID int IDENTITY(1,1) NOT NULL,
    InvoiceID int DEFAULT ((0)),
    CaseID int NOT NULL,
    ProcedureID int NOT NULL,
    TherapyGroupID int DEFAULT ((0)) NOT NULL,
    ProviderID int NOT NULL,
    Description varchar(1000),
    ServiceDescription varchar(255),
    BillReferenceNumber varchar(100),
    TreatmentDate datetime,
    DateBilled datetime,
    DateBillReceived datetime,
    DateBillApproved datetime,
    HasCopay bit DEFAULT ((0)) NOT NULL,
    CopayAmt money,
    Rebill bit DEFAULT ((0)) NOT NULL,
    RebillAmt money,
    IncludeInDemand bit DEFAULT ((1)) NOT NULL,
    CreateDate datetime DEFAULT (getdate()) NOT NULL,
    CreatedByID int,
    ChangeDate datetime,
    ChangeUserID int,
    PRIMARY KEY (BillID)
);


CREATE TABLE dbo.BillItems (
    BillItemID int IDENTITY(1,1) NOT NULL,
    BillID int NOT NULL,
    ItemDescription varchar(1000),
    Amount money,
    WillNotBePaid bit DEFAULT ((0)) NOT NULL,
    CreateDate datetime DEFAULT (getdate()),
    CreatedByID int,
    ChangeDate datetime,
    ChangeUserID varchar(25),
    PRIMARY KEY (BillItemID)
);

I fully understand how complex the function is; but I could not find another way to account for all the scenarios.

I hope that a much better SQL programmer or database administrator will see a more efficient solution.

Any help would be greatly appreciated.

Thank,

Tom

UPDATE:

. , .

-, : - . ; .

"" .

, - . -. Copay ( ) BillItems. .

Case ( ) , .

, .

, @Serpiton SQL Fiddle - , . .

, CTE @Serpiton @GarethD - . CTE, SELECT.

@Serpiton CTE Case. , , , . , .

:

WITH Normal As (
SELECT b.BillID
   , b.CaseID
   , sum(coalesce(n.Amount * (1 - b.Rebill), 0)) Amount
FROM   Procedures p
     INNER JOIN Bills b ON p.ProcedureID = b.ProcedureID
     LEFT  JOIN BillItems n ON b.BillID = n.BillID
WHERE  b.CaseID = 3444
AND  p.StatusID = 3
GROUP BY b.CaseID,b.BillID, b.HasCopay
)
SELECT Amount = Sum(b.Amount) 
          + Sum(Coalesce(c.CopayAmt, 0)) 
          + Sum(Coalesce(r.RebillAmt, 0))
FROM   Normal b
   LEFT  JOIN Bills c ON b.BillID = c.BillID And c.HasCopay = 1
   LEFT  JOIN Bills r ON b.BillID = r.BillID And r.Rebill = 1
GROUP BY b.caseid
+4
5


, CTE, .
, , ,

WHERE b.CaseID IN (list of cases)

- , , , , CaseID.


@DRapp ( , , ), , BillItems , .

WITH Normal As (
  SELECT b.BillID
       , b.ProcedureID
       , sum(coalesce(n.Amount * (1 - b.Rebill), 0)) Amount
  FROM   Procedures p
         INNER JOIN Bills b ON p.ProcedureID = b.ProcedureID
         LEFT  JOIN BillItems n ON b.BillID = n.BillID
WHERE  p.ProcedureID = @PROCEDUREID
  AND  p.StatusID = 3
GROUP BY b.ProcedureID, b.BillID, b.HasCopay
)
SELECT @Billed = Sum(b.Amount) 
               + Sum(Coalesce(c.CopayAmt, 0)) 
               + Sum(Coalesce(r.RebillAmt, 0))
FROM   Normal b
       LEFT  JOIN Bills c ON b.BillID = c.BillID And c.HasCopay = 1
       LEFT  JOIN Bills r ON b.BillID = r.BillID And r.Rebill = 1
GROUP BY b.ProcedureID


Normal CTE , ProcedureID, , Amount * (1 - Rebill) 0, .
Normal CTE , Normal ProcedureID, Procedures .

.


SELECT @billed = Sum(Coalesce(n.Amount, 0)) 
               + Sum(Coalesce(c.CopayAmt, 0)) 
               + Sum(Coalesce(r.RebillAmt, 0))
FROM   Procedures p on 
       INNER JOIN Bills b ON p.ProcedureID = b.ProcedureID And b.Rebill = 0
       INNER JOIN BillItems n ON b.BillID = n.BillID
       INNER JOIN Bills c ON p.ProcedureID = b.ProcedureID And c.HasCopay = 1
       INNER JOIN Bills r ON p.ProcedureID = b.ProcedureID And r.Rebill = 1
Where  p.ProcedureID = @PROCEDUREID
  AND  p.StatusID = 3

b "" ( n ), c r .
JOIN b b.Rebill = 0, "" , .
, HasCopay, Rebill 1

+1

( ) (INLINE) FUNCTION SCALAR (MULTI-STATEMENT).

CREATE FUNCTION [dbo].[fnProcTotalBilled]  (@PROCEDUREID INT)
AS
RETURN (
        SELECT
          (sub-query1)
          +
          (sub-query2)
          +
          (sub-query3)   AS amount
       );

:

SELECT
  something.*,
  totalBilled.*
FROM
  something
CROSS APPLY            -- Or OUTER APPLY
  [dbo].[fnProcTotalBilled](something.procedureID)   AS totalBilled

, .
- INLINE ( Multi-Statement)
- ( )


- , .

EDIT:

, , . , .

SELECT
  SUM(
    CASE WHEN b.HasCopay = 0 AND b.Rebill = 0 THEN               COALESCE(bi.TotalAmount, 0)
         WHEN b.HasCopay = 1                  THEN b.CopayAmt  + COALESCE(bi.TotalAmount, 0)
         WHEN                    b.Rebill = 1 THEN b.RebillAmt
                                              ELSE 0
    END
  )  AS Amount
FROM
  Procedures p
INNER JOIN
  Bills      b
    ON  b.ProcedureID = p.ProcedureID
LEFT JOIN
(
  SELECT BillID, SUM(Amount) AS TotalAmount
    FROM BillItems
GROUP BY BillID
)
  AS bi
    ON  bi.BillID     = b.BillID
WHERE
      p.ProcedureID=@PROCEDUREID
  AND p.StatusID=3

"", , - , BillItems BillID. , , JOIN WHERE.

, Bill: BillItem 1: 0..1, . ;)

+2

, , , , billID ( , ). , , :

(SELECT COALESCE((SELECT sum(bi.Amount) + COALESCE(b.CopayAmt,0)
FROM BillItems bi INNER JOIN Bills b ON b.BillID=bi.BillID
    INNER JOIN Procedures p on p.ProcedureID=b.ProcedureID
WHERE b.ProcedureID=@PROCEDUREID
AND p.StatusID=3
AND b.HasCopay=1
GROUP BY b.billid,b.CopayAmt),0))

- , , . , , .

, UDF, , , .

, , :

SELECT  p.ProcedureID,
        bi.Amount,
        b.HasCopay,
        b.CopayAmt,
        b.Rebill,
        b.RebillAmt,
FROM    (   SELECT  BillID, Amount = SUM(Amount)
            FROM    Billitems 
            GROUP BY BillID
        ) bi
        INNER JOIN Bills b
            ON b.BillID = bi.BillID
        INNER JOIN Procedures p
            ON p.ProcedureID = b.ProcedureID
WHERE   p.StatusID = 3;

, :

SELECT  p.ProcedureID,
        Amount = CASE WHEN b.Rebill = 0 THEN bi.Amount ELSE 0 END,
        CopayAmt = CASE WHEN b.HasCopay = 1 THEN b.CopayAmt ELSE 0 END,
        RebillAmt = CASE WHEN b.Rebill = 1 THEN b.RebillAmt ELSE 0 END,
FROM    (   SELECT  BillID, Amount = SUM(Amount)
            FROM    Billitems 
            GROUP BY BillID
        ) bi
        INNER JOIN Bills b
            ON b.BillID = bi.BillID
        INNER JOIN Procedures p
            ON p.ProcedureID = b.ProcedureID
WHERE   p.StatusID = 3;

( APPLY, case Total):

CREATE VIEW dbo.ProcTotalBilled
AS
    SELECT  p.ProcedureID,
            Amount = SUM(calc.Amount),
            CopayAmt = SUM(calc.CopayAmt),
            Rebill = SUM(cal.RebillAmt),
            Total = SUM(calc.Amount +  calc.CopayAmt + cal.RebillAmt)
    FROM    (   SELECT  BillID, Amount = SUM(Amount)
                FROM    Billitems 
                GROUP BY BillID
            ) bi
            INNER JOIN Bills b
                ON b.BillID = bi.BillID
            INNER JOIN Procedures p
                ON p.ProcedureID = b.ProcedureID
            CROSS APPLY
            (   SELECT  Amount = CASE WHEN b.Rebill = 0 THEN bi.Amount ELSE 0 END,
                        CopayAmt = CASE WHEN b.HasCopay = 1 THEN b.CopayAmt ELSE 0 END,
                        RebillAmt = CASE WHEN b.Rebill = 1 THEN b.RebillAmt ELSE 0 END
            ) calc
    WHERE   p.StatusID = 3
    GROUP BY p.ProcedureID;

- :

SELECT  Total = dbo.fnProcTotalBilled(p.ProcedureID)
FROM    dbo.Procedures p;

SELECT  Total = ISNULL(ptb.Total, 0)
FROM    dbo.Procedures p
        LEFT JOIN dbo.ProcTotalBilled ptb
            ON ptb.ProcedureID = p.ProcedureID;

, , UDF

+1

, ? , , , - , , BillID . , . , , , .

... , , , DDL .

, , crud , .

0

, . :

select sum
(
  coalesce( case when b.rebill = 1 then b.rebillamt end , 0 ) +
  coalesce( case when b.rebill = 0 then (select sum(bi.amount) from billitems bi where bi.billid = b.billid) end , 0 ) +
  coalesce( case when b.rebill = 0 and b.hascopay = 1 then b.copayamt end , 0 )
) as value
from procedures p
inner join bills b on b.procedureid = p.procedureid
where p.ProcedureID = @PROCEDUREID
and p.StatusID = 3;

T-SQL " , ". , .

select sum(value) as total
from
(
  select
    coalesce( case when b.rebill = 1 then b.rebillamt end , 0 ) +
    coalesce( case when b.rebill = 0 then (select sum(bi.amount) from billitems bi where bi.billid = b.billid) end , 0 ) +
    coalesce( case when b.rebill = 0 and b.hascopay = 1 then b.copayamt end , 0 ) as value
  from procedures p
  inner join bills b on b.procedureid = p.procedureid
  where p.ProcedureID = @PROCEDUREID
  and p.StatusID = 3
) allvalues;

, . Serpiton SQL ( Serpiton ), T-SQL , . . , SQL Server :

select sum(value) as total
from
(
  select
    coalesce( case when b.rebill = 1 then b.rebillamt end , 0 ) +
    coalesce( case when b.rebill = 0 then (select sum(bi.amount) from billitems bi where bi.billid = b.billid) end , 0 ) +
    coalesce( case when b.rebill = 0 and b.hascopay = 1 then b.copayamt end , 0 ) as value
  from bills b
  where b.procedureid = 
  (
    select p.procedureid
    from procedures p
    where p.ProcedureID = @PROCEDUREID
    and p.StatusID = 3
  )
) allvalues;

EDIT: Here is another option. If the given procedure identifier always exists, and you only want to check whether the status identifier is 3, then you can write the operator so that the bill selection is performed only in the case of the status identifier = 3. This is not necessarily Faster; it may even be slower. This is another option you can try.

select
  case when p.StatusID = 3 then
    (
      select sum(value)
      from
      (
        select
          coalesce( case when b.rebill = 1 then b.rebillamt end , 0 ) +
          coalesce( case when b.rebill = 0 then (select sum(bi.amount) from billitems bi where bi.billid = b.billid) end , 0 ) +
          coalesce( case when b.rebill = 0 and b.hascopay = 1 then b.copayamt end , 0 ) as value
        from bills b 
        where b.procedureid = p.procedureid
      ) allvalues
    )
  else
    0
  end as value 
from procedures p
where p.ProcedureID = @PROCEDUREID;
0
source

Source: https://habr.com/ru/post/1537895/


All Articles