简体   繁体   中英

SQL Server: query performance optimization

I have following query whose execution time is getting very high with the increase of data. How I can optimize this?

SELECT 
    txh.clid AS clid,
    txh.id AS hlid,
    holdinNo,
    holding,
    ClientID AS cliendID,
    ClientName,
    itm.itemID,
    itm.Item,
    itm.rate,
    (SELECT TOP 1 asset 
     FROM tx_asset 
     WHERE tx_asset.hlid = txh.id 
     ORDER BY id DESC) AS asset
FROM 
    tx_holding AS txh
INNER JOIN 
    tx_set_bill_holding AS itm ON txh.id = itm.hlid AND itm.status = 1
WHERE 
    txh.id IN (SELECT hlid FROM tx_asset 
               WHERE asset IS NOT NULL AND asset != 0)
    AND txh.id NOT IN (SELECT hlid FROM tx_bill_pay 
                       WHERE YEAR(date_month) = YEAR(@tdate) 
                         AND hlid IS NOT NULL)
    AND txh.clid IN (SELECT id FROM tbl_client 
                     WHERE client_type = 'Non-Govt.')
    AND itm.type = 'Non-Govt.' 
    AND txh.roadno = @roadno

try this:

SELECT hlid,max(asset) asset into #temp FROM tx_asset group by hlid
SELECT txh.clid AS clid,
       txh.id AS hlid,
       holdinNo,
       holding,
       ClientID AS cliendID,
       ClientName,
       itm.itemID,
       itm.Item,
       itm.rate,
       t.asset
FROM tx_holding AS txh
INNER JOIN tx_set_bill_holding AS itm ON txh.id = itm.hlid AND itm.status=1
INNER /*or left*/ join #temp t on t.hlid=txh.id
WHERE txh.id IN 
(
 SELECT hlid FROM tx_asset WHERE asset IS NOT NULL AND asset!=0
)
 AND txh.id NOT IN 
 (
 SELECT hlid FROM tx_bill_pay WHERE year(date_month)=year(@tdate) AND hlid IS NOT NULL
 )
 AND txh.clid IN 
 (
  SELECT id FROM tbl_client WHERE client_type='Non-Govt.'
 )
 AND itm.type='Non-Govt.' AND txh.roadno=@roadno

 Drop table #temp

Your code have slow performance because you used function and sub queries in where clause, so I made some changes, I hope it works!

declare @tdate_year dateTime
select @tdate_year = year(@tdate)

WITH
tx_asset AS
(
    SELECT ROW_NUMBER () OVER (ORDER BY asset) rank
    FROM tx_asset
),
tbp AS
(
    SELECT *,year(tx_bill_pay.date_month) year_date
    FROM tx_bill_pay
),
txh AS
(
    SELECT *
    FROM tx_holding AS a
    INNER JOIN tx_asset As b
        ON a.id=b.hlid
    LEFT JOIN tbp As c
        ON a.id=c.id and year_date=@tdate_year
    INNER JOIN tbl_client As d on a.id=d.id
    WHERE
        b.rank = 1 AND b.asset IS NOT NULL AND b.asset!=0 AND
        c.id IS NULL AND c.hlid IS NOT NULL AND
        d.client_type='Non-Govt.'
)

SELECT
    txh.clid AS clid,
    txh.id AS hlid,
    holdinNo,
    holding,
    ClientID AS cliendID,
    ClientName,
    itm.itemID,
    itm.Item,
    itm.rate,
    asset
FROM txh
INNER JOIN tx_asset AS x
    ON txh.id = x.hlid
INNER JOIN tx_set_bill_holding AS itm
    ON txh.id = itm.hlid AND itm.status=1
WHERE
    itm.type='Non-Govt.' AND
    txh.roadno=@roadno

One thing that stands out is the predicate in this subquery:

(SELECT hlid 
 FROM tx_bill_pay 
 WHERE YEAR(date_month) = YEAR(@tdate) 
       AND hlid IS NOT NULL)

Applying the YEAR function will prevent an index on that column (assuming one exists) from being used efficiently so a full table scan of tx_bill_pay may be required and will be costly if the table is large. Try refactoring the expression as below so that such an index may be used.

Query optimization involves not just the query but indexes as well so add the DDL to your question as @Larnu requested in the comment.

(SELECT hlid 
 FROM tx_bill_pay 
 WHERE date_month >= DATEADD(year, DATEDIFF(year, '', @tdate))
      AND date_month < DATEADD(year, DATEDIFF(year, '', @tdate)) 
      AND hlid IS NOT NULL)

The query is basically:

SELECT . . .,
       (SELECT TOP 1 asset 
        FROM tx_asset 
        WHERE tx_asset.hlid = txh.id 
        ORDER BY id DESC) AS asset
FROM tx_holding txh INNER JOIN 
     tx_set_bill_holding itm
     ON txh.id = itm.hlid AND itm.status = 1
WHERE txh.id IN (SELECT a.hlid
                 FROM tx_asset a
                 WHERE a.asset IS NOT NULL AND a.asset <> 0
                ) AND
      txh.id NOT IN (SELECT bp.hlid
                     FROM tx_bill_pay bp
                     WHERE YEAR(bp.date_month) = YEAR(@tdate) AND
                           bp.hlid IS NOT NULL
                    ) AND
      txh.clid IN (SELECT c.id
                   FROM tbl_client c
                   WHERE c.client_type = 'Non-Govt.'
                  ) AND
      itm.type = 'Non-Govt.' AND
      txh.roadno = @roadno;

Rewriting the query using JOIN s is dangerous without understanding the data, because it might introduce duplicates. Instead, I would rewrite using EXISTS and then introduce indexes:

SELECT . . .,
       (SELECT TOP 1 a.asset 
        FROM tx_asset a 
        WHERE a.hlid = txh.id 
        ORDER BY id DESC) AS asset
FROM tx_holding txh INNER JOIN 
     tx_set_bill_holding itm
     ON txh.id = itm.hlid AND itm.status = 1
WHERE EXISTS (SELECT 1
              FROM tx_asset a
              WHERE a.hlid = txh.id AND
                    a.asset <> 0  -- checks for NULL as well
             ) AND
      NOT EXISTS (SELECT 1
                  FROM tx_bill_pay bp
                  WHERE bp.hlid = txh.id AND
                        YEAR(bp.date_month) = YEAR(@tdate) AND
                        bp.hlid IS NOT NULL
                 ) AND
      EXISTS (SELECT 1
              FROM tbl_client c
              WHERE c.id = txh.clid AND
                    c.client_type = 'Non-Govt.'
             ) AND
      itm.type = 'Non-Govt.' AND
      txh.roadno = @roadno;

Then, for the subqueries, you want indexes on:

  • tx_asset(hlid, id desc, asset)
  • tx_bill_pay(hlid, date_month, hlid)
  • tbl_client(id, client_type)

For the main JOIN , you probably want indexes on:

  • tx_holding(roadno, id)
  • tx_set_bill_holding(hlid, status, type)
Declare @Fromdate datetime='2019-01-01'
Declare @ToDate datetime='2019-12-31'

create table #tx_asset(hlid int,asset int)
insert into #tx_asset(hlid,asset)
SELECT hlid,asset FROM tx_asset 
               WHERE asset > 0 -- this wil work for null and <>0 both

SELECT 
    txh.clid AS clid,
    txh.id AS hlid,
    holdinNo,
    holding,
    ClientID AS cliendID,
    ClientName,
    itm.itemID,
    itm.Item,
    itm.rate,
    oa.asset
FROM 
    dbo.tx_holding AS txh
INNER JOIN 
    dbo.tx_set_bill_holding AS itm ON txh.id = itm.hlid AND itm.status = 1
    outer apply(SELECT TOP 1 asset 
     FROM #tx_asset 
     WHERE tx_asset.hlid = txh.id 
     ORDER BY id DESC)oa
WHERE 
    exists (SELECT 1 FROM #tx_asset 
               WHERE hlid=txh.id )
    AND  NOT exists (SELECT hlid FROM dbo.tx_bill_pay 
                       WHERE hlid=txh.id and YEAR(date_month) = YEAR(@tdate) 
                         AND hlid IS NOT NULL)
    AND exists IN (SELECT 1 FROM dbo.tbl_client 
                     WHERE id=txh.clid and client_type = 'Non-Govt.')
    AND itm.type = 'Non-Govt.' 
    AND txh.roadno = @roadno
  1. Put tx_asset in temp table
  2. Instead of sub query use Outer Apply.
  3. If date_month is index column then it matter.It is not SARGAble .to make this SARGable do this,

    date_month>=@Fromdate and date_month<=@ToDate

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM