2013-07-16 13 views
14

Come posso Left Outer Join (penso che sia Left Outer Join ma non sono sicuro al 100%) due tabelle di dati con le seguenti tabelle e condizioni mantenendo tutte le colonne da entrambe le tabelle?Come esterno sinistro Unire due DataTable in C#?

dtblLeft:

id col1 anotherColumn2 
1 1  any2 
2 1  any2 
3 2  any2 
4 3  any2 
5 3  any2 
6 3  any2 
7   any2 

dtblRight:

col1 col2  anotherColumn1 
1  Hi  any1 
2  Bye  any1 
3  Later  any1 
4  Never  any1 

dtblJoined:

id col1 col2  anotherColumn1  anotherColumn2 
1 1  Hi  any1    any2 
2 1  Hi  any1    any2 
3 2  Bye  any1    any2 
4 3  Later any1    any2 
5 3  Later any1    any2 
6 3  Later any1    any2 
7          any2 

Condizioni:

  • In dtblLeft, col1 non è richiesto per avere valori univoci.
  • In dtblRight, col1 ha valori univoci.
  • Se dtblLeft manca una chiave esterna in col1 o ne esiste una che non esiste in dtblRight, verranno inseriti campi vuoti o nulli.
  • Partecipare a col1.

posso utilizzare le operazioni DataTable regolari, LINQ, o qualsiasi altra cosa.

Ho provato questo ma rimuove i duplicati:

dtblA.PrimaryKey = new DataColumn[] {dtblA.Columns["col1"]} 

DataTable dtblJoined = new DataTable(); 
dtblJoined.Merge(dtblA, false, MissingSchemaAction.AddWithKey); 
dtblJoined.Merge(dtblB, false, MissingSchemaAction.AddWithKey); 

EDIT 1:

Questo è vicino alla I quello che voglio, ma ha solo le colonne da una delle tabelle (trovato a questo link):

dtblJoined = (from t1 in dtblA.Rows.Cast<DataRow>() 
        join t2 in dtblB.Rows.Cast<DataRow>() on t1["col1"] equals t2["col1"] 
        select t1).CopyToDataTable(); 

EDIT 2:

una risposta da questo link sembra funzionare per me, ma ho dovuto cambiare un po 'come segue:

DataTable targetTable = dtblA.Clone(); 
var dt2Columns = dtblB.Columns.OfType<DataColumn>().Select(dc => 
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping)); 
var dt2FinalColumns = from dc in dt2Columns.AsEnumerable() 
        where targetTable.Columns.Contains(dc.ColumnName) == false 
        select dc; 

targetTable.Columns.AddRange(dt2FinalColumns.ToArray()); 

var rowData = from row1 in dtblA.AsEnumerable() 
          join row2 in dtblB.AsEnumerable() 
          on row1["col1"] equals row2["col1"] 
          select row1.ItemArray.Concat(row2.ItemArray.Where(r2 => row1.ItemArray.Contains(r2) == false)).ToArray(); 

foreach (object[] values in rowData) 
     targetTable.Rows.Add(values); 

Ho trovato anche questo link e potrei provare che fuori dal momento che sembra più conciso.

EDIT 3 (2013/11/18):

tabelle aggiornate per riflettere più situazioni.

+0

Anche questo è univoco naturale. Dicci cosa dovrebbe succedere quando dtblA ha '4' e dtblB no. – Shoe

+0

aggiornato per mostrare dtblA con 4 – Soenhay

+0

Nel caso in cui abbiate perso questi altri post su SO relativi allo stesso numero [Controllare qui] (http://stackoverflow.com/questions/10404039/left-join-datatables-how-do- i-get-this-to-work) [e qui] (http://stackoverflow.com/questions/9055180/datatables-left-join-c-sharp) – Rwiti

risposta

10

Grazie a tutti per il vostro aiuto. Ecco quello che mi è venuta in base a più risorse:

public static class DataTableHelper 
{ 
    public enum JoinType 
    { 
     /// <summary> 
     /// Same as regular join. Inner join produces only the set of records that match in both Table A and Table B. 
     /// </summary> 
     Inner = 0, 
     /// <summary> 
     /// Same as Left Outer join. Left outer join produces a complete set of records from Table A, with the matching records (where available) in Table B. If there is no match, the right side will contain null. 
     /// </summary> 
     Left = 1 
    } 

    /// <summary> 
    /// Joins the passed in DataTables on the colToJoinOn. 
    /// <para>Returns an appropriate DataTable with zero rows if the colToJoinOn does not exist in both tables.</para> 
    /// </summary> 
    /// <param name="dtblLeft"></param> 
    /// <param name="dtblRight"></param> 
    /// <param name="colToJoinOn"></param> 
    /// <param name="joinType"></param> 
    /// <returns></returns> 
    /// <remarks> 
    /// <para>http://stackoverflow.com/questions/2379747/create-combined-datatable-from-two-datatables-joined-with-linq-c-sharp?rq=1</para> 
    /// <para>http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx</para> 
    /// <para>http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html</para> 
    /// <para>http://stackoverflow.com/questions/406294/left-join-and-left-outer-join-in-sql-server</para> 
    /// </remarks> 
    public static DataTable JoinTwoDataTablesOnOneColumn(DataTable dtblLeft, DataTable dtblRight, string colToJoinOn, JoinType joinType) 
    { 
     //Change column name to a temp name so the LINQ for getting row data will work properly. 
     string strTempColName = colToJoinOn + "_2"; 
     if (dtblRight.Columns.Contains(colToJoinOn)) 
      dtblRight.Columns[colToJoinOn].ColumnName = strTempColName; 

     //Get columns from dtblLeft 
     DataTable dtblResult = dtblLeft.Clone(); 

     //Get columns from dtblRight 
     var dt2Columns = dtblRight.Columns.OfType<DataColumn>().Select(dc => new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping)); 

     //Get columns from dtblRight that are not in dtblLeft 
     var dt2FinalColumns = from dc in dt2Columns.AsEnumerable() 
           where !dtblResult.Columns.Contains(dc.ColumnName) 
           select dc; 

     //Add the rest of the columns to dtblResult 
     dtblResult.Columns.AddRange(dt2FinalColumns.ToArray()); 

     //No reason to continue if the colToJoinOn does not exist in both DataTables. 
     if (!dtblLeft.Columns.Contains(colToJoinOn) || (!dtblRight.Columns.Contains(colToJoinOn) && !dtblRight.Columns.Contains(strTempColName))) 
     { 
      if (!dtblResult.Columns.Contains(colToJoinOn)) 
       dtblResult.Columns.Add(colToJoinOn); 
      return dtblResult; 
     } 

     switch (joinType) 
     { 

      default: 
      case JoinType.Inner: 
       #region Inner 
       //get row data 
       //To use the DataTable.AsEnumerable() extension method you need to add a reference to the System.Data.DataSetExtension assembly in your project. 
       var rowDataLeftInner = from rowLeft in dtblLeft.AsEnumerable() 
             join rowRight in dtblRight.AsEnumerable() on rowLeft[colToJoinOn] equals rowRight[strTempColName] 
             select rowLeft.ItemArray.Concat(rowRight.ItemArray).ToArray(); 


       //Add row data to dtblResult 
       foreach (object[] values in rowDataLeftInner) 
        dtblResult.Rows.Add(values); 

       #endregion 
       break; 
      case JoinType.Left: 
       #region Left 
       var rowDataLeftOuter = from rowLeft in dtblLeft.AsEnumerable() 
             join rowRight in dtblRight.AsEnumerable() on rowLeft[colToJoinOn] equals rowRight[strTempColName] into gj 
             from subRight in gj.DefaultIfEmpty() 
             select rowLeft.ItemArray.Concat((subRight== null) ? (dtblRight.NewRow().ItemArray) :subRight.ItemArray).ToArray(); 


       //Add row data to dtblResult 
       foreach (object[] values in rowDataLeftOuter) 
        dtblResult.Rows.Add(values); 

       #endregion 
       break; 
     } 

     //Change column name back to original 
     dtblRight.Columns[strTempColName].ColumnName = colToJoinOn; 

     //Remove extra column from result 
     dtblResult.Columns.Remove(strTempColName); 

     return dtblResult; 
    } 
} 

EDIT 3:

Questo metodo ora funziona correttamente ed è ancora veloce quando le tabelle hanno 2000+ righe. Eventuali raccomandazioni/suggerimenti/miglioramenti sarebbero apprezzati.

EDIT 4:

ho avuto un certo scenario che mi ha portato a realizzare la versione precedente è stato davvero facendo un join interno. La funzione è stata modificata per risolvere questo problema. Ho usato informazioni a questo link per capirlo.

0

Si potrebbe forse usare LINQ e fare qualcosa del genere:

var dtblJoined = from dB in dtblB.AsEnumerable() 
       join dA in dtblA.AsEnumerable() on dA.col1 equals dB.col1 into dAB 
       from d in dAB.DefaultIfEmpty() 
       select new (col1 = dB.col1, ; col2 = (dB.col1 == dA.col1) ? dA.col2 : null); 

Questa sarebbe tornato un IEnumerable come il risultato non di una DataTable, ma dovrebbe arrivare più vicino a quello che stai cercando credo. Potrebbe essere necessario un piccolo ritocco però.

1

Si tratta semplicemente di un inner join tra le 2 tabelle:

var query = (from x in a.AsEnumerable() 
       join y in b.AsEnumerable() on x.Field<int>("col1") equals y.Field<int>("col1") 
       select new { col1= y.Field<int>("col1"), col2=x.Field<int>("col2") }).ToList(); 

produce:

col1 col2 
1 Hi 
1 Hi 
2 Bye 
3 Later 
3 Later 
3 Later