Thursday, March 11, 2021

Mẫu Regular Expression check định dạng ngày tháng

(years 0-9999) 

^(\w{2})_(\w{1,5})_(?<!\d)(?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(?:(?:(?:0[13578]|1[02])31)|(?:(?:0[1,3-9]|1[0-2])(?:29|30)))|(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))0229)|(?:(?:1[6-9]|[2-9]\d)?\d{2})(?:(?:0?[1-9])|(?:1[0-2]))(?:0?[1-9]|1\d|2[0-8]))(?!\d)_(\w{2,3})(.txt)?$

(years 2000-2099)

(?<!\d)(?:(?:20\d{2})(?:(?:(?:0[13578]|1[02])31)|(?:(?:0[1,3-9]|1[0-2])(?:29|30)))|(?:(?:20(?:0[48]|[2468][048]|[13579][26]))0229)|(?:20\d{2})(?:(?:0?[1-9])|(?:1[0-2]))(?:0?[1-9]|1\d|2[0-8]))(?!\d)


Friday, June 26, 2020

Cách đọc hết data trong Stream

Tham khảo từ https://jonskeet.uk/csharp/readbinary.html

Reading binary data in C#

In the C# newsgroup, I've seen quite a lot of code for reading in data from a file like this:
// Bad code! Do not use!
FileStream fs = File.OpenRead(filename);
byte[] data = new byte[fs.Length];
fs.Read (data, 0, data.Length);
This code is far from guaranteed to work. In particular, the FileStream could be reading just the first 10 bytes of the file into the buffer. The Read method is only guaranteed to block until some data is available (or the end of the stream is reached), not until all of the data is available. That's where the return value (which is ignored in the above code) is vital. You need to cope with the case where you can't read all of the data in one go, and loop round until you've read what you want. Here's a method which you can use if you want to read from a stream into the whole of an array, not stopping until it's finished:
/// <summary>
/// Reads data into a complete array, throwing an EndOfStreamException
/// if the stream runs out of data first, or if an IOException
/// naturally occurs.
/// </summary>
/// <param name="stream">The stream to read data from</param>
/// <param name="data">The array to read bytes into. The array
/// will be completely filled from the stream, so an appropriate
/// size must be given.</param>
public static void ReadWholeArray (Stream stream, byte[] data)
{
    int offset=0;
    int remaining = data.Length;
    while (remaining > 0)
    {
        int read = stream.Read(data, offset, remaining);
        if (read <= 0)
            throw new EndOfStreamException 
                (String.Format("End of stream reached with {0} bytes left to read", remaining));
        remaining -= read;
        offset += read;
    }
}
Sometimes, you don't know the length of the stream in advance (for instance a network stream) and just want to read the whole lot into a buffer. Here's a method to do just that:
/// <summary>
/// Reads data from a stream until the end is reached. The
/// data is returned as a byte array. An IOException is
/// thrown if any of the underlying IO calls fail.
/// </summary>
/// <param name="stream">The stream to read data from</param>
public static byte[] ReadFully (Stream stream)
{
    byte[] buffer = new byte[32768];
    using (MemoryStream ms = new MemoryStream())
    {
        while (true)
        {
            int read = stream.Read (buffer, 0, buffer.Length);
            if (read <= 0)
                return ms.ToArray();
            ms.Write (buffer, 0, read);
        }
    }
}
While the above is simple, it's not terribly efficient, as it ends up copying the data at the very end, and probably several times between. Here's some code which works well if you know the expected length of data to start with. (While you could use Stream.Length, it isn't supported for all streams.)
/// <summary>
/// Reads data from a stream until the end is reached. The
/// data is returned as a byte array. An IOException is
/// thrown if any of the underlying IO calls fail.
/// </summary>
/// <param name="stream">The stream to read data from</param>
/// <param name="initialLength">The initial buffer length</param>
public static byte[] ReadFully (Stream stream, int initialLength)
{
    // If we've been passed an unhelpful initial length, just
    // use 32K.
    if (initialLength < 1)
    {
        initialLength = 32768;
    }
    
    byte[] buffer = new byte[initialLength];
    long read=0;
    
    int chunk;
    while ( (chunk = stream.Read(buffer, read, buffer.Length-read)) > 0)
    {
        read += chunk;
        
        // If we've reached the end of our buffer, check to see if there's
        // any more information
        if (read == buffer.Length)
        {
            int nextByte = stream.ReadByte();
            
            // End of stream? If so, we're done
            if (nextByte==-1)
            {
                return buffer;
            }
            
            // Nope. Resize the buffer, put in the byte we've just
            // read, and continue
            byte[] newBuffer = new byte[buffer.Length*2];
            Array.Copy(buffer, newBuffer, buffer.Length);
            newBuffer[read]=(byte)nextByte;
            buffer = newBuffer;
            read++;
        }
    }
    // Buffer is now too big. Shrink it.
    byte[] ret = new byte[read];
    Array.Copy(buffer, ret, read);
    return ret;
}
Using code such as the above, whether synchronously or asynchronously, you shouldn't come across the kinds of error that can otherwise occur, such as data which appears to be corrupted or truncated.
Note that because read here is a long, this code can theoretically cope with files larger than 2GB. In practice I believe all CLR implementations at the time of this writing will choke when asked to create such a large object, but in the future this restriction may be relaxed.

Back to the main page.

Monday, March 02, 2020

Cách đọc Excel vào DataTable và chỉ định header row từ row nào

Sử dụng config sau

using (var reader = ExcelReaderFactory.CreateReader(stream))
                    {
                        var conf = new ExcelDataSetConfiguration
                        {
                            ConfigureDataTable = _ => new ExcelDataTableConfiguration
                            {
                                UseHeaderRow = true,
                                ReadHeaderRow = rowReader => 
                                {
                                    rowReader.Read();
                                }
                            }
                        };
                        DataSet dataSet = reader.AsDataSet(conf);
                        if (dataSet == null || dataSet.Tables.Count == 0)
                        {
                            return null;
                        }
                        return dataSet.Tables[0];
                    }

Chỗ bôi đen chỉ định bỏ qua 1 dòng đầu tiên và bắt đầu xử lý đọc các dòng từ row thứ 2, UseHeaderRow = true sẽ chỉ định row số 2 là header.

Nguồn tham khảo: https://github.com/ExcelDataReader/ExcelDataReader/issues/300
https://discoverdot.net/projects/excel-data-reader

Thursday, December 12, 2019

Cách update password cho Git khi dùng SourceTree

Tìm đến thư mục C:\Users\USERNAME\AppData\Local\Atlassian\SourceTree

Xóa hoặc đổi tên file passwd.

Thoát ra SourceTree sau đó mở lại, thực hiện các action như Pull, Push, SourceTree sẽ yêu cầu nhập lại username và password. Khi đó sẽ update được.

Nguồn tham khảo:
https://stackoverflow.com/questions/45821037/how-do-i-set-my-password-in-sourcetree

Ngoài ra còn 1 cách nữa là vào Tools -> Options -> tab Authentication, xóa hết các tài khoản Git Saved Password.

Sau đó Pull lại sẽ hỏi user và pass. Nhập vào là xong


Host Web Core trên WindowsService

Tạo 1 project web hoặc web API Core.

class Program sửa thành

public class Program
    {
        public static void Main(string[] args)
        {

            CreateWebHostBuilder(args).Build().RunAsService();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseContentRoot(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName))
                .UseStartup<Startup>().
                UseUrls("http://localhost:5000");
    }

Mở file project, sửa lại giống như sau sau

<PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <RuntimeIdentifiers>win10-x64;</RuntimeIdentifiers>
    <OutputType>Exe</OutputType>
  </PropertyGroup>

Chuyển vào thư mục project, chạy lệnh sau:
dotnet publish --configuration Release --self-contained -r win10-x64

Mở cmd lần nữa để chạy lệnh cài đặt service

sc create WindowsServiceHosted binPath= "C:\WindowsServiceHosted\bin\Debug\netcoreapp2.1\win7-x64\publish\WindowsServiceHosted.exe"

Chạy sc start WindowsServiceHosted để start service.
Chạy sc description WindowsServiceHosted "This is the description of the service.." để gán mô tả cho service

http://localhost:5000 để chạy web

Thursday, June 06, 2019

Sửa lỗi tạo project bị Access Denied

Tắt VS. Tìm file privateregistry.bin trong ổ C. Xóa đi rồi khởi động lại VS

Monday, January 14, 2019

Cách tạo link custom Uri scheme để chạy app như skype

Tạo Uri custom bằng cách đăng ký registry

private static void WriteRegistry()
        {
            try
            {
                using (var key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\" + UriScheme))
                {
                    string applicationLocation = Path.Combine(System.Windows.Forms.Application.StartupPath, "TestApp.exe");

                    key.SetValue("", "URL:" + FriendlyName);
                    key.SetValue("URL Protocol", "");

                    using (var defaultIcon = key.CreateSubKey("DefaultIcon"))
                    {
                        defaultIcon.SetValue("", applicationLocation + ",1");
                    }

                    using (var commandKey = key.CreateSubKey(@"shell\open\command"))
                    {
                        commandKey.SetValue("", "\"" + applicationLocation + "\" \"%1\"");
                    }
                }
            }
            catch (Exception objEx)
            {
                ExceptionManger.WriteLog(objEx, "Exception happended in WriteRegistry.");
            }
        }

Như vậy khi mở trình duyệt gõ testapp: thì TestApp.exe sẽ chạy.

Muốn truyền thêm tham số cho TestApp thì hàm main cần nhận tham số string[] args. Sau đó xử lý tham số truyền vào trong mảng args.

Monday, November 19, 2018

Join 2 bảng chính, phụ nhưng chỉ trả về 1 dòng đơn nhất cho mỗi dòng bảng chính

Sử dụng Cross Apply

https://stackoverflow.com/questions/2043259/sql-server-how-to-join-to-first-row?rq=1

Friday, October 05, 2018

Switch In Và Switch Out Với Table Partitioning


Bài viết tham khảo tại: http://www.sqlviet.com/blog/switch-in-va-switch-out-voi-table-partitioning
Một trong những ưu điểm của phân đoạn bảng là bạn có thể dễ dàng loại bỏ một lượng lớn bản ghi ra khỏi bảng, cũng như dễ dàng nhập một lượng lớn bản ghi vào bảng, thay vì phải sử dụng các lệnh INSERT và DELETE.
Ví dụ, bạn có bảng BanHang chứa dữ liệu bán hàng qua nhiều năm. Nay nhận thấy dữ liệu của năm 2010 không còn cần dùng đến thường xuyên, bạn muốn chuyển dữ liệu này sang một bảng khác để giảm nhẹ bảng chính. Nếu không có partition, bạn phải dùng hai lệnh: INSERT vào bảng mới và DELETE trên bảng chính với điều kiện năm = 2010. Hai lệnh này đều rất tốn kém vì mỗi bản ghi được thêm/xóa đều được ghi vào transaction log. Khi bảng được phân đoạn theo năm, bạn có thể dễ dàng chuyển đoạn cho 2010 sang bảng mới. Thao tác này gọi là switch out. Vì đây là thao tác DDL, dữ liệu sẽ được di chuyển tức thì.
Một ví dụ khác, bạn làm việc ở trung tâm dữ liệu của một hội sở ngân hàng, tập hợp dữ liệu của tất cả các chi nhánh. Mỗi cuối tháng, bạn tiếp nhận dữ liệu giao dịch từ các chi nhánh và gộp chung vào một bảng giao dịch chính. Nếu không có partition, bạn cần chạy lệnh INSERT cho từng chi nhánh. Khỏi cần bàn thêm cũng thấy việc này rất tốn thời gian. Tuy nhiên, nếu bạn phân đoạn bảng chính theo chi nhánh (ví dụ phân đoạn theo mã chi nhánh), bạn có thể nạp dữ liệu của từng chi nhánh vào bảng chính thông qua thao tác switch in. Cũng giống như switch out, đây là thao tác DDL nên cần rất ít tài nguyên hệ thống và dữ liệu được nạp ngay tức thì.
Switch out
Tôi dùng lại ví dụ trong bài Table Partitioning – Các Khái Niệm Cơ Bản, và tạo một bảng phân đoạn theo giá trị của cột id, đoạn 1: id nhỏ hơn 1000; đoạn 2: id từ 1001 – 2000; đoạn 3: id từ 2001 – 3000; và đoạn 4: id từ 3001 trở lên.
CREATE DATABASE MyPartDB
GO
 
-- tạo filegroup
ALTER DATABASE MyPartDB ADD FILEGROUP FG1
ALTER DATABASE MyPartDB ADD FILEGROUP FG2
ALTER DATABASE MyPartDB ADD FILEGROUP FG3
ALTER DATABASE MyPartDB ADD FILEGROUP FG4
 
-- thêm data file vào mỗi filegroup
ALTER DATABASE MyPartDB ADD FILE (NAME = N'F1', FILENAME = N'D:\DATA\MyPartDB_F1.ndf') TO FILEGROUP FG1
ALTER DATABASE MyPartDB ADD FILE (NAME = N'F2', FILENAME = N'D:\DATA\MyPartDB_F2.ndf') TO FILEGROUP FG2
ALTER DATABASE MyPartDB ADD FILE (NAME = N'F3', FILENAME = N'D:\DATA\MyPartDB_F3.ndf') TO FILEGROUP FG3
ALTER DATABASE MyPartDB ADD FILE (NAME = N'F4', FILENAME = N'D:\DATA\MyPartDB_F4.ndf') TO FILEGROUP FG4
GO
 
USE MyPartDB
GO
 
-- tạo partition function
CREATE PARTITION FUNCTION MyPartFunc_1(INT) AS RANGE LEFT FOR VALUES (1000, 2000, 3000)
 
-- tạo partition scheme
CREATE PARTITION SCHEME MyPartScheme_1 AS PARTITION MyPartFunc_1 TO (FG1, FG2, FG3, FG4)
 
-- tạo bảng
CREATE TABLE dbo.MyTablePart(
MyID INT IDENTITY PRIMARY KEY,
MyData VARCHAR(100)
) ON MyPartScheme_1(MyID)
 
--thêm dữ liệu vào bảng
INSERT INTO dbo.MyPartTable(MyData)
SELECT DISTINCT a.name
FROM master.SYS.syscolumns a
Nay tôi muốn chuyển toàn bộ dữ liệu của đoạn 1 sang một bảng khác. Tôi tạo một bảng mới và switch out đoạn 1 sang đó:
-- tạo bảng tạm
CREATE TABLE dbo.Tbl_1000 (
MyID INT IDENTITY PRIMARY KEY,
MyData VARCHAR(100)) ON FG1
GO
 
--Switch out. Boom!
ALTER TABLE dbo.MyPartTable SWITCH PARTITION 1 TO dbo.Tbl_1000
 
-- kiểm tra kết quả
SELECT * FROM  dbo.MyPartTable
 
SELECT * FROM  dbo.Tbl_1000
Vậy là dữ liệu đã được chuyển sang bảng Tbl_1000. Ở đây có vài điểm cần lưu ý:
  • Bảng tạm phải có cấu trúc giống hệt với bảng chính.
  • Bảng tạm phải được tạo trên cùng filegroup với đoạn mà bạn sắp chuyển sang. Ở ví dụ trên tôi cần chuyển đoạn 1 và tôi biết đoạn này nằm trên filegroup FG1 (vì sao tôi lại biết điều này?), nên tôi cũng cần tạo bảng tạm trên filegroup FG1.
  • Và tuy không thể hiện trong ví dụ trên, bảng tạm phải trống trước khi switch out. Khi bảng tạm có chứa dữ liệu, SQL Server sẽ báo lỗi chứ không xóa dọn dữ liệu.
  • Một điểm nữa là các nonclustered index trên bảng chính (MyPartTable) cũng phải được phân đoạn cùng với bảng. Khi đó index được gọi là aligned. Tôi sẽ viết kỹ hơn về aligned index trong một bài tới, tuy nhiên để bạn hình dung bạn hãy chèn lệnh sau đây vào ngay sau lệnh tạo bảng Table_1000:
    CREATE UNIQUE INDEX ui_Tbl_1000 ON dbo.Tbl_1000(MyData)
    Lệnh trên tạo một non-aligned index, và SQL Server sẽ từ chối thực hiện switch out và trả về một thông báo lỗi.
Switch in
Giả sử giờ tôi muốn chuyển lại dữ liệu trong bảng Tbl_1000 vào đoạn 1 của bảng MyPartTable. Các điều kiện áp dụng đối với switch out cũng vẫn cần được thỏa mãn (đồng thời đoạn cần được chuyển vào phải trống). Ngoài ra switch in còn đòi hỏi một điều kiện nữa, đó là cột trên bảng tạm tương ứng với partition key phải có chứa ràng buộc check theo đó nó chỉ cho phép chứa dải giá trị trùng khớp với đoạn tương ứng. Vì tôi muốn chuyển dữ liệu vào đoạn 1, và tôi biết đoạn 1 chỉ chứa các giá trị <= 1000, nên tôi cần tạo một ràng buộc check tương ứng trên bảng tạm rồi mới có thể switch in:
-- tạo ràng buộc
ALTER TABLE dbo.Tbl_1000 ADD CONSTRAINT ck_id CHECK(MyID <= 1000)
 
-- Switch in. Boom!
ALTER TABLE Tbl_1000 SWITCH TO MyPartTable PARTITION 1

Phiên bản áp dụng: SQL Server 2005 trở lên

Table Partitioning – Split và Merge

Bài viết tham khảo tại: http://www.sqlviet.com/blog/table-partitioning-split-va-merge

Khi định nghĩa partition function bạn phải chỉ rõ giá trị biên của từng đoạn và theo đó, số lượng các đoạn cũng được xác định. Qua thời gian sẽ đến lúc bạn cần định nghĩa thêm các đoạn mới cho dữ liệu được thêm vào sau này. Ví dụ bạn phân đoạn bảng bán hàng theo năm của ngày giao dịch, tại thời điểm ban đầu bạn chia bảng làm 3 đoạn cho các năm 2009 trở về trước, 2010, và 2011 trở đi. Tại một thời điểm nào đó trước năm 2012 bạn cần tạo thêm đoạn mới cho năm 2012, và tương tự sau này cho 2013… Thao tác này gọi là split (chia đoạn).
Ngược lại, bạn cũng có thể muốn hợp các đoạn chứa dữ liệu lại với nhau để giảm bớt số lượng đoạn cần quản lý, hoặc đáp ứng yêu cầu thay đổi về nghiệp vụ (ví dụ Hà Tây nhập vào Hà nội). Với bảng bán hàng ở trên, có thể bạn muốn hợp hai đoạn 2009 và 2010 để gom tất cả dữ liệu cũ từ năm 2010 trở về trước vào một đoạn. Thao tác này gọi là merge (hợp đoạn). Lưu ý là partition merge khác với lệnh MERGE được đưa vào từ bản 2008.
Setup
Tôi dùng lại database trong bài Switch In Và Switch Out Với Table Partitioning và tạo thêm các đối tượng mới:
USE MyPartDB
GO
CREATE PARTITION FUNCTION MyPartFuncD(DATETIME) AS RANGE RIGHT FOR VALUES ('2009-01-01', '2010-01-01', '2011-01-01')
GO
CREATE PARTITION SCHEME MyPartSchemeD AS PARTITION MyPartFuncD TO (FG1, FG2, FG3, FG4)
GO
CREATE TABLE dbo.BanHang(
BanHang_ID INT IDENTITY,
Ngay DATETIME,
MaSP VARCHAR(10),
SoLuong INT,
ThanhTien INT,
CONSTRAINT PK_BanHang PRIMARY KEY (BanHang_ID,Ngay)
) ON MyPartSchemeD(Ngay)
GO
 
--thêm dữ liệu vào bảng
INSERT INTO dbo.BanHang(Ngay,MaSP,SoLuong,ThanhTien)
SELECT '2008-03-25 19:26:12','100',2,500000 
UNION ALL
SELECT '2009-06-12 07:14:27','200',1,300000 
UNION ALL
SELECT '2010-01-22 10:01:44','300',3,600000 
UNION ALL
SELECT '2011-10-26 12:06:42','400',1,1000000 
UNION ALL
SELECT '2012-02-17 15:32:16','500',5,400000 
GO
--kiểm tra dữ liệu lưu vào các đoạn
SELECT OBJECT_ID, index_id, partition_number, ROWS
FROM SYS.partitions WHERE OBJECT_NAME(OBJECT_ID)='BanHang'
------------------------------
object_id   partition_number   rows
229575856        1       1
229575856        2       1
229575856        3       1
229575856        4       2
Như vậy bảng được lưu trữ đúng như mong đợi: bản ghi 2008 vào đoạn 1, bản ghi 2009 vào đoạn 2, bản ghi 2010 vào đoạn 3, còn hai bản ghi 2011 và 2012 vào đoạn 4. Số bản ghi của mỗi đoạn (cột “rows”) phản ánh điều này.
Split
Nay bạn muốn tạo thêm một đoạn cho 2012, bạn cần làm theo các bước như sau:
--bước 1: tạo thêm 1 filegroup mới để chứa đoạn sắp được tạo
ALTER DATABASE MyPartDB ADD FILEGROUP FG5
GO 
ALTER DATABASE MyPartDB ADD FILE (NAME = N'F5', FILENAME = N'D:\DATA\MyPartDB_F5.ndf') TO FILEGROUP FG5
GO
--bước 2: mở rộng thêm partition scheme
ALTER PARTITION SCHEME MyPartSchemeD NEXT USED FG5
GO
--bước 3: định nghĩa thêm giá trị biên trong partition function
ALTER PARTITION FUNCTION MyPartFuncD() SPLIT RANGE ('2012-01-01')
GO
--Thêm một số bản ghi mới và kiểm tra dữ liệu lưu vào các đoạn
INSERT INTO dbo.BanHang(Ngay,MaSP,SoLuong,ThanhTien)
SELECT '2012-03-12 14:25:12','100',3,750000 
UNION ALL
SELECT '2012-06-21 07:24:12','200',1,300000 
GO
SELECT OBJECT_ID, index_id, partition_number, ROWS
FROM SYS.partitions WHERE OBJECT_NAME(OBJECT_ID)='BanHang'
object_id   partition_number   rows
229575856        1       1
229575856        2       1
229575856        3       1
229575856        4       1
229575856        5       3
Lưu ý ở bước 2, bạn yêu cầu đoạn tiếp theo sẽ sử dụng filegroup FG5 qua mệnh đề “NEXT USED FG5″. Ở bước 3, bạn tạo thêm một giá trị biên trong partition function, theo đó miền từ 2011-01-01 đến ngày tận thế được cắt tại 2012-01-01. Do đó đoạn 4 giờ được điều chỉnh để chứa dải giá trị từ 2011-01-01 đến trước 2012-01-01, và đoạn mới chứa dải từ 2012-01-01 đến ngày tận thế.
Vậy là đoạn mới đã được tạo (partition number 5), bản ghi 2012 cũ được chuyển vào đây và hai bản ghi 2012 mới cũng được lưu vào đoạn này.
Merge
Việc hợp đoạn đơn giản hơn, để hợp đoạn 2009 vào 2010 bạn chỉ cần thực hiện lệnh sau
ALTER PARTITION FUNCTION MyPartFuncD() merge RANGE ('2009-01-01')
Hiểu một cách đơn giản là xóa đi giá trị biên 2009-01-01 và do đó tất cả các giá trị từ trước đến 2010-01-01 được lưu chung vào một đoạn. Vậy đoạn nào được dùng? Trước khi hợp, đoạn 1 lưu vào filegroup FG1, đoạn 2 lưu vào filegroup FG2. Sau khi hợp, đoạn 1 và đoạn 2 được gộp chung vào thành đoạn 1. Vậy đoạn 1 mới bây giờ được lưu ở FG1 hay FG2? Xin nhường cho bạn tìm câu trả lời.
Lưu ý
Không giống như Switch in và Switch out vốn chỉ thay đổi metadata trong database, Split và Merge kéo theo sự di chuyển của dữ liệu. Các bản ghi sẽ được chuyển sang đoạn mới thích hợp với giá trị partition key của nó. Trong ví dụ split ở trên, bản ghi 2012 ban đầu ở đoạn 4 được chuyển sang đoạn 5; còn ở ví dụ merge hai đoạn 2009 và 2010, các bản ghi ở một đoạn được chuyển qua đoạn kia. Điều này có thể gây kéo dài thời gian thực hiện khi bảng có lượng bản ghi lớn.

Index Partitioning

Bài viết tham khảo tại: http://www.sqlviet.com/blog/index-partitioning

Với một bảng đã được phân đoạn, mỗi index được tạo ra theo mặc định được phân đoạn theo giống như bảng. Ví dụ bảng được phân đoạn theo năm của ngày giao dịch thì index cũng được phân đoạn theo cách đó. Index khi đó được gọi là aligned với bảng. Tuy nhiên bạn cũng có thể không phân đoạn cho index và để nó nguyên một khối, hoặc phân đoạn theo cách khác. Khi đó index được gọi là non-aligned.
Bạn hãy hình dung bảng như một cái bánh tét, và index là sợi lạt buộc xung quanh. Khi phân đoạn, chiếc bánh được cắt thành từng lát. Nếu sợi lạt cũng được cắt theo và mỗi sợi con giờ buộc xung quanh từng lát bánh, sợi lạt được gọi là aligned với chiếc bánh. Nếu sợi lạt không được cắt và vẫn bao xung quanh cả chiếc bánh to, sợi lạt được gọi là non-aligned với chiếc bánh.
SQL Server luôn ưu tiên phân đoạn index để nó aligned với bảng và luôn cố gắng phân đoạn khi có thể, vì nó đem lại nhiều thuận lợi cho các tác vụ trên bảng. Tuy nhiên khi đi vào chi tiết từng loại index, cách cư xử này được thể hiện qua những sắc thái khác nhau, ta sẽ xem xét kỹ hơn ở dưới đây.
Non-clustered Index Khi bạn tạo nonclustered index mà không chỉ định filegroup, SQL Server sẽ mặc định tạo index trên partition scheme của bảng, nghĩa là phân đoạn index cùng với bảng:
USE PartTest
GO
CREATE TABLE dbo.BanHang(
BanHang_ID INT IDENTITY,
NgayGiaoDich DATETIME,
MaSP INT,
SoLuong INT,
ThanhTien INT
) ON PScheme_NGD(NgayGiaoDich)
GO
CREATE INDEX CI_BanHang_MaSP ON dbo.BanHang(MaSP)
GO
Bạn sẽ thấy index cũng được phân đoạn bằng partition scheme PScheme_NGD:
--code kiểm tra xem index có được phân đoạn
SELECT i.name AS IndexName, i.type_desc, ps.name AS PartitionName
FROM SYS.indexes i
JOIN SYS.partition_schemes ps
 ON ps.data_space_id = i.data_space_id 
WHERE i.name = 'IX_BanHang_MaSP'

Đồng thời index cũng tự động được thêm trường partition key là NgayGiaoDich, mặc dù bạn chỉ tạo index trên trường MaSP:
--code để liệt kê các cột chứa trong index
SELECT ind.name AS Index_Name, col.name AS Column_Name
FROM SYS.indexes ind 
INNER JOIN SYS.index_columns ic 
    ON  ind.OBJECT_ID = ic.OBJECT_ID and ind.index_id = ic.index_id 
INNER JOIN SYS.COLUMNS col 
    ON ic.OBJECT_ID = col.OBJECT_ID and ic.column_id = col.column_id 
WHERE ind.name = 'IX_BanHang_MaSP'
Ở ví dụ ở trên, index là non-unique. Khi bạn tạo unique index, tình hình sẽ khác đi một chút. SQL Server không thể tự động thêm trường partition key vào index vì như thế sẽ làm thay đổi tính chất của index. Ví dụ khi bạn tạo unique index trên trường BangHang_ID, trường này chỉ được chứa các giá trị duy nhất trong toàn bảng. Nếu SQL Server thêm trường NgayGD vào index, index đó sẽ chỉ đảm bảo tính duy nhất cho cặp BanHang_ID+NgayGD và khi đó BanHang_ID vẫn có thể chứa các giá trị lặp lại miễn là khác NgayGD. Vì thế, thay vì tự động sửa index, SQL Server sẽ kiểm tra xem index có chứa trường partition key hay không, nếu không thì nó báo lỗi và không cho tạo index. Ví dụ, lệnh tạo index sau sẽ báo lỗi:
CREATE UNIQUE INDEX UI_BanHang_BanHang_ID ON dbo.BanHang(BanHang_ID)
Msg 1908, Level 16, State 1, Line 1
Column ‘NgayGiaoDich’ is partitioning column of the index ‘UI_BanHang_BanHang_ID’. Partition columns for a unique index must be a subset of the index key.
Để khắc phục, bạn cần chỉ định rõ filegroup cho index:
CREATE UNIQUE INDEX UI_BanHang_BanHang_ID ON dbo.BanHang(BanHang_ID) ON [PRIMARY]
Khi đó index được tạo ra trên PRIMARY filegroup thay vì trên partition scheme PScheme_NGD, nói cách khác là nó không được phân đoạn theo bảng.
Clustered Index Vì clustered index chính là bảng nên nó đương nhiên phải được phân đoạn cùng với bảng. Cũng giống như trên, nếu index là non-unique SQL Server sẽ tự động bổ sung partition key vào index và phân đoạn nó. Còn nếu index là unique, trường partition key bắt buộc phải có mặt trong index nếu không sẽ bị lỗi. Tuy nhiên lưu ý là khi bạn chỉ định filegroup cho index, toàn bộ bảng sẽ được kéo sang filegroup mới và bảng sẽ không còn được phân đoạn nữa. Vì thế bạn chỉ nên làm điều này khi chủ định thay đổi cấu trúc lưu trữ vật lý của bảng.
Bảng dưới đây tóm tắt các loại index và các tình huống tương ứng:
Loại IndexKhông chỉ định filegroupChỉ định filegroup
Non-Clustered non-unique
- Không chứa partition keyaligned, partition key được thêm vào indexnot partitioned, non-aligned
- Chứa partition keypartitioned, alignednot partitioned, non-aligned
Non-clustered unique
- Không chứa partition keyLỗi, cần thêm partition key vào indexnot partitioned, non-aligned
- Chứa partition keypartitioned, alignednot partitioned, non-aligned
Clustered non-unique
- Không chứa partition keypartitioned, aligned, partition key được thêm vào index
- Chứa partition keypartitioned, aligned
Clustered unique
- Không chứa partition keyLỗi, cần thêm partition key vào index
- Chứa partition keypartitioned, aligned
Như vậy bạn thấy index luôn được phân đoạn cùng với bảng chỉ trừ trường hợp với non-clustered index và bạn chỉ định filegroup cho nó. Vì sao SQL Server luôn ưu tiên phân đoạn index? Lý do là vì những lợi ích sau:
- Bạn có thể backup/restore từng đoạn của bảng và index cũng được backup/restore theo.
- Bạn có thể rebuild hoặc reorganize lại index trên từng đoạn mà bạn thấy cần phải rebuild hoặc reorganize
- Bạn có thể thực hiện các thao tác trên từng đoạn dễ dàng như switch/switch out hoặc split/merge
- Bộ optimizer khi sử dụng aligned index có thể áp dụng loại bỏ các đoạn không chứa dữ liệu cần cho câu lệnh
Hãy liên hệ bảng với chiếc bánh tét như đã nêu ở đầu bài, tất cả các thao tác kể trên có thể thực hiện dễ dàng nếu như từng lát bánh có một sợi lạt riêng. Còn nếu vẫn còn một sợi lạt buộc vòng tất cả các lát bánh với nhau (non-aligned index) thì các thao tác này sẽ bị sợi lạt đó làm vướng và không thực hiện được.
Vậy trong trường hợp nào vẫn cần non-aligned index? Khi bạn cần tạo unique index (non-clustered) trên một cột để đảm bảo tính duy nhất cho nó, bạn không có cách nào khác là tạo index trên một filegroup khác và để nó non-aligned với bảng.