Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Bermigrasi dari AnswerHub ke WordPress:Kisah 10 Teknologi

Kami baru-baru ini meluncurkan situs dukungan baru, tempat Anda dapat mengajukan pertanyaan, mengirimkan umpan balik produk atau permintaan fitur, atau membuka tiket dukungan. Bagian dari tujuannya adalah untuk memusatkan semua tempat di mana kami menawarkan bantuan kepada masyarakat. Ini termasuk situs Q&A SQLPerformance.com, di mana Paul White, Hugo Kornelis, dan banyak lainnya telah membantu memecahkan pertanyaan penyetelan kueri dan rencana eksekusi Anda yang paling rumit, sejak Februari 2013. Saya memberi tahu Anda dengan perasaan campur aduk bahwa Situs Tanya Jawab telah ditutup.

Ada sisi positifnya. Sekarang Anda dapat mengajukan pertanyaan sulit tersebut di forum dukungan baru. Jika Anda mencari konten lama, yah, itu masih ada, tetapi tampilannya sedikit berbeda. Untuk berbagai alasan yang tidak akan saya bahas hari ini, setelah kami memutuskan untuk menghentikan situs Tanya Jawab asli, kami akhirnya memutuskan untuk hanya meng-host semua konten yang ada di situs WordPress read-only, daripada memigrasikannya ke back end dari situs baru.

Postingan ini bukan tentang alasan di balik keputusan itu.

Saya merasa sangat buruk tentang seberapa cepat situs jawaban harus offline, DNS diaktifkan, dan konten dimigrasikan. Karena spanduk peringatan diterapkan di situs tetapi AnswerHub tidak benar-benar membuatnya terlihat, ini mengejutkan banyak pengguna. Jadi saya ingin memastikan bahwa saya menyimpan sebanyak mungkin konten dengan benar, dan saya ingin itu benar. Posting ini ada di sini karena saya pikir akan menarik untuk membicarakan proses sebenarnya, berapa banyak teknologi berbeda yang terlibat dengan menariknya, dan untuk memamerkan hasilnya. Saya tidak berharap ada di antara Anda yang mendapat manfaat dari ujung ke ujung ini, karena ini adalah jalur migrasi yang relatif tidak jelas, tetapi lebih sebagai contoh mengikat sekelompok teknologi bersama-sama untuk menyelesaikan tugas. Ini juga berfungsi sebagai pengingat yang baik untuk diri saya sendiri bahwa banyak hal tidak semudah yang terdengar sebelum Anda mulai.

TL;DR adalah ini:Saya menghabiskan banyak waktu dan upaya untuk membuat konten yang diarsipkan terlihat bagus, meskipun saya masih mencoba memulihkan beberapa posting terakhir yang masuk menjelang akhir. Saya menggunakan teknologi ini:

  1. Perl
  2. SQL Server
  3. PowerShell
  4. Kirim (FTP)
  5. HTML
  6. CSS
  7. C#
  8. Penurunan harga yang tajam
  9. phpMyAdmin
  10. MySQL

Makanya judulnya. Jika Anda ingin potongan besar dari detail berdarah, ini dia. Jika Anda memiliki pertanyaan atau masukan, silakan hubungi atau beri komentar di bawah.

AnswerHub menyediakan file dump 665 MB dari database MySQL yang menampung konten T&J. Setiap editor yang saya coba mencekiknya, jadi pertama-tama saya harus memecahnya menjadi file per tabel menggunakan skrip Perl yang praktis dari Jared Cheney ini. Tabel yang saya butuhkan disebut network11_nodes (pertanyaan, jawaban, dan komentar), network11_authoritables (pengguna), dan network11_managed_files (semua lampiran, termasuk unggahan paket):perl extract_sql.pl -t network11_nodes -r dump.sql>> nodes.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

Sekarang itu tidak terlalu cepat untuk dimuat di SSMS, tapi setidaknya di sana saya bisa menggunakan Ctrl +H untuk mengubah (misalnya) ini:

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

Untuk ini:

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

Kemudian saya bisa memuat data ke SQL Server sehingga saya bisa memanipulasinya. Dan percayalah, saya memanipulasinya.

Selanjutnya, saya harus mengambil semua lampiran. Lihat, file dump MySQL yang saya dapatkan dari vendor berisi trilyun INSERT pernyataan, tetapi tidak ada file rencana aktual yang telah diunggah pengguna — database hanya memiliki jalur relatif ke file tersebut. Saya menggunakan T-SQL untuk membuat serangkaian perintah PowerShell yang akan memanggil Invoke-WebRequest untuk mengambil semua file dan menyimpannya secara lokal (banyak cara untuk menguliti kucing ini, tetapi ini sangat mudah). Dari ini:

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/%';

Itu menghasilkan kumpulan perintah ini (bersama dengan pra-perintah untuk menyelesaikan masalah TLS ini); semuanya berjalan cukup cepat, tetapi saya tidak menyarankan pendekatan ini untuk kombinasi {massive set of files} dan/atau {low bandwidth}:

$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";
...

Ini mengunduh hampir semua lampiran tetapi, memang, ada beberapa yang terlewatkan karena kesalahan di situs lama saat pertama kali diunggah. Jadi, di situs baru, Anda mungkin sesekali melihat referensi ke lampiran yang tidak ada.

Kemudian saya menggunakan Panic Transmit 5 untuk mengunggah temp folder ke situs baru, dan sekarang ketika konten diunggah, tautan ke /s/temp/1-proc.pesession akan terus bekerja.

Selanjutnya, saya pindah ke SSL. Untuk meminta sertifikat di situs WordPress baru, kami harus memperbarui DNS untuk answer.sqlperformance.com agar mengarah ke CNAME di host WordPress kami, WPEngine. Ini seperti ayam dan telur di sini — kami harus mengalami downtime untuk URL https, yang akan gagal tanpa sertifikat di situs baru. Ini baik-baik saja karena sertifikat di situs lama telah kedaluwarsa, jadi sungguh, kami tidak lebih buruk. Saya juga harus menunggu untuk melakukan ini sampai saya mengunduh semua file dari situs lama, karena begitu DNS dibalik, tidak akan ada cara untuk mengaksesnya kecuali melalui pintu belakang.

Sementara saya menunggu DNS menyebar, saya mulai mengerjakan logika untuk menarik semua pertanyaan, jawaban, dan komentar menjadi sesuatu yang dapat dikonsumsi di WordPress. Tidak hanya skema tabel yang berbeda dari WordPress, jenis entitas juga sangat berbeda. Visi saya adalah menggabungkan setiap pertanyaan — dan jawaban dan/atau komentar apa pun — menjadi satu postingan.

Bagian yang sulit adalah bahwa tabel node hanya berisi ketiga tipe konten dalam tabel yang sama, dengan referensi induk dan induk asli ("master"). Kode front-end mereka kemungkinan menggunakan semacam kursor untuk melangkah dan menampilkan konten dalam urutan hierarkis dan kronologis. Saya tidak akan memiliki kemewahan itu di WordPress, jadi saya harus merangkai HTML bersama dalam satu kesempatan. Sebagai contoh, berikut tampilan datanya:

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
*/

Saya tidak dapat mengurutkan berdasarkan id, atau jenis, atau berdasarkan orang tua, karena terkadang komentar akan muncul kemudian pada jawaban yang lebih awal, jawaban pertama tidak selalu merupakan jawaban yang diterima, dan seterusnya. Saya ingin keluaran ini (di mana ++ mewakili satu tingkat indentasi):

/*
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
*/

Saya mulai menulis CTE rekursif dan, sebagian karena terlalu banyak Rekorderlig malam itu, saya meminta bantuan sesama Manajer Produk, Andy Mallon (@AMtwo). Dia membantu saya menyiapkan kueri ini, yang akan mengembalikan posting dalam urutan tampilan yang benar (dan Anda dapat mencoba cuplikan ini, mengubah orang tua dan/atau jawaban yang diterima, untuk melihat bahwa urutan yang benar masih akan dikembalikan):

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;

Hasil:

/*
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     
*/

jenius. Saya melihat selusin atau lebih yang lain, dan senang bisa melanjutkan ke langkah berikutnya. Saya telah mengucapkan banyak terima kasih kepada Andy, beberapa kali, tetapi izinkan saya melakukannya lagi:Terima kasih Andy!

Sekarang saya dapat mengembalikan seluruh rangkaian dalam urutan yang saya suka, saya harus melakukan beberapa manipulasi output untuk menerapkan elemen HTML dan nama kelas yang memungkinkan saya menandai pertanyaan, jawaban, komentar, dan lekukan dengan cara yang berarti. Tujuan akhirnya adalah keluaran yang terlihat seperti ini (dan perlu diingat, ini adalah salah satu kasus yang lebih sederhana):

<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>

Saya tidak akan melewati jumlah iterasi konyol yang harus saya lalui untuk mendapatkan bentuk output yang andal untuk semua 5.000+ item (yang diterjemahkan menjadi hampir 1.000 posting setelah semuanya direkatkan). Selain itu, saya perlu membuat ini dalam bentuk INSERT pernyataan yang kemudian dapat saya tempel ke phpMyAdmin di situs WordPress, yang berarti mengikuti diagram sintaks yang aneh. Pernyataan tersebut perlu menyertakan informasi tambahan lain yang diperlukan oleh WordPress, tetapi tidak ada atau akurat dalam data sumber (seperti post_type ). Dan konsol admin itu akan kehabisan waktu karena terlalu banyak data, jadi saya harus membaginya menjadi ~750 sisipan sekaligus. Berikut adalah prosedur yang saya dapatkan (ini bukan untuk mempelajari sesuatu yang spesifik, hanya demonstrasi seberapa banyak manipulasi data yang diimpor diperlukan):

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

Output dari itu belum selesai dan belum siap untuk dimasukkan ke WordPress dulu:

Contoh keluaran (klik untuk memperbesar)

Saya memerlukan bantuan tambahan dari C# untuk mengubah konten aktual (termasuk penurunan harga) menjadi HTML dan CSS yang dapat saya kendalikan dengan lebih baik, dan menulis hasilnya (sekelompok INSERT pernyataan yang kebetulan menyertakan banyak kode HTML) ke file di disk yang bisa saya buka dan tempel ke phpMyAdmin. Untuk HTML, teks biasa + penurunan harga yang dimulai seperti ini:

Ada [postingan blog di sini][1] yang membicarakannya, dan juga [postingan ini](https://somewhere).

PILIH sesuatu dari dbo.sometable;

[1]:https://tempat lain

Perlu menjadi ini:

Ada postingan blog di sini yang membicarakannya, dan juga postingan ini .

PILIH sesuatu dari dbo.sometable;

Untuk melakukan ini, saya meminta bantuan MarkdownSharp, perpustakaan sumber terbuka yang berasal dari Stack Overflow yang menangani banyak konversi penurunan harga ke HTML. Itu cocok untuk kebutuhan saya, tetapi tidak sempurna; Saya masih harus melakukan manipulasi lebih lanjut:

  • MarkdownSharp tidak mengizinkan hal-hal seperti target=_blank , jadi saya harus menyuntikkannya sendiri setelah diproses;
  • kode (apa pun yang diawali dengan empat spasi) mewarisi pembungkus
    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();
              }
            }
          }
        }
      }
    }

    Ya, itu sekelompok kode yang jelek, tetapi akhirnya membawa saya ke set keluaran yang tidak akan membuat phpMyAdmin muntah, dan WordPress itu akan tampil dengan baik (cukup). Saya cukup memanggil program C# beberapa kali dengan rentang parameter yang berbeda:

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

    Kemudian saya membuka setiap file, menempelkannya ke phpMyAdmin, dan menekan GO:

    phpMyAdmin (klik untuk memperbesar)

    Tentu saja saya harus menambahkan beberapa CSS di dalam WordPress untuk membantu membedakan antara pertanyaan, komentar, dan jawaban, dan juga untuk membuat indentasi komentar untuk menunjukkan balasan atas pertanyaan dan jawaban, komentar bersarang yang membalas komentar, dan seterusnya. Berikut adalah cuplikan saat Anda menelusuri pertanyaan selama sebulan:

    Ubin pertanyaan (klik untuk memperbesar)

    Dan kemudian contoh posting, menampilkan gambar yang disematkan, beberapa lampiran, komentar bersarang, dan jawaban:

    Contoh Pertanyaan dan Jawaban (klik untuk menuju ke sana)

    Saya masih mencoba memulihkan beberapa posting yang dikirimkan ke situs setelah pencadangan terakhir dilakukan, tetapi saya menyambut Anda untuk menelusurinya. Beri tahu kami jika Anda menemukan sesuatu yang hilang atau tidak pada tempatnya, atau bahkan hanya untuk memberi tahu kami bahwa konten tersebut masih berguna bagi Anda. Kami berharap dapat memperkenalkan kembali fungsionalitas unggahan paket dari dalam Plan Explorer, tetapi ini akan memerlukan beberapa pekerjaan API di situs dukungan baru, jadi saya tidak memiliki ETA untuk Anda hari ini.

      Answers.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. Bekerja dengan Data JDBC di Domo

  2. Mengurangi Fragmentasi Indeks

  3. Nomor baris dengan urutan nondeterministik

  4. Cara Kerja Login di Server Tertaut (Contoh T-SQL)

  5. Menggali Lebih Dalam Migrasi Django