Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Tách chuỗi:Giờ đây với ít T-SQL hơn

Một số cuộc thảo luận thú vị luôn phát triển xung quanh chủ đề tách chuỗi. Trong hai bài đăng trên blog trước đó, "Tách chuỗi đúng cách - hoặc cách tốt nhất tiếp theo" và "Tách chuỗi:Một bước theo dõi", tôi hy vọng tôi đã chứng minh rằng việc theo đuổi chức năng tách T-SQL "hoạt động tốt nhất" là không có kết quả . Khi việc chia nhỏ thực sự cần thiết, CLR luôn thắng và lựa chọn tốt nhất tiếp theo có thể thay đổi tùy thuộc vào nhiệm vụ thực tế hiện tại. Nhưng trong những bài viết đó, tôi đã gợi ý rằng ngay từ đầu, việc tách ở phía cơ sở dữ liệu có thể không cần thiết.

SQL Server 2008 đã giới thiệu các tham số giá trị bảng, một cách để chuyển một "bảng" từ một ứng dụng sang một thủ tục được lưu trữ mà không cần phải xây dựng và phân tích cú pháp một chuỗi, tuần tự hóa thành XML hoặc xử lý bất kỳ phương pháp phân tách nào trong số này. Vì vậy, tôi nghĩ tôi sẽ kiểm tra xem phương pháp này so với phương pháp chiến thắng trong các thử nghiệm trước đó của chúng tôi như thế nào - vì nó có thể là một lựa chọn khả thi, cho dù bạn có thể sử dụng CLR hay không. (Để biết kinh thánh cuối cùng về TVP, vui lòng xem bài viết toàn diện về SQL Server MVP Erland Sommarskog của đồng nghiệp.)

Các bài kiểm tra

Đối với thử nghiệm này, tôi sẽ giả sử chúng ta đang xử lý một tập hợp các chuỗi phiên bản. Hãy tưởng tượng một ứng dụng C # chuyển trong một tập hợp các chuỗi này (giả sử đã được thu thập từ một nhóm người dùng) và chúng ta cần đối sánh các phiên bản với một bảng (giả sử, bảng này cho biết các bản phát hành dịch vụ có thể áp dụng cho một nhóm cụ thể của các phiên bản). Rõ ràng là một ứng dụng thực sẽ có nhiều cột hơn thế này, nhưng chỉ để tạo một số khối lượng và vẫn giữ cho bảng mỏng (tôi cũng sử dụng NVARCHAR trong suốt vì đó là những gì hàm tách CLR thực hiện và tôi muốn loại bỏ bất kỳ sự mơ hồ nào do chuyển đổi ngầm) :

CREATE TABLE dbo.VersionStrings(left_post NVARCHAR(5), right_post NVARCHAR(5));
 
CREATE CLUSTERED INDEX x ON dbo.VersionStrings(left_post, right_post);
 
;WITH x AS 
(
  SELECT lp = CONVERT(DECIMAL(4,3), RIGHT(RTRIM(s1.[object_id]), 3)/1000.0)
  FROM sys.all_objects AS s1 
  CROSS JOIN sys.all_objects AS s2
)
INSERT dbo.VersionStrings
(
  left_post, right_post
)
SELECT 
  lp - CASE WHEN lp >= 0.9 THEN 0.1 ELSE 0 END, 
  lp + (0.1 * CASE WHEN lp >= 0.9 THEN -1 ELSE 1 END)
FROM x;

Bây giờ dữ liệu đã có, điều tiếp theo chúng ta cần làm là tạo một loại bảng do người dùng xác định có thể chứa một tập hợp các chuỗi. Loại bảng ban đầu để giữ chuỗi này khá đơn giản:

CREATE TYPE dbo.VersionStringsTVP AS TABLE (VersionString NVARCHAR(5));

Sau đó, chúng ta cần một vài thủ tục được lưu trữ để chấp nhận danh sách từ C #. Để đơn giản, một lần nữa, chúng tôi sẽ chỉ tính số lượng để có thể chắc chắn thực hiện quét hoàn chỉnh và chúng tôi sẽ bỏ qua số lượng trong ứng dụng:

CREATE PROCEDURE dbo.SplitTest_UsingCLR
  @list NVARCHAR(MAX)
AS
BEGIN
  SET NOCOUNT ON;
 
  SELECT c = COUNT(*) 
    FROM dbo.VersionStrings AS v
    INNER JOIN dbo.SplitStrings_CLR(@list, N',') AS s
    ON s.Item BETWEEN v.left_post AND v.right_post;
END
GO
 
CREATE PROCEDURE dbo.SplitTest_UsingTVP
  @list dbo.VersionStringsTVP READONLY
AS
BEGIN
  SET NOCOUNT ON;
 
  SELECT c = COUNT(*) 
    FROM dbo.VersionStrings AS v
    INNER JOIN @list AS l
    ON l.VersionString BETWEEN v.left_post AND v.right_post;
END
GO

Lưu ý rằng TVP được chuyển vào một thủ tục đã lưu trữ phải được đánh dấu là ĐÃ SN SÀNG - hiện không có cách nào để thực hiện DML trên dữ liệu giống như cách bạn làm đối với biến bảng hoặc bảng tạm thời. Tuy nhiên, Erland đã gửi một yêu cầu rất phổ biến rằng Microsoft phải làm cho các tham số này linh hoạt hơn (và có nhiều thông tin chi tiết hơn đằng sau lập luận của ông tại đây).

Cái hay ở đây là SQL Server không còn phải xử lý việc chia nhỏ một chuỗi - không phải trong T-SQL cũng như không chuyển nó cho CLR - vì nó đã ở trong một cấu trúc được thiết lập vượt trội.

Tiếp theo, một ứng dụng bảng điều khiển C # thực hiện những việc sau:

  • Chấp nhận một số làm đối số để cho biết có bao nhiêu phần tử chuỗi nên được xác định
  • Tạo một chuỗi CSV của các phần tử đó, sử dụng StringBuilder, để chuyển đến thủ tục được lưu trữ CLR
  • Tạo một DataTable với các phần tử giống nhau để chuyển đến quy trình được lưu trữ TVP
  • Đồng thời kiểm tra chi phí chuyển đổi chuỗi CSV thành DataTable và ngược lại trước khi gọi các thủ tục được lưu trữ thích hợp

Mã cho ứng dụng C # được tìm thấy ở cuối bài viết. Tôi có thể đánh vần C #, nhưng tôi không phải là một guru; Tôi chắc chắn rằng có những điểm không hiệu quả mà bạn có thể phát hiện ra ở đó có thể làm cho mã hoạt động tốt hơn một chút. Nhưng bất kỳ thay đổi nào như vậy sẽ ảnh hưởng đến toàn bộ tập hợp các thử nghiệm theo cách tương tự.

Tôi đã chạy ứng dụng 10 lần bằng cách sử dụng các phần tử 100, 1.000, 2.500 và 5.000. Kết quả như sau (điều này cho thấy thời lượng trung bình, tính bằng giây, trên 10 bài kiểm tra):

Bên cạnh hiệu suất…

Ngoài sự khác biệt rõ ràng về hiệu suất, TVP có một lợi thế khác - các loại bảng triển khai đơn giản hơn nhiều so với các cụm CLR, đặc biệt là trong các môi trường mà CLR đã bị cấm vì các lý do khác. Tôi hy vọng rằng các rào cản đối với CLR đang dần biến mất và các công cụ mới đang làm cho việc triển khai và bảo trì ít khó khăn hơn, nhưng tôi nghi ngờ việc dễ dàng triển khai ban đầu cho CLR sẽ dễ dàng hơn các phương pháp tiếp cận gốc.

Mặt khác, ngoài hạn chế chỉ đọc, các loại bảng giống như các loại bí danh ở chỗ chúng rất khó sửa đổi sau khi thực tế. Nếu bạn muốn thay đổi kích thước của một cột hoặc thêm một cột, không có lệnh ALTER TYPE và để DROP loại và tạo lại nó, trước tiên bạn phải xóa tham chiếu đến loại khỏi tất cả các thủ tục đang sử dụng nó . Vì vậy, ví dụ trong trường hợp trên nếu chúng ta cần tăng cột Chuỗi phiên bản lên NVARCHAR (32), chúng ta phải tạo một kiểu giả và thay đổi thủ tục được lưu trữ (và bất kỳ thủ tục nào khác đang sử dụng nó):

CREATE TYPE dbo.VersionStringsTVPCopy AS TABLE (VersionString NVARCHAR(32));
GO
 
ALTER PROCEDURE dbo.SplitTest_UsingTVP
  @list dbo.VersionStringsTVPCopy READONLY
AS
...
GO
 
DROP TYPE dbo.VersionStringsTVP;
GO
 
CREATE TYPE dbo.VersionStringsTVP AS TABLE (VersionString NVARCHAR(32));
GO
 
ALTER PROCEDURE dbo.SplitTest_UsingTVP
  @list dbo.VersionStringsTVP READONLY
AS
...
GO
 
DROP TYPE dbo.VersionStringsTVPCopy;
GO

(Hoặc cách khác, bỏ thủ tục, bỏ loại, tạo lại loại và tạo lại thủ tục.)

Kết luận

Phương pháp TVP luôn hoạt động tốt hơn phương pháp tách CLR và tỷ lệ phần trăm lớn hơn khi số lượng phần tử tăng lên. Ngay cả việc thêm chi phí chuyển đổi chuỗi CSV hiện có thành DataTable cũng mang lại hiệu suất end-to-end tốt hơn nhiều. Vì vậy, tôi hy vọng rằng, nếu tôi chưa thuyết phục bạn từ bỏ các kỹ thuật tách chuỗi T-SQL để ủng hộ CLR, tôi đã khuyến khích bạn cung cấp cho các tham số có giá trị bảng. Sẽ dễ dàng kiểm tra ngay cả khi bạn hiện không sử dụng DataTable (hoặc một số loại tương đương).

Mã C # được sử dụng cho các bài kiểm tra này

Như tôi đã nói, tôi không phải là C # guru, vì vậy có lẽ có rất nhiều điều tôi đang làm ở đây khá ngây ngô, nhưng phương pháp luận phải khá rõ ràng.

using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Collections;
 
namespace SplitTester
{
  class SplitTester
  {
    static void Main(string[] args)
    {
      DataTable dt_pure = new DataTable();
      dt_pure.Columns.Add("Item", typeof(string));
 
      StringBuilder sb_pure = new StringBuilder();
      Random r = new Random();
 
      for (int i = 1; i <= Int32.Parse(args[0]); i++)
      {
        String x = r.NextDouble().ToString().Substring(0,5);
        sb_pure.Append(x).Append(",");
        dt_pure.Rows.Add(x);
      }
 
      using 
      ( 
          SqlConnection conn = new SqlConnection(@"Data Source=.;
          Trusted_Connection=yes;Initial Catalog=Splitter")
      )
      {
        conn.Open();
 
        // four cases:
        // (1) pass CSV string directly to CLR split procedure
        // (2) pass DataTable directly to TVP procedure
        // (3) serialize CSV string from DataTable and pass CSV to CLR procedure
        // (4) populate DataTable from CSV string and pass DataTable to TCP procedure
 
 
 
        // ********** (1) ********** //
 
        write(Environment.NewLine + "Starting (1)");
 
        SqlCommand c1 = new SqlCommand("dbo.SplitTest_UsingCLR", conn);
        c1.CommandType = CommandType.StoredProcedure;
        c1.Parameters.AddWithValue("@list", sb_pure.ToString());
        c1.ExecuteNonQuery();
        c1.Dispose();
 
        write("Finished (1)");
 
 
 
        // ********** (2) ********** //
 
        write(Environment.NewLine + "Starting (2)");
 
        SqlCommand c2 = new SqlCommand("dbo.SplitTest_UsingTVP", conn);
        c2.CommandType = CommandType.StoredProcedure;
        SqlParameter tvp1 = c2.Parameters.AddWithValue("@list", dt_pure);
        tvp1.SqlDbType = SqlDbType.Structured;
        c2.ExecuteNonQuery();
        c2.Dispose();
 
        write("Finished (2)");
 
 
 
        // ********** (3) ********** //
 
        write(Environment.NewLine + "Starting (3)");
 
        StringBuilder sb_fake = new StringBuilder();
        foreach (DataRow dr in dt_pure.Rows)
        {
          sb_fake.Append(dr.ItemArray[0].ToString()).Append(",");
        }
 
        SqlCommand c3 = new SqlCommand("dbo.SplitTest_UsingCLR", conn);
        c3.CommandType = CommandType.StoredProcedure;
        c3.Parameters.AddWithValue("@list", sb_fake.ToString());
        c3.ExecuteNonQuery();
        c3.Dispose();
 
        write("Finished (3)");
 
 
 
        // ********** (4) ********** //
 
        write(Environment.NewLine + "Starting (4)");
 
        DataTable dt_fake = new DataTable();
        dt_fake.Columns.Add("Item", typeof(string));
 
        string[] list = sb_pure.ToString().Split(',');
 
        for (int i = 0; i < list.Length; i++)
        {
          if (list[i].Length > 0)
          {
            dt_fake.Rows.Add(list[i]);
          }
        }
 
        SqlCommand c4 = new SqlCommand("dbo.SplitTest_UsingTVP", conn);
        c4.CommandType = CommandType.StoredProcedure;
        SqlParameter tvp2 = c4.Parameters.AddWithValue("@list", dt_fake);
        tvp2.SqlDbType = SqlDbType.Structured;
        c4.ExecuteNonQuery();
        c4.Dispose();
 
        write("Finished (4)");
      }
    }
 
    static void write(string msg)
    {
      Console.WriteLine(msg + ": " 
        + DateTime.UtcNow.ToString("HH:mm:ss.fffff"));
    }
  }
}

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Mức lương cao thứ n

  2. Phân tích cái chết của một nghìn cắt giảm khối lượng công việc

  3. Danh pháp &Kiến trúc Sản phẩm IRI

  4. Giải pháp thử thách trình tạo chuỗi số - Phần 5

  5. Cân nhắc về hiệu suất phiên bản được quản lý của Azure SQL