sql >> Databáze >  >> RDS >> Database

Migrace z AnswerHub na WordPress:Příběh 10 technologií

Nedávno jsme spustili nový web podpory, kde můžete klást otázky, odesílat zpětnou vazbu k produktu nebo požadavky na funkce nebo otevírat lístky podpory. Součástí cíle bylo centralizovat všechna místa, kde jsme komunitě nabízeli pomoc. To zahrnovalo stránky SQLPerformance.com Q&A, kde Paul White, Hugo Kornelis a mnozí další pomáhali řešit vaše nejsložitější otázky týkající se ladění dotazů a plánu provádění, a to až do února 2013. Se smíšenými pocity vám říkám, že Stránka Q&A byla uzavřena.

Má to ale výhodu. Na novém fóru podpory nyní můžete klást tyto těžké otázky. Pokud hledáte starý obsah, dobře, stále tam je, ale vypadá trochu jinak. Z různých důvodů, kterými se dnes nebudu zabývat, jsme se poté, co jsme se rozhodli zrušit původní web s otázkami a odpověďmi, nakonec rozhodli jednoduše hostit veškerý existující obsah na webu WordPress, který je pouze pro čtení, místo abychom jej migrovali do back-endu. nového webu.

Tento příspěvek není o důvodech tohoto rozhodnutí.

Cítil jsem se opravdu špatně z toho, jak rychle se stránka odpovědí musela dostat do režimu offline, DNS se přepnul a obsah migroval. Vzhledem k tomu, že na webu byl implementován varovný banner, ale AnswerHub jej ve skutečnosti nezviditelnil, byl to pro mnoho uživatelů šok. Chtěl jsem se tedy ujistit, že jsem správně zachoval co nejvíce obsahu, a chtěl jsem, aby to bylo správné. Tento příspěvek je zde, protože jsem si myslel, že by bylo zajímavé mluvit o skutečném procesu, o tom, kolik různých částí technologie bylo spojeno s jeho vytažením, a předvést výsledek. Neočekávám, že kdokoli z vás bude mít prospěch z tohoto end-to-endu, protože se jedná o poměrně nejasnou cestu migrace, ale spíše jako příklad spojení hromady technologií dohromady za účelem splnění úkolu. Slouží mi to také jako dobrá připomínka toho, že mnoho věcí není tak snadné, jak to zní, než začnete.

TL;DR je toto:Strávil jsem spoustu času a úsilí, aby archivovaný obsah vypadal dobře, i když se stále snažím obnovit několik posledních příspěvků, které přišly ke konci. Použil jsem tyto technologie:

  1. Perl
  2. SQL Server
  3. PowerShell
  4. Přenos (FTP)
  5. HTML
  6. CSS
  7. C#
  8. MarkdownSharp
  9. phpMyAdmin
  10. MySQL

Odtud název. Pokud chcete velký kus krvavých detailů, tady jsou. Pokud máte nějaké dotazy nebo zpětnou vazbu, kontaktujte nás nebo napište komentář níže.

Služba AnswerHub poskytla soubor výpisu o velikosti 665 MB z databáze MySQL, která hostila obsah otázek a odpovědí. Každý editor, který jsem zkoušel, se tím dusil, takže jsem to nejprve musel rozdělit do souboru podle tabulky pomocí tohoto praktického skriptu v Perlu od Jareda Cheneyho. Potřebné tabulky se jmenovaly network11_nodes (otázky, odpovědi a komentáře), network11_authoritables (uživatelé) a network11_managed_files (všechny přílohy, včetně nahrání plánu):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

Ty se nyní v SSMS nenačítaly extrémně rychle, ale alespoň tam jsem mohl použít Ctrl +H změnit (například) toto:

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

K tomu:

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

Poté jsem mohl načíst data na SQL Server, abych s nimi mohl manipulovat. A věřte mi, že jsem to zmanipuloval.

Dále jsem musel získat všechny přílohy. Vidíte, soubor výpisu MySQL, který jsem dostal od dodavatele, obsahoval gazillion INSERT výkazy, ale žádný ze skutečných souborů plánu, které uživatelé nahráli – databáze měla pouze relativní cesty k souborům. Použil jsem T-SQL k vytvoření řady příkazů PowerShell, které by volaly Invoke-WebRequest získat všechny soubory a uložit je lokálně (mnoho způsobů, jak stáhnout tuto kočku z kůže, ale tohle bylo snadné). Z tohoto:

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

To přineslo tuto sadu příkazů (spolu s předpříkazem k vyřešení tohoto problému s TLS); celá věc běžela docela rychle, ale tento přístup nedoporučuji pro žádnou kombinaci {masivní sady souborů} a/nebo {nízká šířka pásma}:

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

Tím byly staženy téměř všechny přílohy, ale některé byly při prvotním nahrání vynechány kvůli chybám na starém webu. Takže na novém webu můžete občas vidět odkaz na přílohu, která neexistuje.

Pak jsem použil Panic Transmit 5 k nahrání temp složky na nový web, a když se obsah nahraje, odkazy na /s/temp/1-proc.pesession bude pokračovat v práci.

Dále jsem přešel na SSL. Abychom mohli požádat o certifikát na novém webu WordPress, museli jsme aktualizovat DNS pro answer.sqlperformance.com, aby ukazoval na CNAME na našem hostiteli WordPress, WPEngine. Tady to bylo něco jako slepice a vejce – museli jsme trpět určitým výpadkem u https URL, které by selhaly bez certifikátu na novém webu. To bylo v pořádku, protože platnost certifikátu na starém webu vypršela, takže jsme na tom opravdu nebyli hůř. Také jsem na to musel počkat, dokud si nestáhnu všechny soubory ze starého webu, protože jakmile se DNS překlopí, nebude možné se k nim dostat jinak než nějakými zadními vrátky.

Zatímco jsem čekal, až se DNS rozšíří, začal jsem pracovat na logice, abych všechny otázky, odpovědi a komentáře převedl do něčeho použitelného ve WordPressu. Nejen, že se schémata tabulek lišila od WordPress, ale také typy entit jsou docela odlišné. Mojí vizí bylo spojit každou otázku – a jakékoli odpovědi a/nebo komentáře – do jediného příspěvku.

Záludná část je v tom, že tabulka uzlů obsahuje pouze všechny tři typy obsahu ve stejné tabulce s odkazy na rodiče a původní ("hlavní") rodiče. Jejich front-end kód pravděpodobně používá nějaký druh kurzoru k procházení a zobrazení obsahu v hierarchickém a chronologickém pořadí. Ve WordPressu bych takový luxus neměl, takže jsem musel propojit HTML najednou. Jako příklad uvádíme, jak data vypadala:

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

Nemohl jsem seřadit podle ID, typu nebo podle rodiče, protože někdy komentář přišel později na dřívější odpověď, první odpověď by nebyla vždy přijatou odpovědí a tak dále. Chtěl jsem tento výstup (kde ++ představuje jednu úroveň odsazení):

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

Začal jsem psát rekurzivní CTE a částečně kvůli příliš velkému množství Rekorderlig toho večera jsem požádal o pomoc kolegu produktového manažera Andyho Mallona (@AMtwo). Pomohl mi vyvinout tento dotaz, který by vrátil příspěvky ve správném pořadí zobrazení (a můžete vyzkoušet tento úryvek, změnit rodiče a/nebo přijatou odpověď, abyste viděli, že se stále vrátí správné pořadí):

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;

Výsledky:

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

Génius. Zkontroloval jsem asi tucet dalších a byl jsem rád, že jsem postoupil k dalšímu kroku. Už jsem Andymu několikrát poděkoval, ale dovolte mi to udělat znovu:Díky Andy!

Nyní, když jsem mohl vrátit celou sadu v pořadí, které se mi líbilo, musel jsem provést nějakou manipulaci s výstupem, abych použil prvky HTML a názvy tříd, které mi umožní smysluplným způsobem označit otázky, odpovědi, komentáře a odsazení. Konečným cílem byl výstup, který vypadal takto (a mějte na paměti, toto je jeden z jednodušších případů):

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

Nebudu procházet směšným počtem iterací, kterými jsem musel projít, abych se dostal na spolehlivou formu tohoto výstupu pro všech 5 000+ položek (což znamenalo téměř 1 000 příspěvků, jakmile bylo vše slepeno dohromady). Navíc jsem je potřeboval vygenerovat ve tvaru INSERT prohlášení, které jsem pak mohl vložit do phpMyAdmin na webu WordPress, což znamenalo držet se jejich bizarního syntaktického diagramu. Tato prohlášení musela obsahovat další dodatečné informace požadované WordPressem, které však nejsou přítomné nebo přesné ve zdrojových datech (např. post_type ). A tato administrátorská konzole vypršela kvůli příliš velkému množství dat, takže jsem je musel rozdělit na ~750 vložek najednou. Zde je postup, kterým jsem skončil (ve skutečnosti nejde o nic konkrétního, jen o ukázku toho, jak velká manipulace s importovanými daty byla nutná):

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

Výstup z toho ještě není kompletní a ještě není připravený nacpat do WordPressu:

Ukázkový výstup (kliknutím zvětšíte)

Potřeboval bych nějakou další pomoc od C#, abych převedl skutečný obsah (včetně markdown) do HTML a CSS, které bych mohl lépe ovládat, a napsal výstup (hromada INSERT příkazy, které náhodou obsahovaly spoustu HTML kódu) do souborů na disku, které jsem mohl otevřít a vložit do phpMyAdmin. Pro HTML, prostý text + markdown, který začínal takto:

Existuje [příspěvek na blogu zde][1], který o tom mluví, a také [tento příspěvek](https://somewhere).

VYBERTE něco z dbo.sometable;

[1]:https://jinde

Musí se stát tímto:

Je zde příspěvek na blogu , který o tom mluví, a také tento příspěvek .

VYBERTE něco z dbo.sometable;

Abych toho dosáhl, požádal jsem o pomoc MarkdownSharp, knihovnu s otevřeným zdrojovým kódem pocházející ze Stack Overflow, která zpracovává velkou část převodu markdown-to-HTML. Bylo to dobré pro mé potřeby, ale ne dokonalé; Ještě bych musel provést další manipulaci:

  • MarkdownSharp neumožňuje věci jako target=_blank , takže ty bych si po zpracování musel píchnout sám;
  • kód (cokoli s předponou čtyřmi mezerami) zdědí obaly
    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();
              }
            }
          }
        }
      }
    }

    Ano, je to ošklivá snůška kódu, ale nakonec jsem se dostal k sadě výstupů, ze kterých by phpMyAdmin nezvracel a který by WordPress prezentoval pěkně (dost). Jednoduše jsem zavolal program C# několikrát s různými rozsahy parametrů:

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

    Poté jsem otevřel každý ze souborů, vložil je do phpMyAdmin a zmáčkl GO:

    phpMyAdmin (kliknutím zvětšíte)

    Samozřejmě jsem musel přidat nějaké CSS do WordPress, abych pomohl rozlišovat mezi otázkami, komentáři a odpověďmi a také odsadit komentáře, aby se zobrazily odpovědi na otázky i odpovědi, vnořit komentáře jako odpovědi na komentáře a tak dále. Zde je návod, jak vypadá úryvek, když si projdete otázky za měsíc:

    Dlaždice s otázkou (kliknutím zvětšíte)

    A pak příklad příspěvku, který ukazuje vložené obrázky, více příloh, vnořené komentáře a odpověď:

    Ukázková otázka a odpověď (kliknutím sem přejdete)

    Stále se snažím obnovit několik příspěvků, které byly odeslány na web po pořízení poslední zálohy, ale vítám vás, když si je můžete procházet. Dejte nám prosím vědět, pokud si všimnete, že něco chybí nebo není na svém místě, nebo nám jen sdělte, že obsah je pro vás stále užitečný. Doufáme, že znovu zavedeme funkci nahrávání plánu z Průzkumníka plánu, ale bude to vyžadovat nějakou práci s API na novém webu podpory, takže pro vás dnes nemám odhadovaný čas příjezdu.

      Answers.SQLPerformance.com

  1. Jaký typ JOIN použít

  2. Vyberte 10 nejlepších záznamů pro každou kategorii v MySQL

  3. Jak Mod() funguje v PostgreSQL

  4. MySQL MariaDB – Dotaz pomocí Temp Table