2013-07-16 13 views
14

Jak mogę pozostawić zewnętrzne połączenie (myślę, że jest to lewe zewnętrzne połączenie, ale nie jestem w 100% pewny) dwie tabele danych z następującymi tabelami i warunkami, zachowując wszystkie kolumny z obu tabel?Jak pozostawić zewnętrzny Dołącz dwa DataTables w języku 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 

Warunki:

  • W dtblLeft, col1 nie musi posiadać unikalnych wartości.
  • W dtblRight, col1 ma unikalne wartości.
  • Jeśli w dtblLeft brakuje klucza obcego w kolumnie col1 lub takiego, który nie istnieje w dtblRight, zostaną wstawione puste lub puste pola.
  • Łączenie na col1.

mogę użyć regularne operacje DataTable, LINQ, czy cokolwiek innego.

próbowałem tego, ale usuwa duplikaty:

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:

Jest blisko do I co chcę, ale to ma tylko kolumny z jednym ze stolików (na tym 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:

Odpowiedź z tego link wydaje się działać dla mnie, ale musiałem go zmienić trochę sposób następujący:

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

Znalazłem to link także, a może ja spróbuję się od niego wydaje się bardziej zwięzły.

EDIT 3 (18.11.2013):

Updated tabele odzwierciedlają kolejne sytuacje.

+0

To także naturalne połączenie. Powiedz nam, co ma się stać, gdy dtblA ma "4", a dtblB nie. – Shoe

+0

zaktualizowano, aby wyświetlać dtblA z 4 – Soenhay

+0

Na wypadek, gdyby brakowało Ci innych wpisów na SO dotyczących tego samego problemu. [Sprawdź tutaj] (http://stackoverflow.com/questions/10404039/left-join-datatables-how-do- i-get-this-to-work) [i tutaj] (http://stackoverflow.com/questions/9055180/datatables-left-join-c- sharp) – Rwiti

Odpowiedz

10

Dzięki wszystkim za pomoc. Oto co wymyśliłem na podstawie wielu źródeł:

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:

Ta metoda działa poprawnie i nadal jest szybki, gdy tabele mają 2000 + wiersze. Wszelkie rekomendacje/sugestie/ulepszenia zostaną docenione.

EDIT 4:

miałem pewien scenariusz, który doprowadził mnie do realizacji poprzednia wersja była naprawdę robi sprzężenie wewnętrzne. Funkcja została zmodyfikowana w celu rozwiązania tego problemu. Użyłem informacji pod tym link, aby to zrozumieć.

0

Można prawdopodobnie używać LINQ i zrobić coś takiego:

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

byłby to powrót IEnumerable w wyniku nie DataTable, ale powinna ona dostać się bliżej do tego, co szukasz myślę. Może jednak wymagać trochę poprawek.

1

To jest po prostu sprzężenie wewnętrzne między 2 tabele:

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(); 

Produkuje:

col1 col2 
1 Hi 
1 Hi 
2 Bye 
3 Later 
3 Later 
3 Later 
Powiązane problemy