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

Chuyển từ AnswerHub sang WordPress:Câu chuyện về 10 công nghệ

Gần đây, chúng tôi đã khởi chạy một trang web hỗ trợ mới, nơi bạn có thể đặt câu hỏi, gửi phản hồi về sản phẩm hoặc yêu cầu tính năng hoặc mở phiếu hỗ trợ. Một phần của mục tiêu là tập trung tất cả những nơi mà chúng tôi đang cung cấp hỗ trợ cho cộng đồng. Điều này bao gồm trang web Hỏi và Đáp SQLPerformance.com, nơi Paul White, Hugo Kornelis và nhiều người khác đã giúp giải quyết các câu hỏi về kế hoạch thực thi và điều chỉnh truy vấn phức tạp nhất của bạn, kể từ tháng 2 năm 2013. Tôi nói với bạn với cảm xúc lẫn lộn rằng Trang web Hỏi &Đáp đã bị đóng cửa.

Tuy nhiên, có một mặt trái. Bây giờ bạn có thể hỏi những câu hỏi hóc búa đó tại diễn đàn hỗ trợ mới. Nếu bạn đang tìm kiếm nội dung cũ, thì nó vẫn ở đó, nhưng nó có vẻ hơi khác một chút. Vì nhiều lý do mà tôi sẽ không tham gia hôm nay, khi chúng tôi quyết định ngừng cung cấp trang Hỏi &Đáp ban đầu, cuối cùng chúng tôi quyết định chỉ lưu trữ tất cả nội dung hiện có trên một trang web WordPress chỉ đọc, thay vì di chuyển nó vào phần cuối của trang web mới.

Bài đăng này không nói về lý do đằng sau quyết định đó.

Tôi thực sự cảm thấy thực sự tồi tệ về tốc độ trang web câu trả lời phải ngoại tuyến, DNS chuyển đổi và nội dung được di chuyển. Vì một biểu ngữ cảnh báo đã được triển khai trên trang web nhưng AnswerHub không thực sự hiển thị nó, đây là một cú sốc đối với nhiều người dùng. Vì vậy, tôi muốn đảm bảo rằng tôi đã lưu giữ nhiều nội dung nhất có thể và tôi muốn nó đúng. Bài đăng này ở đây vì tôi nghĩ sẽ rất thú vị khi nói về quy trình thực tế, có bao nhiêu phần công nghệ khác nhau liên quan đến việc khai thác nó và để giới thiệu kết quả. Tôi không mong đợi bất kỳ ai trong số các bạn sẽ được hưởng lợi từ end-to-end này, vì đây là một con đường di chuyển tương đối mù mờ, nhưng nhiều hơn như một ví dụ về việc kết hợp nhiều công nghệ với nhau để hoàn thành một nhiệm vụ. Nó cũng là một lời nhắc nhở tốt cho bản thân tôi rằng nhiều thứ không dễ dàng như trước khi bạn bắt đầu.

TL; DR là thế này:Tôi đã dành rất nhiều thời gian và nỗ lực để làm cho nội dung lưu trữ trông đẹp mắt, mặc dù tôi vẫn đang cố gắng khôi phục một số bài đăng cuối cùng xuất hiện ở phần cuối. Tôi đã sử dụng các công nghệ này:

  1. Perl
  2. Máy chủ SQL
  3. PowerShell
  4. Truyền (FTP)
  5. HTML
  6. CSS
  7. C #
  8. MarkdownSharp
  9. phpMyAdmin
  10. MySQL

Do đó tiêu đề. Nếu bạn muốn có một phần lớn các chi tiết đẫm máu, đây là chúng. Nếu bạn có bất kỳ câu hỏi hoặc phản hồi nào, vui lòng liên hệ hoặc nhận xét bên dưới.

AnswerHub đã cung cấp một tệp kết xuất 665 MB từ cơ sở dữ liệu MySQL lưu trữ nội dung Hỏi và Đáp. Mọi trình soạn thảo tôi đã thử đều mắc kẹt, vì vậy trước tiên tôi phải chia nó thành một tệp trên mỗi bảng bằng cách sử dụng tập lệnh Perl tiện dụng này của Jared Cheney. Các bảng tôi cần được gọi là network11_nodes (câu hỏi, câu trả lời và nhận xét), network11_authoritables (người dùng) và network11_managed_files (tất cả tệp đính kèm, bao gồm cả tải lên gói):perl extract_sql.pl -t network11_nodes -r dump.sql>> node.sql
perl extract_sql.pl -t network11_authoritables -r dump.sql>> users.sql
perl extract_sql.pl -t network11_managed_files -r dump.sql>> files.sql

Bây giờ những thứ đó không được tải cực nhanh trong SSMS, nhưng ít nhất ở đó tôi có thể sử dụng Ctrl + H để thay đổi (ví dụ) điều này:

CREATE TABLE `network11_managed_files` (
  `c_id` bigint(20) NOT NULL,
  ...
);
 
INSERT INTO `network11_managed_files` (`c_id`, ...) VALUES (1, ...);

Về điều này:

CREATE TABLE dbo.files
(
  c_id bigint NOT NULL,
  ...
);
 
INSERT dbo.files (c_id, ...) VALUES (1, ...);

Sau đó, tôi có thể tải dữ liệu vào SQL Server để có thể thao tác với nó. Và tin tôi đi, tôi đã thao túng nó.

Tiếp theo, tôi phải truy xuất tất cả các tệp đính kèm. Hãy xem, tệp kết xuất MySQL mà tôi nhận được từ nhà cung cấp chứa một gazillion INSERT nhưng không có câu lệnh nào trong số các tệp kế hoạch thực tế mà người dùng đã tải lên - cơ sở dữ liệu chỉ có các đường dẫn tương đối đến các tệp. Tôi đã sử dụng T-SQL để tạo một chuỗi lệnh PowerShell sẽ gọi Invoke-WebRequest để lấy tất cả các tệp và lưu trữ cục bộ (nhiều cách để lột da con mèo này, nhưng điều này rất dễ dàng). Từ cái này:

SELECT 'Invoke-WebRequest -Uri '
  + '"$($url)' + RTRIM(c_id) + '-' + c_name + '"'
  + ' -OutFile "E:\s\temp\' + RTRIM(c_id) + '-' + c_name + '";'
  FROM dbo.files
  WHERE LOWER(c_mime_type) LIKE 'application/%';

Điều đó mang lại bộ lệnh này (cùng với lệnh trước để giải quyết vấn đề TLS này); toàn bộ mọi thứ chạy khá nhanh, nhưng tôi không khuyến nghị phương pháp này cho bất kỳ sự kết hợp nào của {bộ tệp lớn} và / hoặc {băng thông thấp}:

$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12';
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols;
$u = "https://answers.sqlperformance.com/s/temp/";
 
Invoke-WebRequest -Uri "$($u)/1-proc.pesession"   -OutFile "E:\s\temp\1-proc.pesession";
Invoke-WebRequest -Uri "$($u)/14-test.pesession"  -OutFile "E:\s\temp\14-test.pesession";
Invoke-WebRequest -Uri "$($u)/15-a.QueryAnalysis" -OutFile "E:\s\temp\15-a.QueryAnalysis";
...

Điều này đã tải xuống gần như tất cả các tệp đính kèm nhưng phải thừa nhận rằng một số tệp đã bị bỏ sót do lỗi trên trang web cũ khi chúng được tải lên ban đầu. Vì vậy, trên trang web mới, đôi khi bạn có thể thấy tham chiếu đến tệp đính kèm không tồn tại.

Sau đó, tôi sử dụng Panic Transmit 5 để tải lên temp đến trang web mới và bây giờ khi nội dung được tải lên, liên kết đến /s/temp/1-proc.pesession sẽ tiếp tục hoạt động.

Tiếp theo, tôi chuyển sang SSL. Để yêu cầu chứng chỉ trên trang WordPress mới, chúng tôi phải cập nhật DNS cho answer.sqlperformance.com để trỏ đến CNAME trên máy chủ WordPress của chúng tôi, WPEngine. Ở đây nó giống như gà và trứng - chúng tôi đã phải chịu một số thời gian ngừng hoạt động cho các URL https, điều này sẽ không thành công do không có chứng chỉ trên trang web mới. Điều này không sao cả vì chứng chỉ trên trang web cũ đã hết hạn, vì vậy, thực sự, chúng tôi không tệ hơn. Tôi cũng phải đợi để thực hiện việc này cho đến khi tải xuống tất cả các tệp từ trang web cũ, bởi vì một khi DNS bị lật, sẽ không có cách nào để truy cập chúng ngoại trừ một số cửa sau.

Trong khi chờ đợi DNS phổ biến, tôi bắt đầu làm việc trên logic để kéo tất cả các câu hỏi, câu trả lời và nhận xét vào một thứ gì đó có thể sử dụng được trong WordPress. Không chỉ các lược đồ bảng khác với WordPress, các loại thực thể cũng khác nhau khá nhiều. Tầm nhìn của tôi là kết hợp mỗi câu hỏi - và bất kỳ câu trả lời và / hoặc nhận xét nào - vào một bài đăng duy nhất.

Phần khó khăn là bảng nút chỉ chứa tất cả ba kiểu nội dung trong cùng một bảng, với các tham chiếu gốc và tham chiếu gốc ("master"). Mã giao diện người dùng của họ có thể sử dụng một số loại con trỏ để lướt qua và hiển thị nội dung theo thứ tự thời gian và phân cấp. Tôi sẽ không có sự xa xỉ đó trong WordPress, vì vậy tôi phải xâu chuỗi HTML lại với nhau trong một lần. Chỉ là một ví dụ, đây là dữ liệu trông như thế nào:

SELECT c_type, c_id, c_parent, oParent = c_originalParent, c_creation_date, c_title
  FROM dbo.nodes 
  WHERE c_originalParent = 285;
 
/*
c_type      c_id    c_parent  oParent  c_creation_date   accepted  c_title
----------  ------  --------  -------  ----------------  --------  -------------------------
question    285     NULL      285      2013-02-13 16:30            why is the MERGE JOIN ...
answer      287     285       285      2013-02-14 01:15  1         NULL
comment     289     285       285      2013-02-14 13:35            NULL
answer      293     285       285      2013-02-14 18:22            NULL
comment     294     287       285      2013-02-14 18:29            NULL
comment     298     285       285      2013-02-14 20:40            NULL
comment     299     298       285      2013-02-14 18:29            NULL
*/

Tôi không thể sắp xếp theo id, hoặc loại, hoặc theo phụ huynh, vì đôi khi một nhận xét sẽ đến sau câu trả lời trước đó, câu trả lời đầu tiên không phải lúc nào cũng là câu trả lời được chấp nhận, v.v. Tôi muốn đầu ra này (trong đó ++ đại diện cho một mức thụt lề):

/*
c_type        c_id    c_parent  oParent  c_creation_date   reason
----------    ------  --------  -------  ----------------  -------------------------
question      285     NULL      285      2013-02-13 16:30  question is ALWAYS first
++comment     289     285       285      2013-02-14 13:35  comments on the question before answers
answer        287     285       285      2013-02-14 01:15  first answer (accepted = 1)
++comment     294     287       285      2013-02-14 18:29  first comment on first answer
++comment     298     287       285      2013-02-14 20:40  second comment on first answer
++++comment   299     298       285      2013-02-14 18:29  reply to second comment on first answer
answer        293     285       285      2013-02-14 18:22  second answer
*/

Tôi đã bắt đầu viết một CTE đệ quy và một phần do quá nhiều Rekorderlig vào buổi tối hôm đó, tôi đã tranh thủ sự giúp đỡ của Giám đốc sản phẩm, Andy Mallon (@AMtwo). Anh ấy đã giúp tôi thực hiện truy vấn này, điều này sẽ trả về các bài đăng theo thứ tự hiển thị thích hợp của chúng (và bạn có thể thử đoạn mã này, thay đổi cha mẹ và / hoặc câu trả lời được chấp nhận, để thấy rằng thứ tự phù hợp sẽ vẫn được trả lại):

DECLARE @foo TABLE
(
  c_type varchar(255), 
  c_id int, 
  c_parent int, 
  oParent int,
  accepted bit
);
 
INSERT @foo(c_type, c_id, c_parent, oParent, accepted) VALUES
('question', 285, NULL, 285, 0),
('answer',   287, 285 , 285, 1),
('comment',  289, 285 , 285, 0),
('comment',  294, 287 , 285, 0),
('comment',  298, 287 , 285, 0),
('comment',  299, 298 , 285, 0),
('answer',   293, 285 , 285, 0);
 
;WITH cte AS 
(
  SELECT 
    lvl = 0,
    f.c_type,
    f.c_id, f.c_parent, f.oParent,
    Sort = CONVERT(varchar(255),RIGHT('00000' + CONVERT(varchar(5),f.c_id),5))
  FROM @foo AS f WHERE f.c_parent IS NULL
  UNION ALL
  SELECT 
    lvl = c.lvl + 1,
    c_type = CONVERT(varchar(255), CASE
        WHEN f.accepted = 1 THEN 'accepted answer'
        WHEN f.c_type = 'comment' THEN c.c_type + ' ' + f.c_type
        ELSE f.c_type
      END),
    f.c_id, f.c_parent, f.oParent,
    Sort = CONVERT(varchar(255),c.Sort + RIGHT('00000' + CONVERT(varchar(5),f.c_id),5))
  FROM @foo AS f INNER JOIN cte AS c ON c.c_id = f.c_parent
)
SELECT lvl = CASE lvl WHEN 0 THEN 1 ELSE lvl END, c_type, c_id, c_parent, oParent, Sort
FROM cte
ORDER BY 
  oParent,
  CASE
    WHEN c_type LIKE 'question%'        THEN 1 -- it's a question *or* a comment on the question
    WHEN c_type LIKE 'accepted answer%' THEN 2 -- accepted answer *or* comment on accepted answer
    ELSE 3 END,
  Sort;

Kết quả:

/*
lvl  c_type                            c_id        c_parent    oParent     Sort
---- --------------------------------- ----------- ----------- ----------- --------------------
1    question                          285         NULL        285         00285               
1    question comment                  289         285         285         0028500289          
1    accepted answer                   287         285         285         0028500287          
2    accepted answer comment           294         287         285         002850028700294     
2    accepted answer comment           298         287         285         002850028700298     
3    accepted answer comment comment   299         298         285         00285002870029800299
1    answer                            293         285         285         0028500293     
*/

Thiên tài. Tôi phát hiện ra hàng tá người khác đã kiểm tra, và rất vui khi được chuyển sang bước tiếp theo. Tôi đã rất nhiều lần cảm ơn Andy, nhưng hãy để tôi làm lại lần nữa: Cảm ơn Andy!

Bây giờ tôi có thể trả lại toàn bộ tập hợp theo thứ tự tôi thích, tôi phải thực hiện một số thao tác với đầu ra để áp dụng các phần tử HTML và tên lớp cho phép tôi đánh dấu câu hỏi, câu trả lời, nhận xét và thụt lề một cách có ý nghĩa. Mục tiêu cuối cùng là đầu ra trông giống như thế này (và hãy nhớ rằng đây là một trong những trường hợp đơn giản hơn):

<div class="question">
  <span class="authorq" title=" Author : author name ">
    <i class="fas fa-user"></i>Author name</span> 
  <span class="createdq" title=" February 13th, 2013 ">
    <i class="fas fa-calendar-alt"></i>2013-02-13 16:30:36</span>
 
  <div class=mainbodyq>I don't understand why the merge operator is passing over 4million 
  rows to the hash match operator when there is only 41K and 19K from other operators.
 
	<div class=attach><i class="fas fa-file"></i>
	  <a target="_blank" href="/s/temp/254-tmp4DA0.queryanalysis" rel="noopener noreferrer">
      /s/temp/254-tmp4DA0.queryanalysis</a>
	</div>
  </div>
 
  <div class="comment indent1 ">
    <div class=linecomment>
	  <span class="authorc" title=" Author : author name ">
	    <i class="fas fa-user"></i>author name</span>
	  <span class="createdc" title=" February 14th, 2013 ">
	    <i class="fas fa-calendar-alt"></i>2013-02-14 13:35:39</span>
	</div>
    <div class=mainbodyc>
	  I am still trying to understand the significant amount of rows from the MERGE operator. 
	  Unless it's a result of a Cartesian product from the two inputs then finally the WHERE 
	  predicate is applied to filter out the unmatched rows leaving the 4 million row count.
    </div>
  </div>
  <div class="answer indent1 [accepted]">
    <div class=lineanswer>
	  <span class="authora" title=" Author : author name ">
	    <i class="fas fa-user"></i>author name</span>
	  <span class="createda" title=" February 14th, 2013 ">
	    <i class="fas fa-calendar-alt"></i>2013-02-14 01:15:42</span>
	</div>
    <div class=mainbodya>
	    The reason for the large number of rows can be seen in the Plan Explorer tool tip for 
		the Merge Join operator:
 
	    <img src="/s/temp/259-sp.png" alt="Merge Join tool tip" />
	  	...
	</div>
  </div>
</div>

Tôi sẽ không vượt qua số lần lặp lại vô lý mà tôi phải trải qua để đạt được một dạng đầu ra đáng tin cậy cho tất cả hơn 5.000 mục (được dịch thành gần 1.000 bài đăng sau khi mọi thứ được gắn kết với nhau). Trên hết, tôi cần tạo những thứ này ở dạng INSERT các câu lệnh mà sau đó tôi có thể dán vào phpMyAdmin trên trang web WordPress, có nghĩa là phải tuân theo sơ đồ cú pháp kỳ lạ của chúng. Những câu lệnh đó cần thiết để bao gồm thông tin bổ sung khác do WordPress yêu cầu, nhưng không hiển thị hoặc không chính xác trong dữ liệu nguồn (như post_type ). Và bảng điều khiển quản trị đó sẽ hết thời gian chờ do có quá nhiều dữ liệu, vì vậy tôi phải chia nhỏ nó thành ~ 750 lần chèn cùng một lúc. Đây là quy trình tôi đã kết thúc (đây không thực sự là để tìm hiểu bất cứ điều gì cụ thể từ đó, chỉ là một minh chứng về mức độ cần thiết của việc thao tác dữ liệu đã nhập):

CREATE /* OR ALTER */ PROCEDURE dbo.BuildMySQLInserts
  @LowerBound int = 1, 
  @UpperBound int = 750
AS
BEGIN
  SET NOCOUNT ON;
 
  ;WITH CTE AS 
  (
    SELECT lvl = 0,
            [type] = CONVERT(varchar(100),f.[type]),
            f.id,
            f.parent,
            f.master_parent,
            created = CONVERT(char(10), f.created, 120) + ' ' 
			        + CONVERT(char(8),  f.created, 108),
            f.state,
            Sort = CONVERT(varchar(100),RIGHT('0000000000' 
			     + CONVERT(varchar(10),f.id),10))
    FROM dbo.foo AS f
    WHERE f.type = 'question' 
      AND master_parent BETWEEN @LowerBound AND @UpperBound
    UNION ALL
    SELECT lvl = c.lvl + 1,
            CONVERT(varchar(100),CASE
                WHEN f.[state] = '[accepted]' THEN 'accepted answer'
                WHEN f.type = 'comment' THEN c.type + ' ' + f.type
                ELSE f.type
            END),
            f.id,
            f.parent,
            f.master_parent,
            created = CONVERT(char(10), f.created, 120) + ' ' 
			        + CONVERT(char(8), f.created, 108),
            f.state,
            Sort = CONVERT(varchar(100),c.sort + RIGHT('0000000000' 
			     + CONVERT(varchar(10),f.id),10))
    FROM dbo.foo AS f
    JOIN CTE AS c ON c.id = f.parent
)
SELECT 
  master_parent, 
  prefix = CASE WHEN lvl = 0 THEN 
    CONVERT(varchar(11), master_parent) + ', 3, ''' + created + ''', ''' 
	+ created + ''',''' END, 
  bodypre = '<div class="' + COALESCE(c_type, RTRIM(LEFT([type],8))) 
	  + CASE WHEN c_type <> 'question' THEN ' indent' + RTRIM(lvl) 
	  + COALESCE(' ' + [state], '') ELSE '' END + '">'
	  + CASE WHEN c_type <> 'question' THEN 
	    '<div class=line' + c_type + '>' ELSE '' END 
	  + '<span class="author' + LEFT(c_type, 1) + '" title=" Author : ' 
	  + REPLACE(REPLACE(Fullname,'''','\'''),'"','') 
	  + ' "><i class="fas fa-user"></i>' + REPLACE(Fullname,'''','\''') --"
	  + '</span> <span class="created' + LEFT(c_type,1) + '" title=" ' 
	  + DATENAME(MONTH, c_creation_date) + ' ' + RTRIM(DAY(c_creation_date)) 
	  + CASE 
        WHEN DAY(c_creation_date) IN (1,21,31) THEN 'st'
        WHEN DAY(c_creation_date) IN (2,22) THEN 'nd'
        WHEN DAY(c_creation_date) IN (3,23) THEN 'rd' ELSE 'th' END
        + ', ' + RTRIM(YEAR(c_creation_date)) 
      + ' "><i class="fas fa-calendar-alt"></i>' + created + '</span>'
      + CASE WHEN c_type <> 'question' THEN '</div>' ELSE '' END,
  body = '<div class=mainbody' + left(c_type,1) + '>' 
	  + REPLACE(REPLACE(c_body, char(39), '\' + char(39)), '’', '\' + char(39)),
  bodypost = COALESCE(urls, '') + '</div></div>',--' 
	  + CASE WHEN c_type = 'question' THEN '</div>' ELSE '' END, 
  suffix = ''',''' + REPLACE(n.c_title, '''', '\''') + ''','''',''publish'',
	  ''closed'',''closed'','''',''' + REPLACE(n.c_plug, '''', '\''') 
	  + ''','''','''',''' + created + ''',''' + created + ''','''',0,
	  ''https://answers.sqlperformance.com/?p=' + CONVERT(varchar(11), master_parent) 
	  + ''', 0, ''post'','''',0);',
  rn = RTRIM(ROW_NUMBER() OVER (PARTITION BY master_parent 
      ORDER BY master_parent,
      CASE
        WHEN [type] LIKE 'question%' THEN 1
        WHEN [type] LIKE 'accepted answer%' THEN 2
        ELSE 3
      END,
      Sort)), 
  c = RTRIM(COUNT(*) OVER (PARTITION BY master_parent))
FROM CTE
LEFT OUTER JOIN dbo.network11_nodes AS n
ON cte.id = n.c_id
LEFT OUTER JOIN dbo.Users AS u
ON n.c_author = u.UserID
LEFT OUTER JOIN 
(
  SELECT NodeID, urls = STRING_AGG('<div class=attach>
    <i class="fas fa-file' 
	+ CASE WHEN c_mime_type IN ('image/jpeg','image/png') 
      THEN '-image' ELSE '' END 
    + '"></i><a target="_blank" href=' + url + ' rel="noopener noreferrer">' + url + '</a></div>', '\n') 
  FROM dbo.Attachments 
  GROUP BY NodeID
) AS a
ON n.c_id = a.NodeID
ORDER BY master_parent,
  CASE
    WHEN [type] LIKE 'question%' THEN 1
    WHEN [type] LIKE 'accepted answer%' THEN 2
    ELSE 3
  END,
  Sort;
END
GO

Đầu ra từ đó chưa hoàn chỉnh và chưa sẵn sàng để đưa vào WordPress:

Đầu ra mẫu (nhấp để phóng to)

Tôi sẽ cần thêm một số trợ giúp từ C # để chuyển nội dung thực tế (bao gồm cả dấu xuống) thành HTML và CSS mà tôi có thể kiểm soát tốt hơn và viết đầu ra (một loạt INSERT các câu lệnh đã xảy ra bao gồm một loạt mã HTML) vào các tệp trên đĩa mà tôi có thể mở và dán vào phpMyAdmin. Đối với HTML, văn bản thuần túy + ​​dấu xuống bắt đầu như thế này:

Có một [bài đăng trên blog ở đây] [1] nói về nó và cả [bài đăng này] (https:// đâu đó).

CHỌN nội dung nào đó từ dbo.sometable;

[1]:https:// nơi khác

Cần phải trở thành cái này:

Có một bài đăng trên blog nói về nó và cũng có bài đăng này .

 CHỌN nội dung nào đó từ dbo.sometable; 

Để giải quyết vấn đề này, tôi đã nhờ đến sự trợ giúp của MarkdownSharp, một thư viện mã nguồn mở có nguồn gốc từ Stack Overflow xử lý phần lớn việc chuyển đổi markdown-to-HTML. Nó phù hợp với nhu cầu của tôi, nhưng không hoàn hảo; Tôi vẫn sẽ phải thực hiện thêm thao tác:

  • MarkdownSharp không cho phép những thứ như target =_blank , vì vậy tôi sẽ phải tự mình tiêm những thứ đó sau khi xử lý;
  • mã (bất kỳ thứ gì có tiền tố là bốn dấu cách) kế thừa các trình bao bọc
    using System.Text;
    using System.Data;
    using System.Data.SqlClient;
    using MarkdownSharp;
    using System.IO;
     
    namespace AnswerHubMigrator
    {
      class Program
      {
        static void Main(string[] args)
        {
          StringBuilder output;
          string suffix = "";
          string thisfile = "";
     
          // pass two arguments on the command line, e.g. 1, 750
          int LowerBound = int.Parse(args[0]);
          int UpperBound = int.Parse(args[1]);
     
          // auto-expand URLs, and only accept bold/italic markdown
          // when it completely surrounds an entire word
          var options = new MarkdownOptions
          {
            AutoHyperlink = true,
            StrictBoldItalic = true
          };
          MarkdownSharp.Markdown mark = new MarkdownSharp.Markdown(options);
     
          using (var conn = new SqlConnection("Server=.\\SQL2017;Integrated Security=true"))
          using (var cmd = new SqlCommand("MigrateDB.dbo.BuildMySQLInserts", conn))
          {
     
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@LowerBound", SqlDbType.Int).Value = LowerBound;
            cmd.Parameters.Add("@UpperBound", SqlDbType.Int).Value = UpperBound;
            conn.Open();
            using (var reader = cmd.ExecuteReader())
            {
              // use a StringBuilder to dump output to a file
              output = new StringBuilder();
              while (reader.Read())
              {
                // on first pass, make a new delete/insert
                // delete is to make the commands idempotent
                if (reader["rn"].Equals("1"))
                {
     
                  // for each master parent, I would create a
                  // new WordPress post, inheriting the parent ID
                  output.Append("DELETE FROM `wp_posts` WHERE ID = ");
                  output.Append(reader["master_parent"].ToString());
                  output.Append("; INSERT INTO `wp_posts` (`ID`, `post_author`, ");
                  output.Append("`post_date`, `post_date_gmt`, `post_content`, ");
                  output.Append("`post_title`, `post_excerpt`, `post_status`, ");
                  output.Append("`comment_status`, `ping_status`, `post_password`,");
                  output.Append(" `post_name`, `to_ping`, `pinged`, `post_modified`,");
                  output.Append(" `post_modified_gmt`, `post_content_filtered`, ");
                  output.Append("`post_parent`, `guid`, `menu_order`, `post_type`, ");
                  output.Append("`post_mime_type`, `comment_count`) VALUES (");
     
                  // I'm sure some of the above columns are optional, but identifying
                  // those would not be a valuable use of time IMHO
     
                  output.Append(reader["prefix"]);
     
                  // hold on to the additional values until last row
                  suffix = reader["suffix"].ToString();
                }
     
                // manipulate the body content to be WordPress and INSERT statement-friendly
                string body = reader["body"].ToString().Replace(@"\n", "\n");
                body = mark.Transform(body).Replace("href=", "target=_blank href=");
                body = body.Replace("<p>", "").Replace("</p>", "");
                body = body.Replace("<pre><code>", "<pre lang=\"tsql\">");
                body = body.Replace("</code></"+"pre>", "</"+"pre>");
                body = body.Replace(@"'", "\'").Replace(@"’", "\'");
     
                body = reader["bodypre"].ToString() + body.Replace("\n", @"\n");
                body += reader["bodypost"].ToString();
                body = body.Replace("&lt;", "<").Replace("&gt;", ">");
                output.Append(body);
     
                // if we are on the last row, add additional values from the first row
                if (reader["c"].Equals(reader["rn"]))
                {
                  output.Append(suffix);
                }
              }
     
              thisfile = UpperBound.ToString();
              using (StreamWriter w = new StreamWriter(@"C:\wp\" + thisfile + ".sql"))
              {
                w.WriteLine(output);
                w.Flush();
              }
            }
          }
        }
      }
    }

    Vâng, đó là một loạt mã xấu xí, nhưng cuối cùng nó đã đưa tôi đến tập hợp đầu ra sẽ không làm cho phpMyAdmin buồn nôn và WordPress sẽ trình bày độc đáo (đủ). Tôi chỉ đơn giản gọi chương trình C # nhiều lần với các phạm vi tham số khác nhau:

    AnswerHubMigrator    1  750
    AnswerHubMigrator  751 1500
    AnswerHubMigrator 1501 2250
    ...

    Sau đó, tôi mở từng tệp, dán chúng vào phpMyAdmin và nhấn GO:

    phpMyAdmin (nhấp để phóng to)

    Tất nhiên tôi phải thêm một số CSS trong WordPress để giúp phân biệt giữa các câu hỏi, nhận xét và câu trả lời, đồng thời thụt lề các nhận xét để hiển thị câu trả lời cho cả câu hỏi và câu trả lời, lồng ghép các nhận xét trả lời nhận xét, v.v. Đây là phần trích dẫn trông như thế nào khi bạn xem qua các câu hỏi của một tháng:

    Ô câu hỏi (nhấp để phóng to)

    Và sau đó là một bài đăng mẫu, hiển thị hình ảnh được nhúng, nhiều tệp đính kèm, nhận xét lồng nhau và câu trả lời:

    Câu hỏi và câu trả lời mẫu (nhấp để chuyển đến đó)

    Tôi vẫn đang cố gắng khôi phục một số bài đăng đã được gửi đến trang web sau khi bản sao lưu cuối cùng được thực hiện, nhưng tôi hoan nghênh bạn duyệt qua. Vui lòng cho chúng tôi biết nếu bạn phát hiện bất kỳ điều gì bị thiếu hoặc không đúng chỗ, hoặc thậm chí chỉ để cho chúng tôi biết rằng nội dung đó vẫn hữu ích cho bạn. Chúng tôi hy vọng sẽ giới thiệu lại chức năng tải lên kế hoạch từ bên trong Plan Explorer, nhưng nó sẽ yêu cầu một số API hoạt động trên trang web hỗ trợ mới, vì vậy tôi không có ETA cho bạn hôm nay.

      Đáp án.SQLPerformance.com

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sử dụng dữ liệu được bảo vệ bằng Azure Key Vault từ Linux

  2. Toán tử SET trong SQL

  3. Mô hình dữ liệu bữa tiệc dành cho trẻ em

  4. SQL Right Join

  5. Sự khác biệt giữa khóa chính và khóa duy nhất