{"id":65,"date":"2009-10-29T16:13:18","date_gmt":"2009-10-29T21:13:18","guid":{"rendered":"http:\/\/sqlity.net\/en\/?p=65"},"modified":"2014-11-13T14:04:40","modified_gmt":"2014-11-13T19:04:40","slug":"a-working-table-compare-approach","status":"publish","type":"post","link":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/","title":{"rendered":"A Working Table Compare Approach"},"content":{"rendered":"<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><span style=\"color:#000000;\"><span style=\"font-size:10pt;\">A while back I wrote an article about <a href=\"http:\/\/www2.sqlity.net\/articles\/TableCompareComplexity\">the underestimated complexity of a table compare algorithm<\/a>. At the time I did not show a way of how to actually implement such an algorithm.<\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><span style=\"color:#000000;\"><span style=\"font-size:10pt;\">Over the last couple of weeks I ran into several blog post that lead me to introduce a way to write one now...<\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">The goal is to write a stored procedure, that takes the names of two tables as input and identifies all the rows that are in the one table, but not in the other. Both tables need to have the same schema. A primary key is not necessarily defined, which means that there is the potential for duplicate rows in the tables.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">The first Idea that comes to mind is, to somehow compare each column of the one table with the matching column in the other table. This needs to take into account, that both values could be NULL which should be considered a match. It needs to somehow keep track of rows it matched already to not use a row more than once matching against to separate but identical rows in the other table. It also needs to automatically generate the code to do this compare as the tables where passed in as parameters. I actually have implemented this solution, and believe me, it is plain ugly.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">Last week Tony Rogerson wrote about <a href=\"http:\/\/sqlblogcasts.com\/blogs\/tonyrogerson\/archive\/2009\/10\/21\/detecting-changed-rows-in-a-trigger-using-hashbytes-and-without-eventdata-and-or-s.aspx\">detecting changed rows in a trigger<\/a>. He is suggesting to concatenate all the column values of each single row together an then use the HashBytes function to calculate a hash value as a row value indicator that then can be used to compare rows to each other easily. Using a hash value for comparisons is an industry standard and is also used in several places in the SQL Server engine.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">However, it is not unproblematic to do such a data compression before a comparison operation. He himself points out, that HashBytes has an input length limitation of 8000 bytes and suggests to use the CHECKSUM function instead. The CHECKSUM function returns an integer. That means you can get only 2^32 = 4,294,967,296 different values. While that sounds like a large number, it really is not.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">In his blog entry <a href=\"http:\/\/consultingblogs.emc.com\/jamesrowlandjones\/archive\/2009\/05\/28\/the-curious-case-of-the-dubious-deadlock-and-the-not-so-logical-lock.aspx\">The curious case of the Dubious Deadlock<\/a> James Rowland-Jones gives a very good explanation about what can happen if you get to comfortable with hash values. Make sure to also check out the follow-up post to this, in which Remus Rusanu explains, <a href=\"http:\/\/rusanu.com\/2009\/05\/29\/lockres-collision-probability-magic-marker-16777215\/\">why 281,474,976,710,656 is a small number<\/a>.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">So, what can we do about this? One option is to not calculate a checksum in the first place and just compare the concatenated column values to each other. This should not be slower that first calculating a checksum over the strings and then comparing these numbers. But this approach is also not complete. One of the reasons is, that XML is limited in length. While I admit that it is very unlikely that you will find a table with rows that contain more than 2GB of data, it is a restriction that you need to consider when designing a table compare algorithm. Also, if you take the development of hard drive or ram size into consideration, 2GB will not be huge for a long time. The other issue with this approach is, that you will have trouble converting some string or binary values to XML. Most bytes with a value under 30 cannot be converted to XML. To get the full list, run the following code:<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><\/p>\n<table border=\"0\" width=\"95%\" cellpadding=\"5\" cellspacing=\"0\">\n<col width=\"256*\" \/>\n<tr>\n<td bgcolor=\"#e6e6e6\" width=\"100%\">\n<p style=\"margin-bottom: 0in\" lang=\"zxx\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span>DECLARE<\/span><span style=\"color:#000000;\"><span>@cmd<\/span><\/span> <span>NVARCHAR<\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff00ff;\"><span>MAX<\/span><\/span><span style=\"color:#808080;\"><span>);<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>@cmd<\/span><\/span> <span style=\"color:#808080;\"><span>=<\/span><\/span><span style=\"color:#ff0000;\"><span>'DECLARE @x XML;'<\/span><\/span><span style=\"color:#808080;\"><span>+(<\/span><\/span><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff0000;\"><span>'BEGIN TRY SET @x = CAST( CHAR('<\/span><\/span><span style=\"color:#808080;\"><span>+<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#ff00ff;\"><span>CAST<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>n<\/span><\/span><span style=\"color:#808080;\"><span>-<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span> <span style=\"color:#0000ff;\"><span>AS<\/span><\/span><span style=\"color:#0000ff;\"><span>VARCHAR<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#808080;\"><span>+<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#ff0000;\"><span>') AS XML);END TRY BEGIN CATCH PRINT '<\/span><\/span><span style=\"color:#808080;\"><span>+<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#ff00ff;\"><span>CAST<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>n<\/span><\/span><span style=\"color:#808080;\"><span>-<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span> <span style=\"color:#0000ff;\"><span>AS<\/span><\/span><span style=\"color:#0000ff;\"><span>VARCHAR<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#808080;\"><span>+<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#ff0000;\"><span>';END CATCH;'<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>dbo<\/span><\/span><span style=\"color:#808080;\"><span>.<\/span><\/span><span style=\"color:#000000;\"><span>GetNums<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>256<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>FOR<\/span><\/span><span style=\"color:#0000ff;\"><span>XML<\/span><\/span><span style=\"color:#0000ff;\"><span>PATH<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff0000;\"><span>''<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#808080;\"><span>);<\/span><\/span><\/span><\/span><\/p>\n<p><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>EXEC<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>@cmd<\/span><\/span><span style=\"color:#808080;\"><span>);<\/span><\/span><\/span><\/span><\/p>\n<\/td>\n<\/tr>\n<\/table>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">This code makes us of Itzik Ben-Gan's <a href=\"http:\/\/www.sqlmag.com\/Articles\/ArticleID\/103056\/103056.html?Ad=1\">Virtual Auxiliary Table of Numbers<\/a> dbo.GetNums to produce 256 different possible byte values. Then it tries to convert each of them to XML. If this conversion fails, the number gets printed out. As you can see, there are quite a few that fail.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">Also about a week ago Merrill Aldrich wrote about <a href=\"http:\/\/sqlblog.com\/blogs\/merrill_aldrich\/archive\/2009\/10\/19\/simple-monitoring-for-data-changes.aspx\">Simple Monitoring for Data Changes<\/a>, and his solution gets around all the problems that I mentioned above. Almost all, that is.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">His solution uses the GROUP BY statement to make SQL Server do all the comparisons. This works for extremely large rows and it works for &quot;funky&quot; binary data. But it does not work for TEXT, NTEXT and IMAGE. However, There is an easy fix for that:<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><\/p>\n<table border=\"0\" width=\"95%\" cellpadding=\"5\" cellspacing=\"0\">\n<col width=\"256*\" \/>\n<tr>\n<td bgcolor=\"#e6e6e6\" width=\"100%\">\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>IF<\/span><\/span><span style=\"color:#ff00ff;\"><span>OBJECT_ID<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff0000;\"><span>'tempdb..#t1'<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#808080;\"><span>IS<\/span><\/span><span style=\"color:#808080;\"><span>NOT<\/span><\/span><span style=\"color:#808080;\"><span>NULL<\/span><\/span><span style=\"color:#0000ff;\"><span>DROP<\/span><\/span><span style=\"color:#0000ff;\"><span>TABLE<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>CREATE<\/span><\/span><span style=\"color:#0000ff;\"><span>TABLE<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>id<\/span><\/span> <span style=\"color:#0000ff;\"><span>INT<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>t<\/span><\/span> <span style=\"color:#0000ff;\"><span>TEXT<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>i<\/span><\/span> <span style=\"color:#0000ff;\"><span>IMAGE<\/span><\/span><span style=\"color:#808080;\"><span>);<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\">GO<\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#ff0000;\"><span>'text'<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>0x1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#ff0000;\"><span>'test'<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>0x2<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>3<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#ff0000;\"><span>'text'<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>0x2<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\">GO<\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff00ff;\"><span>CAST<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>t<\/span><\/span> <span style=\"color:#0000ff;\"><span>AS<\/span><\/span><span style=\"color:#0000ff;\"><span>VARCHAR<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff00ff;\"><span>MAX<\/span><\/span><span style=\"color:#808080;\"><span>))<\/span><\/span><span style=\"color:#000000;\"><span>t<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#ff00ff;\"><span>COUNT<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>GROUP<\/span><\/span><span style=\"color:#0000ff;\"><span>BY<\/span><\/span><span style=\"color:#ff00ff;\"><span>CAST<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>t<\/span><\/span> <span style=\"color:#0000ff;\"><span>AS<\/span><\/span><span style=\"color:#0000ff;\"><span>VARCHAR<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff00ff;\"><span>MAX<\/span><\/span><span style=\"color:#808080;\"><span>));<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\">GO<\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff00ff;\"><span>CAST<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>i<\/span><\/span> <span style=\"color:#0000ff;\"><span>AS<\/span><\/span><span style=\"color:#0000ff;\"><span>VARBINARY<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff00ff;\"><span>MAX<\/span><\/span><span style=\"color:#808080;\"><span>)),<\/span><\/span><span style=\"color:#ff00ff;\"><span>COUNT<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><\/span><\/span><\/p>\n<p><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>GROUP<\/span><\/span><span style=\"color:#0000ff;\"><span>BY<\/span><\/span><span style=\"color:#ff00ff;\"><span>CAST<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>i<\/span><\/span> <span style=\"color:#0000ff;\"><span>AS<\/span><\/span><span style=\"color:#0000ff;\"><span>VARBINARY<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff00ff;\"><span>MAX<\/span><\/span><span style=\"color:#808080;\"><span>));<\/span><\/span><\/span><\/span><\/p>\n<\/td>\n<\/tr>\n<\/table>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">The other thing that Merrill's solution does not handle is key free tables with duplicate rows. But that also can be fixed easily. Let's look at an example:<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><\/p>\n<table border=\"0\" width=\"95%\" cellpadding=\"5\" cellspacing=\"0\">\n<col width=\"256*\" \/>\n<tr>\n<td bgcolor=\"#e6e6e6\" width=\"100%\">\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>IF<\/span><\/span><span style=\"color:#ff00ff;\"><span>OBJECT_ID<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff0000;\"><span>'tempdb..#t1'<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#808080;\"><span>IS<\/span><\/span><span style=\"color:#808080;\"><span>NOT<\/span><\/span><span style=\"color:#808080;\"><span>NULL<\/span><\/span><span style=\"color:#0000ff;\"><span>DROP<\/span><\/span><span style=\"color:#0000ff;\"><span>TABLE<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>IF<\/span><\/span><span style=\"color:#ff00ff;\"><span>OBJECT_ID<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#ff0000;\"><span>'tempdb..#t2'<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#808080;\"><span>IS<\/span><\/span><span style=\"color:#808080;\"><span>NOT<\/span><\/span><span style=\"color:#808080;\"><span>NULL<\/span><\/span><span style=\"color:#0000ff;\"><span>DROP<\/span><\/span><span style=\"color:#0000ff;\"><span>TABLE<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\">GO<\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>CREATE<\/span><\/span><span style=\"color:#0000ff;\"><span>TABLE<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span> <span style=\"color:#0000ff;\"><span>INT<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span> <span style=\"color:#0000ff;\"><span>INT<\/span><\/span><span style=\"color:#808080;\"><span>);<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>CREATE<\/span><\/span><span style=\"color:#0000ff;\"><span>TABLE<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span> <span style=\"color:#0000ff;\"><span>INT<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span> <span style=\"color:#0000ff;\"><span>INT<\/span><\/span><span style=\"color:#808080;\"><span>);<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\">GO<\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>3<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>3<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#0000ff;\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\">GO<\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>2<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>3<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>3<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>4<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>4<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<p><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>INSERT<\/span><\/span><span style=\"color:#0000ff;\"><span>INTO<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#000000;\"><span>4<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>4<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<\/td>\n<\/tr>\n<\/table>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">Merrill's generated code looks like this:<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><\/p>\n<table border=\"0\" width=\"95%\" cellpadding=\"5\" cellspacing=\"0\">\n<col width=\"256*\" \/>\n<tr>\n<td bgcolor=\"#e6e6e6\" width=\"100%\">\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff00ff;\"><span>MIN<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff0000;\"><span>'#t1'<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span> <span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>UNION<\/span><\/span><span style=\"color:#808080;\"><span>ALL<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff0000;\"><span>'#t2'<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span> <span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#000000;\"><span>allRows<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>GROUP<\/span><\/span><span style=\"color:#0000ff;\"><span>BY<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span><\/span><\/span><\/p>\n<p><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>HAVING<\/span><\/span><span style=\"color:#ff00ff;\"><span>COUNT<\/span><\/span><span style=\"color:#808080;\"><span>(*)<\/span><\/span><span style=\"color:#808080;\"><span>=<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<\/td>\n<\/tr>\n<\/table>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">It is supposed to return all mismatching rows, but it returns non. To fix this we can just throw a per table count of unique rows into the mix:<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in; font-style: normal; font-weight: normal; text-decoration: none\"><\/p>\n<table border=\"0\" width=\"95%\" cellpadding=\"5\" cellspacing=\"0\">\n<col width=\"256*\" \/>\n<tr>\n<td bgcolor=\"#e6e6e6\" width=\"100%\">\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff00ff;\"><span>MIN<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>cnt<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff0000;\"><span>'#t1'<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#ff00ff;\"><span>COUNT<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#000000;\"><span>cnt<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span> <span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>#t1<\/span><\/span> <span style=\"color:#0000ff;\"><span>GROUP<\/span><\/span><span style=\"color:#0000ff;\"><span>BY<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>UNION<\/span><\/span><span style=\"color:#808080;\"><span>ALL<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>SELECT<\/span><\/span><span style=\"color:#ff0000;\"><span>'#t2'<\/span><\/span><span style=\"color:#000000;\"><span>table_name<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#ff00ff;\"><span>COUNT<\/span><\/span><span style=\"color:#808080;\"><span>(<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#000000;\"><span>cnt<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span> <span style=\"color:#0000ff;\"><span>FROM<\/span><\/span><span style=\"color:#000000;\"><span>#t2<\/span><\/span> <span style=\"color:#0000ff;\"><span>GROUP<\/span><\/span><span style=\"color:#0000ff;\"><span>BY<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#808080;\"><span>)<\/span><\/span><span style=\"color:#000000;\"><span>allRows<\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>GROUP<\/span><\/span><span style=\"color:#0000ff;\"><span>BY<\/span><\/span><span style=\"color:#000000;\"><span>c1<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>c2<\/span><\/span><span style=\"color:#808080;\"><span>,<\/span><\/span><span style=\"color:#000000;\"><span>cnt<\/span><\/span><\/span><\/span><\/p>\n<p><span style=\"font-family:Courier New, monospace;\"><span style=\"font-size: 8pt; font-size:8pt;\"><span style=\"color:#0000ff;\"><span>HAVING<\/span><\/span><span style=\"color:#ff00ff;\"><span>COUNT<\/span><\/span><span style=\"color:#808080;\"><span>(*)<\/span><\/span><span style=\"color:#808080;\"><span>=<\/span><\/span><span style=\"color:#000000;\"><span>1<\/span><\/span><span style=\"color:#808080;\"><span>;<\/span><\/span><\/span><\/span><\/p>\n<\/td>\n<\/tr>\n<\/table>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">This now returns all rows that are only in one table and not in the other, as well as all rows that are in both tables but more often in one than in the other. It also returns the number of occurrences per row and table of the mismatching rows.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">To make a fully working solution out of this, you have to generate this code dynamically. You can follow Merrill's example for that. Don't forget to incorporate the CAST into one of the ...(MAX) data types for TEXT, NTEXT and IMAGE type columns. The last thing you need to think of are unique column names for the two additional columns. You do not want this solution to break, if you compare tables that already contain a column called &quot;table_name&quot; or &quot;cnt&quot;.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><span style=\"color:#000000;\"><span style=\"text-decoration: none\"><span style=\"font-size:10pt;\"><span style=\"font-style: normal\"><span style=\"font-weight: normal\">This solution fulfills all the requirements that I have for a table compare implementation. It can handle any data type and all data sizes. It performs well. And most importantly it is easy to understand an maintain.<\/span><\/span><\/span><\/span><\/span><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n<p style=\"margin-bottom: 0in\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A while back I wrote an article about the underestimated complexity of a table compare algorithm. At the time I did not show a way of how to actually implement such an algorithm. Over the last couple of weeks I <a href=\"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/\">[more&#8230;]<\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[5],"tags":[],"class_list":["post-65","post","type-post","status-publish","format-standard","hentry","category-general"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>A Working Table Compare Approach<\/title>\n<meta name=\"description\" content=\"This post introduces a stored procedure, that compares two tables and identifies all the rows that are in one table, but not in the other.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"A Working Table Compare Approach\" \/>\n<meta property=\"og:description\" content=\"This post introduces a stored procedure, that compares two tables and identifies all the rows that are in one table, but not in the other.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/\" \/>\n<meta property=\"og:site_name\" content=\"sqlity.net\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/sqlity.net\" \/>\n<meta property=\"article:published_time\" content=\"2009-10-29T21:13:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2014-11-13T19:04:40+00:00\" \/>\n<meta name=\"author\" content=\"Sebastian Meine\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@sqlity\" \/>\n<meta name=\"twitter:site\" content=\"@sqlity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sebastian Meine\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/\"},\"author\":{\"name\":\"Sebastian Meine\",\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/#\\\/schema\\\/person\\\/bcffd8c572bc2f1bd10fdba80135e53c\"},\"headline\":\"A Working Table Compare Approach\",\"datePublished\":\"2009-10-29T21:13:18+00:00\",\"dateModified\":\"2014-11-13T19:04:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/\"},\"wordCount\":1238,\"commentCount\":0,\"articleSection\":[\"General\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/\",\"url\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/\",\"name\":\"A Working Table Compare Approach\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/#website\"},\"datePublished\":\"2009-10-29T21:13:18+00:00\",\"dateModified\":\"2014-11-13T19:04:40+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/#\\\/schema\\\/person\\\/bcffd8c572bc2f1bd10fdba80135e53c\"},\"description\":\"This post introduces a stored procedure, that compares two tables and identifies all the rows that are in one table, but not in the other.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/65\\\/a-working-table-compare-approach\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/sqlity.net\\\/en\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"A Working Table Compare Approach\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/sqlity.net\\\/en\\\/\",\"name\":\"sqlity.net\",\"description\":\"Quality for SQL\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/sqlity.net\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/sqlity.net\\\/en\\\/#\\\/schema\\\/person\\\/bcffd8c572bc2f1bd10fdba80135e53c\",\"name\":\"Sebastian Meine\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4ab0a6d02dd494849a584a2c3c8bc3bdcef1d0aa5f87e98bf905dbdb9ad2ce3a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4ab0a6d02dd494849a584a2c3c8bc3bdcef1d0aa5f87e98bf905dbdb9ad2ce3a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4ab0a6d02dd494849a584a2c3c8bc3bdcef1d0aa5f87e98bf905dbdb9ad2ce3a?s=96&d=mm&r=g\",\"caption\":\"Sebastian Meine\"},\"sameAs\":[\"http:\\\/\\\/sqlity.net\",\"https:\\\/\\\/x.com\\\/sqlity\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"A Working Table Compare Approach","description":"This post introduces a stored procedure, that compares two tables and identifies all the rows that are in one table, but not in the other.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/","og_locale":"en_US","og_type":"article","og_title":"A Working Table Compare Approach","og_description":"This post introduces a stored procedure, that compares two tables and identifies all the rows that are in one table, but not in the other.","og_url":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/","og_site_name":"sqlity.net","article_publisher":"https:\/\/www.facebook.com\/sqlity.net","article_published_time":"2009-10-29T21:13:18+00:00","article_modified_time":"2014-11-13T19:04:40+00:00","author":"Sebastian Meine","twitter_card":"summary_large_image","twitter_creator":"@sqlity","twitter_site":"@sqlity","twitter_misc":{"Written by":"Sebastian Meine","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/#article","isPartOf":{"@id":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/"},"author":{"name":"Sebastian Meine","@id":"https:\/\/sqlity.net\/en\/#\/schema\/person\/bcffd8c572bc2f1bd10fdba80135e53c"},"headline":"A Working Table Compare Approach","datePublished":"2009-10-29T21:13:18+00:00","dateModified":"2014-11-13T19:04:40+00:00","mainEntityOfPage":{"@id":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/"},"wordCount":1238,"commentCount":0,"articleSection":["General"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/","url":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/","name":"A Working Table Compare Approach","isPartOf":{"@id":"https:\/\/sqlity.net\/en\/#website"},"datePublished":"2009-10-29T21:13:18+00:00","dateModified":"2014-11-13T19:04:40+00:00","author":{"@id":"https:\/\/sqlity.net\/en\/#\/schema\/person\/bcffd8c572bc2f1bd10fdba80135e53c"},"description":"This post introduces a stored procedure, that compares two tables and identifies all the rows that are in one table, but not in the other.","breadcrumb":{"@id":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/sqlity.net\/en\/65\/a-working-table-compare-approach\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/sqlity.net\/en\/"},{"@type":"ListItem","position":2,"name":"A Working Table Compare Approach"}]},{"@type":"WebSite","@id":"https:\/\/sqlity.net\/en\/#website","url":"https:\/\/sqlity.net\/en\/","name":"sqlity.net","description":"Quality for SQL","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/sqlity.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/sqlity.net\/en\/#\/schema\/person\/bcffd8c572bc2f1bd10fdba80135e53c","name":"Sebastian Meine","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/4ab0a6d02dd494849a584a2c3c8bc3bdcef1d0aa5f87e98bf905dbdb9ad2ce3a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/4ab0a6d02dd494849a584a2c3c8bc3bdcef1d0aa5f87e98bf905dbdb9ad2ce3a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/4ab0a6d02dd494849a584a2c3c8bc3bdcef1d0aa5f87e98bf905dbdb9ad2ce3a?s=96&d=mm&r=g","caption":"Sebastian Meine"},"sameAs":["http:\/\/sqlity.net","https:\/\/x.com\/sqlity"]}]}},"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2wXuw-13","jetpack-related-posts":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/posts\/65","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/comments?post=65"}],"version-history":[{"count":0,"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/posts\/65\/revisions"}],"wp:attachment":[{"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/media?parent=65"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/categories?post=65"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sqlity.net\/en\/wp-json\/wp\/v2\/tags?post=65"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}