pairwiseCompareClusters <- function(tbl, colsToAgg , dateCol
  , colsToPull=c(dateCol, "storeid", "transactiontypeid")
  , schema="production"
  , clusters=c(7, 9)
  , verbose=TRUE
  ) {

  if (length(clusters) > 2)
    stop ("Only two clusters at a time") ## Todo, iterate over the combos

  ## Check that we user can read from each cluster
  if (is.null(names(clusters)))
    names(clusters) <- sprintf("Cluster_%02i", clusters)
  canread <- unlist(sapply(clusters, function(cl) qCanRead(tbl=tbl, schema=schema, cluster=cl)), use.names=TRUE)
  if (any(!canread)) {
    stop (warningCols("user cannot read for the following: ", nwhich(!canread)))
  }


  suffixes <- sprintf(".%02i", clusters)

  ## Wrapper function to append suffixes
  psfx1 <- function(string) paste0(string, suffixes[[1]])
  psfx2 <- function(string) paste0(string, suffixes[[2]])

  ## Add a rowCount to colsToAgg
  if ("rows" %ni% colsToAgg && "rows" %ni% names(colsToAgg))
    colsToAgg <- c(rows=".rowcount", colsToAgg)

  ## Create Query
  Qry <- makeQry(tbl=tbl, schema=schema, colsToAgg=colsToAgg, colsToPull=colsToPull, limit=NULL, key=colsToPull, dateCol=dateCol)
  ## Clean up colsToAgg after the query
  colsToAgg <- colNamesFromVector(colsToAgg)

  ## Execute the Querys
  DT1 <- runQry(Qry, cluster=clusters[[1]])
  DT2 <- runQry(Qry, cluster=clusters[[2]])

  ## Merge, and organize the columns for fun
  DT.clusters <- merge(DT1, DT2, suffix=suffixes, all=TRUE)
  setcolorderpt(DT.clusters, endcols=zipperCombine(psfx1(colsToAgg), psfx2(colsToAgg)))

  ## Check that there are no unmatched rows
  col.row1 <- psfx1("rows")
  col.row2 <- psfx2("rows")
  DT.clusters[, psfx1("missing_from") := is.na(get(col.row1)) & !is.na(get(col.row2))]
  DT.clusters[, psfx2("missing_from") := is.na(get(col.row2)) & !is.na(get(col.row1))]
  if (any(DT.clusters[, get(psfx1("missing_from")) | get(psfx2("missing_from"))])) {
    err.msg.unmatched <- sprintf("\nThere are %5i groups %s  and \nThere are %5i groups %s"
      , DT.clusters[get(psfx1("missing_from")), .N] 
      , psfx1("missing_from")
      , DT.clusters[get(psfx2("missing_from")), .N] 
      , psfx2("missing_from")
      )
    warning (err.msg.unmatched)
  }

  ## Check each pair of columns
  for (col in colsToAgg) {
    DT.clusters[, paste0(col, ".SAME") := equals0(.SD[[1]] - .SD[[2]], na.check=TRUE), .SDcols=paste0(col, suffixes)]
  }
  ## Check which row is good across all columns
  DT.clusters[, ROW_IS_GOOD := rowSums(.SD) == length(colsToAgg), .SDcols=paste0(colsToAgg, ".SAME")]

  ## There should be no rows that are not good
  if (nrow(DT.clusters[!(ROW_IS_GOOD)])) {
    err.msg <- DT.clusters[!(ROW_IS_GOOD), sprintf("For table '%s':\n  There are %i rows (out of %i) that are not good, across %i different dates\n  mindate is %s and maxdate is %s.\n  Also, there are %i different stores and %i different transaction types", tbl, .N, nrow(DT.clusters), lunique(get(dateCol)), min(get(dateCol)), max(get(dateCol)), lunique(storeid), lunique(transactiontypeid))]
    warning(err.msg)
    return(invisible(DT.clusters))
  }
  ## ELSE
  if (rowCount1 != rowCount2)
    warning ("The row counts are off")

  ## Message that all is well
  if (!(rowCount1 != rowCount2 || nrow(DT.clusters[!(ROW_IS_GOOD)])))
    message (sprintf("Clusters %02i & %02i match for table '%s'", clusters[[1]], clusters[[2]], tbl))

  return(invisible(DT.clusters))
}