Tuesday, August 9, 2022

Script to find large indexes of Tables in database and rebuilding indexes for better performance

Script to find large indexes of tables in specific database.

--------Script Starts

declare @MaxPercentOfTotal numeric(6, 2) = 100.0

declare @MinPercentOfTotal numeric(6, 2) = 0.5

DECLARE @AddCompressionEstimate BIT = 0

declare @IndexInfo TABLE

(

    SchemaName sysname,

    TableName sysname,

    IndexName sysname NULL,

    IndexId INT NULL,

    Rows INT,

    TotalSpaceMB BIGINT,

    UsedSpaceMB BIGINT,

    ReservedSpaceMB BIGINT,

    PercentOfTotal numeric(6, 2),

    InUse BIT,

    FragmentationPercent numeric(6, 2),

    CompressionStatus TINYINT,

    CompressionStatusDesc sysname NULL,

    PageCompressionSize BIGINT NULL,

    RowCompressionSize BIGINT NULL

)

;with SpaceInfo(SchemaName, TableName, IndexName, IndexId, Rows, TotalSpaceMB, UsedSpaceMB)

as

    select  

        s.name as SchemaName,

        t.Name as [TableName],

        i.name as [Index Name],

        i.index_id as [IndexId],

        sum(p.[Rows]) as [Rows],

        sum(au.total_pages) * 8 / 1024 as [Total Space MB],

        sum(au.used_pages) * 8 / 1024 as [Used Space MB]

    from sys.tables t 

        join sys.schemas s on s.schema_id = t.schema_id

        join sys.indexes i on t.object_id = i.object_id

        join sys.partitions p on i.object_id = p.object_id and i.index_id = p.index_id

        cross apply

        (

            select 

                sum(a.total_pages) as total_pages,

                sum(a.used_pages) as used_pages

            from sys.allocation_units a 

            where p.partition_id = a.container_id 

        ) au

    where i.object_id > 255

    group by t.object_id, i.index_id, s.name, t.name, i.name

)

INSERT INTO @IndexInfo(SchemaName, TableName, IndexName, IndexId, Rows, TotalSpaceMB, UsedSpaceMB, ReservedSpaceMB)

select 

    SchemaName, TableName, IndexName, IndexId, Rows, TotalSpaceMB, UsedSpaceMB

    ,TotalSpaceMB - UsedSpaceMB as [ReservedSpaceMB]

from SpaceInfo

where TotalSpaceMB > 0

order by TotalSpaceMB desc

option (recompile)

DECLARE @TotalSpaceMB BIGINT

select @TotalSpaceMB = SUM(TotalSpaceMB)

from @IndexInfo

update @IndexInfo

set PercentOfTotal = (TotalSpaceMB * 100.0) / @TotalSpaceMB

DELETE FROM @IndexInfo

WHERE PercentOfTotal < @MinPercentOfTotal OR PercentOfTotal > @MaxPercentOfTotal

update ii

set InUse = IIF(

    (s.user_seeks IS NULL OR s.user_seeks = 0)

    AND (s.user_lookups IS NULL OR s.user_lookups = 0)

    and (s.user_scans IS NULL OR s.user_scans = 0),

    0, 1),

    CompressionStatus = p.data_compression,

    CompressionStatusDesc = p.data_compression_desc

from @IndexInfo ii

    INNER join sys.indexes i ON OBJECT_NAME(i.OBJECT_ID) = ii.TableName AND OBJECT_SCHEMA_NAME(i.OBJECT_ID) = ii.SchemaName AND i.index_id = ii.IndexId

    LEFT JOIN sys.partitions p ON p.OBJECT_ID = i.OBJECT_ID AND p.index_id = i.index_id

    LEFT JOIN sys.allocation_units a ON a.container_id = p.partition_id

    LEFT JOIN sys.dm_db_index_usage_stats s ON s.object_id = i.object_id and i.index_id = s.index_id

update ii

set FragmentationPercent = ph.avg_fragmentation_in_percent

from @IndexInfo ii

    INNER join sys.indexes i ON OBJECT_NAME(i.OBJECT_ID) = ii.TableName AND OBJECT_SCHEMA_NAME(i.OBJECT_ID) = ii.SchemaName AND i.index_id = ii.IndexId

    LEFT JOIN sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL, NULL) ph on i.object_id = ph.object_id and i.index_id = ph.index_id

declare @CompressionSavings TABLE

(

    ObjectName NVARCHAR(128),

    SchemaName NVARCHAR(128),

    IndexId INT,

    PartitionNumber INT,

    CurrentSizeKB BIGINT,

    CompressedSizeKB BIGINT,

    CurrentSampleSizeKB BIGINT,

    RequestedSampleSizeKB BIGINT,

    NewSizePercent NUMERIC(6, 2)

)

IF @AddCompressionEstimate = 1

BEGIN

    DECLARE @err int

    declare @TableName sysname

    declare @SchemaName sysname

    DECLARE @IndexId INT

    DECLARE loopCursor CURSOR FOR

    SELECT SchemaName, TableName, IndexId

    FROM @IndexInfo

    where CompressionStatus = 0

    OPEN loopCursor

    WHILE 1 = 1

    BEGIN

        FETCH loopCursor INTO @SchemaName, @TableName, @IndexId

        SELECT @err = @@error

        IF @err <> 0 OR @@fetch_status <> 0

            BREAK

        DELETE FROM @CompressionSavings

        INSERT INTO @CompressionSavings(ObjectName,SchemaName,IndexId,PartitionNumber,CurrentSizeKB,CompressedSizeKB,CurrentSampleSizeKB,RequestedSampleSizeKB)

        EXEC sys.sp_estimate_data_compression_savings @SchemaName, @TableName, @IndexId, NULL, 'PAGE';  

        update ii

        set PageCompressionSize = (select top 1 ISNULL((CAST(CompressedSizeKB AS NUMERIC(16, 2)) * 100.0) / CAST(CurrentSizeKB AS NUMERIC(16, 2)), 0.0) from @CompressionSavings)

        from @IndexInfo ii

        where SchemaName = @SchemaName AND TableName = @TableName AND IndexId = @IndexId

        DELETE FROM @CompressionSavings

        INSERT INTO @CompressionSavings(ObjectName,SchemaName,IndexId,PartitionNumber,CurrentSizeKB,CompressedSizeKB,CurrentSampleSizeKB,RequestedSampleSizeKB)

        EXEC sys.sp_estimate_data_compression_savings @SchemaName, @TableName, @IndexId, NULL, 'ROW';  

        update ii

        set RowCompressionSize = (select top 1 ISNULL((CAST(CompressedSizeKB AS NUMERIC(16, 2)) * 100.0) / CAST(CurrentSizeKB AS NUMERIC(16, 2)), 0.0) from @CompressionSavings)

        from @IndexInfo ii

        where SchemaName = @SchemaName AND TableName = @TableName AND IndexId = @IndexId

    END

    DEALLOCATE loopCursor

END

select * from @IndexInfo

order by TotalSpaceMB desc 

---------Script Ends

Then rebuild the indexes which has more fregmentation percentage as suggested in microsoft document. 

Commerce Data Exchange best practices - Commerce | Dynamics 365 | Microsoft Docs


Hope this helps


Wednesday, May 11, 2022

Code to process InventTransOrigin info

 class RunnableClass2

{

    InventTransOrigin transOrigin;

    InventTrans       trans;

    InventDim         inventDim;

    InventTable       tempTable;

    


    /// <summary>

    /// Runs the class with the specified arguments.

    /// </summary>

    /// <param name = "_args">The specified arguments.</param>

    public void ProcessReport()

    {

        utcdatetime fromDateTime;

        utcdatetime toDateTime;

        FromDate    fromDate;

        ToDate      toDate;

        InventSiteId site;

        InventLocationId warehouse;


        fromDateTime =  DateTimeUtil::newDateTime(fromDate,0);

        toDateTime = DateTimeUtil::newDateTime(toDate,86400);


        this.populateOnorderSales(fromDateTime, toDateTime, site, warehouse);

        this.populateOnorderTransfers(fromDateTime, toDateTime, site, warehouse);

    }


    public void populateOnorderSales(utcdatetime _fromDate, utcdatetime _toDate, InventSiteId _site, InventLocationId _warehouse)

    {

        SalesTable        salesTable;

        SalesLine         salesLine;

        

        while select transOrigin

            join trans

            where trans.InventTransOrigin == transOrigin.RecId

            && transOrigin.ReferenceCategory == InventTransType::Sales

            && trans.StatusIssue == StatusIssue::OnOrder

            join inventDim

            where inventDim.inventDimId == trans.inventDimId

            && (inventDim.InventSiteId == _site || !_site)

            && (inventDim.InventLocationId == _warehouse || !_warehouse)

            join salesTable

            where salesTable.SalesId == transOrigin.ReferenceId

            && salesTable.CreatedDateTime >= _fromDate

            && salesTable.CreatedDateTime <= _toDate

        {

            salesLine = SalesLine::findInventTransId(transOrigin.InventTransId);

            tempTable.clear();

            this.insertTempTable();

            //tempTable.CreatedDateTime = salesTable.CreatedDateTime;

            tempTable.insert();

        }

    }


    public void populateOnorderTransfers(utcdatetime _fromDate, utcdatetime _toDate, InventSiteId _site, InventLocationId _warehouse)

    {

        InventTransferTable transferTable;

        

        while select transOrigin

            join trans

            where trans.InventTransOrigin == transOrigin.RecId

            && transOrigin.ReferenceCategory == InventTransType::TransferOrderShip

            && trans.StatusIssue == StatusIssue::OnOrder

            join inventDim

            where inventDim.inventDimId == trans.inventDimId

            && (inventDim.InventSiteId == _site || !_site)

            && (inventDim.InventLocationId == _warehouse || !_warehouse)

            join transferTable

            where transferTable.TransferId == transOrigin.ReferenceId

            && transferTable.CreatedDateTime >= _fromDate

            && transferTable.CreatedDateTime <= _toDate

        {

            tempTable.clear();

            this.insertTempTable();

            //tempTable.CreatedDateTime = transferTable.CreatedDateTime;

            tempTable.insert();

        }

    }


    public void insertTempTable()

    {

        tempTable.ItemId = transOrigin.ItemId;

    }


}

Thursday, May 5, 2022

Reversing general journal using x++ code in Dynamics 365 finance and operation


Hello Guys, 

Recently I had a requirement to reversing already posted general journal and below code will help you to create the same. copy the journal I have not mentioned posting logic below.

  /// <summary>

    /// Creates a reversing entry record in the <c>LedgerJournalTrans</c> table for the current journal

    /// number that is being posted.

    /// </summary>

    /// <param name="_numberSequenceTableRecId">

    /// The number sequence ID to use when you are retrieving voucher numbers for the reversing entries.

    /// </param>

    /// <remarks>

    /// For the current record being posted, each <c>LedgerJournalTrans</c> record that has a

    /// <c>ReverseEntry</c> field value of true and has a <c>ReverseDate</c> field value that is not empty

    /// will have a reversing entry created for it in the <c>LedgerJournalTrans</c> table. New vouchers

    /// will be created to hold the reversing entries. Each unique originating voucher will have a new

    /// reversing voucher created for it. If the journal is set up to use one voucher number only or Manual

    /// voucher numbers, the Voucher on each reversing line will be taken from its originating line. Each

    /// originating voucher line will be updated with a link to its corresponding new reversing line.

    /// </remarks>

    protected void createRevEntries(RefRecId _numberSequenceTableRecId, LedgerJournalId _journalIdToReverse, LedgerJournalId _journalId)

    {

        LedgerJournalTrans  updLedgerJournalTrans;

        NumberSeq           numberSeq;

        Voucher             newVoucher,

                            prevVoucher;

        TransactionTxt      transactionTxt;


        // get the text defined for ledger reversing entries

        transactionTxt = TransactionTxt::construct(LedgerTransTxt::LedgerReversingEntry);


        while select forupdate updLedgerJournalTrans 

            order by Voucher 

            where updLedgerJournalTrans.JournalNum == _journalIdToReverse

        {

            if (!prevVoucher || prevVoucher != updLedgerJournalTrans.Voucher)

            {

                numberSeq   = NumberSeq::newGetVoucherFromId(_numberSequenceTableRecId, false, false);

                newVoucher = numberSeq.voucher();

                numberSeq.used();

                

                prevVoucher = updLedgerJournalTrans.Voucher;


                // set the originating voucher into the transaction text defined for ledger reversing entries

                transactionTxt.setVoucher(updLedgerJournalTrans.Voucher);

                transactionTxt.setDate(DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone()));

                transactionTxt.setFormLetter(LedgerDimensionFacade::getMainAccountIdFromLedgerDimension(updLedgerJournalTrans.LedgerDimension));

                transactionTxt.setLanguage(currentUserLanguage());

            }


            this.createReverseEntryJournalLine(updLedgerJournalTrans, newVoucher, transactionTxt.txt(), _journalId);


            // Use doUpdate() instead of update() since the only value being

            // changed is the link to the reversing entry. Since that does not

            // impact taxes or the other validation done in the update() method,

            // that code can all be skipped to save processing time.

            updLedgerJournalTrans.doUpdate();

        }

    }


    /// <summary>

    ///    Creates a reversing entry <c>LedgerJournalTrans</c> record that uses the specified voucher and

    ///    transaction text that offsets the <c>LedgerJournalTrans</c> record.

    /// </summary>

    /// <param name="_ledgerJournalTrans">

    ///    The <c>LedgerJournalTrans</c> record for which to create the reversing entry

    ///    <c>LedgerJournalTrans</c> record.

    /// </param>

    /// <param name="_voucher">

    ///    The voucher to use when you are creating the reversing <c>LedgerJournalTrans</c> record.

    /// </param>

    /// <param name="_transactionTxt">

    ///    The transaction text to use when you are creating the reversing <c>LedgerJournalTrans</c> record.

    /// </param>

    /// <remarks>

    ///    This method also creates reversing child records in the <c>LedgerJournalTrans_Asset</c> table and

    ///    the <c>LedgerJournalTrans_Project</c> tables, if the <c>LedgerJournalTrans</c> record being

    ///    reversed had child records in those tables.

    /// </remarks>

    protected void createReverseEntryJournalLine(

        LedgerJournalTrans      _ledgerJournalTrans,

        Voucher                 _voucher,

        LedgerJournalTransTxt   _transactionTxt,

        LedgerJournalId _journalId)

    {

        LedgerJournalTrans ledgerJournalTransRE = this.populateReverseEntryJournalLine(_ledgerJournalTrans, _voucher, _transactionTxt, _journalId);


        ledgerJournalTransRE.insert();


        this.populateReverseEntryJournalLineForUpdate(_ledgerJournalTrans, ledgerJournalTransRE);

        

        ledgerJournalTransRE.doUpdate();


        // generate the taxes for the reversal based on the taxes of the original line to insure modified amounts are reversed.

        TaxReverse::reverseTaxUncommitted(_ledgerJournalTrans.TableId, _ledgerJournalTrans.RecId, ledgerJournalTransRE.TableId, ledgerJournalTransRE.RecId, _voucher, _ledgerJournalTrans.ReverseDate);


        _ledgerJournalTrans.RevRecId = ledgerJournalTransRE.RecId;

    }


    /// <summary>

    ///    Populates the reversing entry <c>LedgerJournalTrans</c> record that uses the specified voucher and

    ///    transaction text that offsets the <c>LedgerJournalTrans</c> record.

    /// </summary>

    /// <param name="_ledgerJournalTrans">

    ///    The <c>LedgerJournalTrans</c> record for which to base field values on the reversing entry

    ///    <c>LedgerJournalTrans</c> record.

    /// </param>

    /// <param name="_voucher">

    ///    The voucher to use when you are creating the reversing <c>LedgerJournalTrans</c> record.

    /// </param>

    /// <param name="_transactionTxt">

    ///    The transaction text to use when you are creating the reversing <c>LedgerJournalTrans</c> record.

    /// </param>

    protected LedgerJournalTrans populateReverseEntryJournalLine(

        LedgerJournalTrans      _ledgerJournalTrans,

        Voucher                 _voucher,

        LedgerJournalTransTxt   _transactionTxt,

        LedgerJournalId _journalId)

    {

        LedgerJournalTrans ledgerJournalTransRE;


        ledgerJournalTransRE.JournalNum         = _journalId;

        ledgerJournalTransRE.Voucher            = _voucher;

        ledgerJournalTransRE.TransDate          = _ledgerJournalTrans.ReverseDate;

        ledgerJournalTransRE.AccountType        = _ledgerJournalTrans.AccountType;

        ledgerJournalTransRE.LedgerDimension    = _ledgerJournalTrans.LedgerDimension;

        ledgerJournalTransRE.OffsetAccountType  = _ledgerJournalTrans.OffsetAccountType;

        ledgerJournalTransRE.OffsetLedgerDimension = _ledgerJournalTrans.OffsetLedgerDimension;

        ledgerJournalTransRE.SettleVoucher      = _ledgerJournalTrans.SettleVoucher ;

        ledgerJournalTransRE.CurrencyCode       = _ledgerJournalTrans.CurrencyCode;

        ledgerJournalTransRE.PaymReference      = _ledgerJournalTrans.PaymReference;

        ledgerJournalTransRE.Txt                = _transactionTxt ? _transactionTxt : _ledgerJournalTrans.Txt;

        ledgerJournalTransRE.OffsetTxt          = _transactionTxt ? _transactionTxt : _ledgerJournalTrans.OffsetTxt;

        ledgerJournalTransRE.ExchRate           = _ledgerJournalTrans.ExchRate;

        ledgerJournalTransRE.ReportingCurrencyExchRate = _ledgerJournalTrans.ReportingCurrencyExchRate;

        ledgerJournalTransRE.ReasonRefRecID = _ledgerJournalTrans.ReasonRefRecID;

        ledgerJournalTransRE.Approved = _ledgerJournalTrans.Approved;

        ledgerJournalTransRE.ExchRateSecond = _ledgerJournalTrans.ExchRateSecond;


        if (_ledgerJournalTrans.AmountCurCredit)

        {

            ledgerJournalTransRE.AmountCurDebit = _ledgerJournalTrans.AmountCurCredit;

        }

        else

        {

            ledgerJournalTransRE.AmountCurCredit = _ledgerJournalTrans.AmountCurDebit;

        }


        ledgerJournalTransRE.Qty                = -_ledgerJournalTrans.Qty;


        ledgerJournalTransRE.PostingProfile     = _ledgerJournalTrans.PostingProfile;

        ledgerJournalTransRE.PaymMode           = _ledgerJournalTrans.PaymMode;

        ledgerJournalTransRE.Payment            = _ledgerJournalTrans.Payment;

        ledgerJournalTransRE.Company            = _ledgerJournalTrans.Company;

        ledgerJournalTransRE.OffsetCompany      = _ledgerJournalTrans.OffsetCompany;

        ledgerJournalTransRE.DefaultDimension   = _ledgerJournalTrans.DefaultDimension;

        ledgerJournalTransRE.OffsetDefaultDimension = _ledgerJournalTrans.OffsetDefaultDimension;

        ledgerJournalTransRE.TransactionType    = _ledgerJournalTrans.TransactionType;

        ledgerJournalTransRE.DocumentDate       = _ledgerJournalTrans.DocumentDate;

        ledgerJournalTransRE.DocumentNum        = _ledgerJournalTrans.DocumentNum;

        ledgerJournalTransRE.Invoice            = _ledgerJournalTrans.Invoice;


        return ledgerJournalTransRE;

    }


    /// <summary>

    ///    Updates fields on the reversing entry <c>LedgerJournalTrans</c> record that need to be updated after the initial insert of the record.

    /// </summary>

    /// <param name="_ledgerJournalTrans">

    ///    The <c>LedgerJournalTrans</c> record for which to base field values on the updated version of the reversing entry

    ///    <c>LedgerJournalTrans</c> record.

    /// </param>

    /// <param name = "_ledgerJournalTransRE">

    ///     The <c>LedgerJournalTrans</c> record for the reversing entry.

    /// </param>

    protected void populateReverseEntryJournalLineForUpdate(

        LedgerJournalTrans _ledgerJournalTrans,

        LedgerJournalTrans _ledgerJournalTransRE)

    {

        // need to set the tax codes after the insert because the taxes are calculated during the insert if the tax codes are set.

        _ledgerJournalTransRE.TaxCode            = _ledgerJournalTrans.TaxCode;

        _ledgerJournalTransRE.TaxGroup           = _ledgerJournalTrans.TaxGroup;

        _ledgerJournalTransRE.TaxItemGroup       = _ledgerJournalTrans.TaxItemGroup;

    }