Thủ tục lưu trữ trung tâm để thực hiện trong bối cảnh gọi cơ sở dữ liệu


17

Tôi đang làm việc trên một giải pháp bảo trì tùy chỉnh bằng cách sử dụng sys.dm_db_index_physical_statschế độ xem. Tôi hiện đang có nó được tham chiếu từ một thủ tục được lưu trữ. Bây giờ khi quy trình được lưu trữ đó chạy trên một trong các cơ sở dữ liệu của tôi, nó thực hiện những gì tôi muốn nó làm và kéo xuống một danh sách tất cả các bản ghi liên quan đến bất kỳ cơ sở dữ liệu nào. Khi tôi đặt nó trên một cơ sở dữ liệu khác, nó sẽ kéo xuống một danh sách tất cả các bản ghi liên quan đến chỉ DB đó.

Ví dụ: (mã ở dưới cùng):

  • Truy vấn chạy với Cơ sở dữ liệu 6 hiển thị thông tin [được yêu cầu] cho cơ sở dữ liệu 1-10.
  • Truy vấn chạy với Cơ sở dữ liệu 3 hiển thị thông tin [được yêu cầu] chỉ cho cơ sở dữ liệu 3.

Lý do tôi muốn quy trình này cụ thể trên cơ sở dữ liệu ba là vì tôi muốn giữ tất cả các đối tượng bảo trì trong cùng một cơ sở dữ liệu. Tôi muốn có công việc này ngồi trong cơ sở dữ liệu bảo trì và làm việc như thể nó nằm trong cơ sở dữ liệu ứng dụng đó.

Mã số:

ALTER PROCEDURE [dbo].[GetFragStats] 
    @databaseName   NVARCHAR(64) = NULL
    ,@tableName     NVARCHAR(64) = NULL
    ,@indexID       INT          = NULL
    ,@partNumber    INT          = NULL
    ,@Mode          NVARCHAR(64) = 'DETAILED'
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @databaseID INT, @tableID INT

    IF @databaseName IS NOT NULL
        AND @databaseName NOT IN ('tempdb','ReportServerTempDB')
    BEGIN
        SET @databaseID = DB_ID(@databaseName)
    END

    IF @tableName IS NOT NULL
    BEGIN
        SET @tableID = OBJECT_ID(@tableName)
    END

    SELECT D.name AS DatabaseName,
      T.name AS TableName,
      I.name AS IndexName,
      S.index_id AS IndexID,
      S.avg_fragmentation_in_percent AS PercentFragment,
      S.fragment_count AS TotalFrags,
      S.avg_fragment_size_in_pages AS PagesPerFrag,
      S.page_count AS NumPages,
      S.index_type_desc AS IndexType
    FROM sys.dm_db_index_physical_stats(@databaseID, @tableID, 
           @indexID, @partNumber, @Mode) AS S
    JOIN 
       sys.databases AS D ON S.database_id = D.database_id
    JOIN 
       sys.tables AS T ON S.object_id = T.object_id
    JOIN 
       sys.indexes AS I ON S.object_id = I.object_id
                        AND S.index_id = I.index_id
    WHERE 
        S.avg_fragmentation_in_percent > 10
    ORDER BY 
        DatabaseName, TableName, IndexName, PercentFragment DESC    
END
GO

4
@JoachimIsaksson có vẻ như câu hỏi là làm thế nào để có một bản sao của quy trình trong cơ sở dữ liệu bảo trì của họ, tham chiếu DMV trong các cơ sở dữ liệu khác, thay vì phải đặt một bản sao của quy trình trong mỗi cơ sở dữ liệu.
Aaron Bertrand

Xin lỗi tôi đã không rõ ràng hơn, đã nhìn chằm chằm vào điều này trong một vài ngày. Aaron là tại chỗ. Tôi muốn SP này ngồi trong cơ sở dữ liệu bảo trì của tôi với khả năng lấy dữ liệu từ khắp máy chủ. Như hiện tại, khi nó nằm trong DB bảo trì của tôi, nó chỉ lấy dữ liệu phân mảnh về chính DB bảo trì. Điều tôi bối rối là tại sao, khi tôi đặt chính xác SP này vào một cơ sở dữ liệu khác và thực thi nó một cách giống hệt nhau, liệu nó có kéo dữ liệu phân mảnh từ khắp máy chủ không? Có một cài đặt hoặc đặc quyền nào cần thay đổi để SP này hoạt động như vậy từ DB bảo trì không?

(Lưu ý rằng cách tiếp cận hiện tại của bạn bỏ qua thực tế là có thể có hai bảng có cùng tên dưới hai lược đồ khác nhau - ngoài các đề xuất trong câu trả lời của tôi, bạn có thể muốn coi tên lược đồ là một phần của đầu vào và / hoặc đầu ra.)
Aaron Bertrand

Câu trả lời:


15

Một cách sẽ là tạo một thủ tục hệ thống mastervà sau đó tạo một trình bao bọc trong cơ sở dữ liệu bảo trì của bạn. Lưu ý rằng điều này sẽ chỉ hoạt động cho một cơ sở dữ liệu tại một thời điểm.

Đầu tiên, trong chủ:

USE [master];
GO
CREATE PROCEDURE dbo.sp_GetFragStats -- sp_prefix required
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  SET NOCOUNT ON;

  SELECT
    DatabaseName    = DB_NAME(),
    TableName       = t.name,
    IndexName       = i.name,
    IndexID         = s.index_id,
    PercentFragment = s.avg_fragmentation_in_percent,
    TotalFrags      = s.fragment_count,
    PagesPerFrag    = s.avg_fragment_size_in_pages,
    NumPages        = s.page_count,
    IndexType       = s.index_type_desc
    -- shouldn't s.partition_number be part of the output as well?
  FROM sys.tables AS t
  INNER JOIN sys.indexes AS i
    ON t.[object_id] = i.[object_id]
    AND i.index_id = COALESCE(@indexID, i.index_id)
    AND t.name = COALESCE(@tableName, t.name)
  CROSS APPLY
    sys.dm_db_index_physical_stats(DB_ID(), t.[object_id], 
      i.index_id, @partNumber, @Mode) AS s
  WHERE s.avg_fragmentation_in_percent > 10
  -- probably also want to filter on minimum page count too
  -- do you really care about a table that has 100 pages?
  ORDER BY 
    DatabaseName, TableName, IndexName, PercentFragment DESC;
END
GO
-- needs to be marked as a system object:
EXEC sp_MS_MarkSystemObject N'dbo.sp_GetFragStats';
GO

Bây giờ, trong cơ sở dữ liệu bảo trì của bạn, hãy tạo một trình bao bọc sử dụng SQL động để đặt bối cảnh chính xác:

USE YourMaintenanceDatabase;
GO
CREATE PROCEDURE dbo.GetFragStats
  @DatabaseName SYSNAME,      -- can't really be NULL, right?
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  DECLARE @sql NVARCHAR(MAX);

  SET @sql = N'USE ' + QUOTENAME(@DatabaseName) + ';
    EXEC dbo.sp_GetFragStats @tableName, @indexID, @partNumber, @Mode;';

  EXEC sp_executesql 
    @sql,
    N'@tableName NVARCHAR(128),@indexID INT,@partNumber INT,@Mode NVARCHAR(20)',
    @tableName, @indexID, @partNumber, @Mode;
END
GO

(Lý do tên cơ sở dữ liệu thực sự không thể NULLlà vì bạn không thể tham gia vào những thứ như sys.objectssys.indexesvì chúng tồn tại độc lập trong mỗi cơ sở dữ liệu. Vì vậy, có thể có một quy trình khác nếu bạn muốn thông tin trên toàn thể hiện.)

Bây giờ bạn có thể gọi nó cho bất kỳ cơ sở dữ liệu khác, ví dụ

EXEC YourMaintenanceDatabase.dbo.GetFragStats 
  @DatabaseName = N'AdventureWorks2012',
  @TableName    = N'SalesOrderHeader';

Và bạn luôn có thể tạo một synonymcơ sở dữ liệu trong mỗi cơ sở dữ liệu để bạn thậm chí không phải tham chiếu tên của cơ sở dữ liệu bảo trì:

USE SomeOtherDatabase;`enter code here`
GO
CREATE SYNONYM dbo.GetFragStats FOR YourMaintenanceDatabase.dbo.GetFragStats;

Một cách khác là sử dụng SQL động, tuy nhiên, điều này cũng sẽ chỉ hoạt động cho một cơ sở dữ liệu tại một thời điểm:

USE YourMaintenanceDatabase;
GO
CREATE PROCEDURE dbo.GetFragStats
  @DatabaseName SYSNAME,
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @sql NVARCHAR(MAX) = N'SELECT
    DatabaseName    = @DatabaseName,
    TableName       = t.name,
    IndexName       = i.name,
    IndexID         = s.index_id,
    PercentFragment = s.avg_fragmentation_in_percent,
    TotalFrags      = s.fragment_count,
    PagesPerFrag    = s.avg_fragment_size_in_pages,
    NumPages        = s.page_count,
    IndexType       = s.index_type_desc
  FROM ' + QUOTENAME(@DatabaseName) + '.sys.tables AS t
  INNER JOIN ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS i
    ON t.[object_id] = i.[object_id]
    AND i.index_id = COALESCE(@indexID, i.index_id)
    AND t.name = COALESCE(@tableName, t.name)
  CROSS APPLY
    ' + QUOTENAME(@DatabaseName) + '.sys.dm_db_index_physical_stats(
        DB_ID(@DatabaseName), t.[object_id], i.index_id, @partNumber, @Mode) AS s
  WHERE s.avg_fragmentation_in_percent > 10
  ORDER BY 
    DatabaseName, TableName, IndexName, PercentFragment DESC;';

  EXEC sp_executesql @sql, 
    N'@DatabaseName SYSNAME, @tableName NVARCHAR(128), @indexID INT,
      @partNumber INT, @Mode NVARCHAR(20)',
    @DatabaseName, @tableName, @indexID, @partNumber, @Mode;
END
GO

Tuy nhiên, một cách khác là tạo một khung nhìn (hoặc hàm có giá trị bảng) để hợp nhất tên bảng và chỉ mục của tất cả các cơ sở dữ liệu của bạn, tuy nhiên bạn phải mã hóa tên cơ sở dữ liệu vào dạng xem và duy trì chúng khi bạn thêm / xóa cơ sở dữ liệu mà bạn muốn cho phép được bao gồm trong truy vấn này. Điều này sẽ, không giống như những cái khác, cho phép bạn truy xuất số liệu thống kê cho nhiều cơ sở dữ liệu cùng một lúc.

Đầu tiên, quan điểm:

CREATE VIEW dbo.CertainTablesAndIndexes
AS
  SELECT 
    db = N'AdventureWorks2012',
    t.[object_id],
    [table] = t.name,
    i.index_id,
    [index] = i.name
  FROM AdventureWorks2012.sys.tables AS t
  INNER JOIN AdventureWorks2012.sys.indexes AS i
  ON t.[object_id] = i.[object_id]

  UNION ALL

  SELECT 
    db = N'database2',
    t.[object_id],
    [table] = t.name,
    i.index_id,
    [index] = i.name
  FROM database2.sys.tables AS t
  INNER JOIN database2.sys.indexes AS i
  ON t.[object_id] = i.[object_id]

  -- ... UNION ALL ...
  ;
GO

Sau đó làm thủ tục:

CREATE PROCEDURE dbo.GetFragStats
  @DatabaseName NVARCHAR(128) = NULL,
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  SET NOCOUNT ON;

  SELECT
    DatabaseName    = DB_NAME(s.database_id),
    TableName       = v.[table],
    IndexName       = v.[index],
    IndexID         = s.index_id,
    PercentFragment = s.avg_fragmentation_in_percent,
    TotalFrags      = s.fragment_count,
    PagesPerFrag    = s.avg_fragment_size_in_pages,
    NumPages        = s.page_count,
    IndexType       = s.index_type_desc
  FROM dbo.CertainTablesAndIndexes AS v
  CROSS APPLY sys.dm_db_index_physical_stats
    (DB_ID(v.db), v.[object_id], v.index_id, @partNumber, @Mode) AS s
  WHERE s.avg_fragmentation_in_percent > 10
    AND v.index_id = COALESCE(@indexID, v.index_id)
    AND v.[table] = COALESCE(@tableName, v.[table])
    AND v.db = COALESCE(@DatabaseName, v.db)
  ORDER BY 
    DatabaseName, TableName, IndexName, PercentFragment DESC;
END
GO

15

Vâng, có tin xấu, tin tốt với một nắm bắt, và một số tin tức thực sự tốt.

Các tin xấu

Các đối tượng T-SQL thực thi trong cơ sở dữ liệu nơi chúng cư trú. Có hai trường hợp ngoại lệ (không hữu ích):

  1. các thủ tục được lưu trữ với các tên có tiền tố sp_và tồn tại trong [master]cơ sở dữ liệu (không phải là một tùy chọn tuyệt vời: mỗi lần một DB, thêm một cái gì đó vào [master], có thể thêm Từ đồng nghĩa vào mỗi DB, phải được thực hiện cho mỗi DB mới)
  2. thủ tục lưu trữ tạm thời - cục bộ và toàn cầu (không phải là một lựa chọn thực tế vì chúng phải được tạo ra mỗi lần và để lại cho bạn những vấn đề tương tự mà bạn gặp phải với sp_Proc được lưu trữ [master].

Tin tốt (với một nắm bắt)

Nhiều người (có lẽ là hầu hết?) Nhận thức được các hàm dựng sẵn để có được một số dữ liệu meta thực sự phổ biến:

Việc sử dụng các hàm này có thể loại bỏ sự cần thiết của THAM GIA sys.databases(mặc dù điều này không thực sự là vấn đề), sys.objects(ưu tiên sys.tablesloại trừ Chế độ xem được lập chỉ mục) và sys.schemas(bạn đã thiếu cái đó và không phải mọi thứ đều nằm trong dbolược đồ ;-). Nhưng ngay cả khi loại bỏ ba trong số bốn THAM GIA, chúng ta vẫn hoạt động cùng một chỗ, phải không? Sai-o!

Một trong những tính năng hay của chức năng OBJECT_NAME()OBJECT_SCHEMA_NAME()là chúng có tham số thứ hai tùy chọn cho @database_id. Có nghĩa là, trong khi THAM GIA vào các bảng đó (ngoại trừ sys.databases) là dành riêng cho cơ sở dữ liệu, sử dụng các hàm này sẽ cung cấp cho bạn thông tin trên toàn máy chủ. Ngay cả OBJECT_ID () cũng cho phép thông tin trên toàn máy chủ bằng cách đặt cho nó một tên đối tượng đủ điều kiện.

Bằng cách kết hợp các hàm dữ liệu meta này vào truy vấn chính, chúng ta có thể đơn giản hóa đồng thời mở rộng ra ngoài cơ sở dữ liệu hiện tại. Bước đầu tiên của việc tái cấu trúc truy vấn cho chúng ta:

SELECT  DB_NAME(stat.database_id) AS [DatabaseName],
        OBJECT_SCHEMA_NAME(stat.[object_id], stat.database_id) AS [SchemaName],
        OBJECT_NAME(stat.[object_id], stat.database_id) AS [TableName],
        ind.name AS [IndexName],
        stat.index_id AS [IndexID],
        stat.avg_fragmentation_in_percent AS [PercentFragment],
        stat.fragment_count AS [TotalFrags],
        stat.avg_fragment_size_in_pages AS [PagesPerFrag],
        stat.page_count AS [NumPages],
        stat.index_type_desc AS [IndexType]
FROM sys.dm_db_index_physical_stats(@DatabaseID, @TableID, 
        @IndexID, @PartitionNumber, @Mode) stat
INNER JOIN sys.indexes ind
        ON ind.[object_id] = stat.[object_id]
       AND ind.[index_id] = stat.[index_id]
WHERE stat.avg_fragmentation_in_percent > 10
ORDER BY DatabaseName, TableName, IndexName, PercentFragment DESC;

Và bây giờ đối với "bắt": không có chức năng siêu dữ liệu để lấy tên Index, chứ đừng nói đến một máy chủ toàn máy chủ. Vậy có phải vậy không? Có phải chúng ta đã hoàn thành 90% và vẫn bị mắc kẹt cần phải ở trong một cơ sở dữ liệu cụ thể để lấy sys.indexesdữ liệu? Chúng ta có thực sự cần phải tạo một thủ tục được lưu trữ để sử dụng SQL động để cư trú không, mỗi lần Proc chính của chúng ta chạy, một bảng tạm thời của tất cả sys.indexescác mục trên tất cả các cơ sở dữ liệu để chúng ta có thể THAM GIA với nó? KHÔNG!

Tin tốt

Vì vậy, cùng với một tính năng nhỏ mà một số người thích ghét, nhưng khi được sử dụng đúng cách, có thể làm một số điều tuyệt vời. Đúng: SQLCLR. Tại sao? Bởi vì các hàm SQLCLR rõ ràng có thể gửi các câu lệnh SQL, nhưng theo bản chất của việc gửi từ mã ứng dụng, đó SQL động. Vì vậy, không giống như các hàm T-SQL, các hàm SQLCLR có thể đưa tên cơ sở dữ liệu vào truy vấn trước khi thực hiện nó. Có nghĩa là, chúng ta có thể tạo chức năng của riêng mình để phản ánh khả năng OBJECT_NAME()OBJECT_SCHEMA_NAME()nhận database_idvà lấy thông tin cho cơ sở dữ liệu đó.

Các mã sau đây là chức năng đó. Nhưng nó cần một tên cơ sở dữ liệu thay vì ID để không cần thực hiện thêm bước tìm kiếm (điều này làm cho nó ít phức tạp hơn và nhanh hơn một chút).

public class MetaDataFunctions
{
    [return: SqlFacet(MaxSize = 128)]
    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true,
        SystemDataAccess = SystemDataAccessKind.Read)]
    public static SqlString IndexName([SqlFacet(MaxSize = 128)] SqlString DatabaseName,
        SqlInt32 ObjectID, SqlInt32 IndexID)
    {
        string _IndexName = @"<unknown>";

        using (SqlConnection _Connection =
                                    new SqlConnection("Context Connection = true;"))
        {
            using (SqlCommand _Command = _Connection.CreateCommand())
            {
                _Command.CommandText = @"
SELECT @IndexName = si.[name]
FROM   [" + DatabaseName.Value + @"].[sys].[indexes] si
WHERE  si.[object_id] = @ObjectID
AND    si.[index_id] = @IndexID;
";

                SqlParameter _ParamObjectID = new SqlParameter("@ObjectID",
                                               SqlDbType.Int);
                _ParamObjectID.Value = ObjectID.Value;
                _Command.Parameters.Add(_ParamObjectID);

               SqlParameter _ParamIndexID = new SqlParameter("@IndexID", SqlDbType.Int);
                _ParamIndexID.Value = IndexID.Value;
                _Command.Parameters.Add(_ParamIndexID);

                SqlParameter _ParamIndexName = new SqlParameter("@IndexName",
                                                  SqlDbType.NVarChar, 128);
                _ParamIndexName.Direction = ParameterDirection.Output;
                _Command.Parameters.Add(_ParamIndexName);

                _Connection.Open();
                _Command.ExecuteNonQuery();

                if (_ParamIndexName.Value != DBNull.Value)
                {
                    _IndexName = (string)_ParamIndexName.Value;
                }
            }
        }

        return _IndexName;
    }
}

Nếu bạn sẽ chú ý, chúng tôi đang sử dụng Kết nối bối cảnh, không chỉ nhanh mà còn hoạt động trong SAFELắp ráp. Đúng, điều này hoạt động trong một hội được đánh dấu làSAFE, do đó, nó (hoặc các biến thể của nó) thậm chí sẽ hoạt động trên Azure SQL Database V12 (hỗ trợ cho SQLCLR đã bị xóa, khá đột ngột, từ Cơ sở dữ liệu SQL Azure vào tháng 4 năm 2016) .

Vì vậy, tái cấu trúc vượt qua lần thứ hai của truy vấn chính cho chúng ta như sau:

SELECT  DB_NAME(stat.database_id) AS [DatabaseName],
        OBJECT_SCHEMA_NAME(stat.[object_id], stat.database_id) AS [SchemaName],
        OBJECT_NAME(stat.[object_id], stat.database_id) AS [TableName],
        dbo.IndexName(DB_NAME(stat.database_id), stat.[object_id], stat.[index_id])
                     AS [IndexName],
        stat.index_id AS [IndexID],
        stat.avg_fragmentation_in_percent AS [PercentFragment],
        stat.fragment_count AS [TotalFrags],
        stat.avg_fragment_size_in_pages AS [PagesPerFrag],
        stat.page_count AS [NumPages],
        stat.index_type_desc AS [IndexType]
FROM sys.dm_db_index_physical_stats(@DatabaseID, @TableID, 
        @IndexID, @PartitionNumber, @Mode) stat
WHERE stat.avg_fragmentation_in_percent > 10
ORDER BY DatabaseName, TableName, IndexName, PercentFragment DESC;

Đó là nó! Cả UDF vô hướng SQLCLR này và Quy trình lưu trữ T-SQL bảo trì của bạn có thể sống trong cùng một [maintenance]cơ sở dữ liệu tập trung . VÀ, bạn không phải xử lý một cơ sở dữ liệu cùng một lúc; bây giờ bạn có các hàm siêu dữ liệu cho tất cả thông tin phụ thuộc trên toàn máy chủ.

PS Không có .IsNullkiểm tra các tham số đầu vào trong mã C # do đối tượng trình bao bọc T-SQL sẽ được tạo bằng WITH RETURNS NULL ON NULL INPUTtùy chọn:

CREATE FUNCTION [dbo].[IndexName]
                   (@DatabaseName [nvarchar](128), @ObjectID [int], @IndexID [int])
RETURNS [nvarchar](128) WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
AS EXTERNAL NAME [{AssemblyName}].[MetaDataFunctions].[IndexName];

Ghi chú bổ sung:

  • Phương pháp được mô tả ở đây cũng có thể được sử dụng để giải quyết các vấn đề khác, rất giống với các hàm dữ liệu meta cơ sở dữ liệu chéo bị thiếu. Đề xuất Microsoft Connect sau đây là một ví dụ về một trường hợp như vậy. Và, thấy rằng Microsoft đã đóng nó dưới dạng "Không sửa chữa", rõ ràng họ không quan tâm đến việc cung cấp các chức năng tích hợp sẵn OBJECT_NAME()để đáp ứng nhu cầu này (do đó, Giải pháp được đăng trên Đề xuất đó :-).

    Thêm chức năng siêu dữ liệu để lấy tên đối tượng từ hobt_id

  • Để tìm hiểu thêm về cách sử dụng SQLCLR, vui lòng xem loạt Stairway to SQLCLR tôi đang viết trên SQL Server Central (yêu cầu đăng ký miễn phí; xin lỗi, tôi không kiểm soát các chính sách của trang web đó).

  • Hàm IndexName()SQLCLR được hiển thị ở trên có sẵn, được biên dịch sẵn, trong một tập lệnh dễ cài đặt trên Pastebin. Tập lệnh cho phép tính năng "Tích hợp CLR" nếu nó chưa được bật và Hội được đánh dấu là SAFE. Nó được biên dịch dựa trên .NET Framework phiên bản 2.0 để nó sẽ hoạt động trong SQL Server 2005 và mới hơn (tức là tất cả các phiên bản hỗ trợ SQLCLR).

    Hàm siêu dữ liệu SQLCLR cho cơ sở dữ liệu chéo IndexName ()

  • Nếu bất cứ ai quan tâm đến IndexName()hàm SQLCLR hơn 320 hàm khác và các thủ tục được lưu trữ, thì nó có sẵn trong thư viện SQL # (mà tôi là tác giả của). Xin lưu ý rằng mặc dù có phiên bản Miễn phí, chức năng Sys_IndexName chỉ khả dụng trong phiên bản Đầy đủ (cùng với chức năng Sys_AssuggingName tương tự ).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.