join – klauzule (Referenční dokumentace jazyka C#)
Klauzule join
je užitečná pro přidružení prvků z různých zdrojových sekvencí, které nemají žádný přímý vztah v objektovém modelu. Jediným požadavkem je, aby prvky v každém zdroji sdílely určitou hodnotu, kterou je možné porovnat s rovností. Například distributor potravin může mít seznam dodavatelů určitého výrobku a seznam kupujících. Klauzuli join
lze použít například k vytvoření seznamu dodavatelů a kupujících daného produktu, kteří jsou všichni ve stejné zadané oblasti.
Klauzule join
přebírá jako vstup dvě zdrojové sekvence. Prvky v každé sekvenci musí být nebo musí obsahovat vlastnost, která se dá porovnat s odpovídající vlastností v druhé sekvenci. Klauzule join
porovnává zadané klíče pro rovnost pomocí speciálního equals
klíčového slova. Všechna spojení prováděná join
klauzulí jsou equijoins. Tvar výstupu join
klauzule závisí na konkrétním typu spojení, které provádíte. Toto jsou tři nejběžnější typy spojení:
Vnitřní spojení
Připojení ke skupině
Levé vnější spojení
Vnitřní spojení
Následující příklad ukazuje jednoduchý vnitřní equijoin. Tento dotaz vytvoří plochou sekvenci párů "název produktu / kategorie". Stejný řetězec kategorie se zobrazí ve více prvech. Pokud prvek z categories
nemá žádnou shodu products
, tato kategorie se ve výsledcích nezobrazí.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
Další informace naleznete v tématu Provádění vnitřních spojení.
Připojení ke skupině
join
Klauzule s výrazem into
se nazývá spojení skupiny.
var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
Spojení skupiny vytvoří hierarchickou sekvenci výsledků, která přidruží prvky v levé zdrojové sekvenci k jednomu nebo více odpovídajícím prvkům v pravé sekvenci zdroje. Spojení skupiny nemá žádný ekvivalent v relačních termínech; je to v podstatě posloupnost polí objektů.
Pokud nejsou nalezeny žádné prvky ze správné zdrojové sekvence, které by odpovídaly prvku v levém zdroji, join
klauzule vytvoří prázdné pole pro danou položku. Proto spojení skupiny je v podstatě vnitřní koňské spojení s tím rozdílem, že výsledná sekvence je uspořádaná do skupin.
Pokud vyberete jenom výsledky připojení ke skupině, budete mít přístup k položkám, ale nemůžete identifikovat klíč, na který se shodují. Proto je obecně užitečnější vybrat výsledky spojení skupiny do nového typu, který má také název klíče, jak je znázorněno v předchozím příkladu.
Můžete také samozřejmě použít výsledek spojení skupiny jako generátor jiného poddotazu:
var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;
Další informace najdete v tématu Provádění seskupených spojení.
Levé vnější spojení
V levém vnějším spojení se vrátí všechny prvky v levé zdrojové sekvenci, i když nejsou v správné sekvenci žádné odpovídající prvky. Chcete-li provést levé vnější spojení v LINQ, použijte DefaultIfEmpty
metodu v kombinaci se spojením skupiny k určení výchozího pravého prvku pro vytvoření, pokud levý prvek nemá žádné shody. Jako výchozí hodnotu můžete použít null
libovolný typ odkazu nebo můžete zadat výchozí typ definovaný uživatelem. V následujícím příkladu je zobrazen výchozí typ definovaný uživatelem:
var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
select new { CatName = category.Name, ProdName = item.Name };
Další informace naleznete v tématu Provádění levých vnějších spojení.
Operátor rovná se
Klauzule A join
provádí equijoin. Jinými slovy, můžete založit pouze shody na rovnosti dvou klíčů. Jiné typy porovnání, například "větší než" nebo "nerovná se", nejsou podporovány. Aby bylo jasné, že všechna spojení jsou equijoins, join
klauzule místo operátoru ==
používá equals
klíčové slovo. Klíčové equals
slovo lze použít pouze v join
klauzuli a liší se od operátoru ==
několika důležitými způsoby. Při porovnávání řetězců equals
má přetížení pro porovnání podle hodnoty a operátor ==
používá rovnost odkazů. Pokud obě strany porovnání mají stejné řetězcové proměnné equals
a ==
dosáhne stejného výsledku: true. Je to proto, že když program deklaruje dvě nebo více ekvivalentních řetězcových proměnných, kompilátor je uloží do stejného umístění. To se označuje jako interning. Dalším důležitým rozdílem je porovnání s hodnotou null: null equals null
je vyhodnoceno jako nepravda s operátorem equals
místo operátoru, ==
který ho vyhodnotí jako true. Nakonec se chování rozsahu liší: pomocí equals
, levý klíč spotřebovává vnější zdrojovou sekvenci a pravý klíč spotřebovává vnitřní zdroj. Vnější zdroj je pouze v oboru na levé straně equals
a vnitřní zdrojová sekvence je pouze v oboru na pravé straně.
Non-equijoins
K nezávislému zavedení nových sekvencí do dotazu můžete provádět neekvižné operace, křížové spojení a další vlastní operace from
spojení. Další informace naleznete v tématu Provádění vlastních operací spojení.
Spojení v kolekcích objektů vs. relačních tabulek
Ve výrazu dotazu LINQ se operace spojení provádějí u kolekcí objektů. Kolekce objektů nelze "spojit" úplně stejným způsobem jako dvě relační tabulky. V LINQ jsou explicitní join
klauzule vyžadovány pouze v případě, že dvě zdrojové sekvence nejsou svázané žádnou relací. Při práci s LINQ to SQL jsou tabulky cizích klíčů reprezentovány v objektovém modelu jako vlastnosti primární tabulky. Například v databázi Northwind má tabulka Customer relaci cizího klíče s tabulkou Orders. Při mapování tabulek na objektový model má třída Customer vlastnost Orders, která obsahuje kolekci Objednávek přidružených k danému zákazníkovi. Spojení už pro vás bylo provedeno.
Další informace o dotazování napříč souvisejícími tabulkami v kontextu LINQ to SQL naleznete v tématu Postupy: Mapování databázových relací.
Složené klávesy
Rovnost více hodnot můžete otestovat pomocí složeného klíče. Další informace naleznete v tématu Spojení pomocí složených klíčů. Složené klíče lze také použít v klauzuli group
.
Příklad
Následující příklad porovnává výsledky vnitřního spojení, spojení skupiny a levého vnějšího spojení ve stejných zdrojích dat pomocí stejných odpovídajících klíčů. Do těchto příkladů se přidá další kód, který objasní výsledky v zobrazení konzoly.
class JoinDemonstration
{
#region Data
class Product
{
public required string Name { get; init; }
public required int CategoryID { get; init; }
}
class Category
{
public required string Name { get; init; }
public required int ID { get; init; }
}
// Specify the first data source.
List<Category> categories =
[
new Category {Name="Beverages", ID=001},
new Category {Name="Condiments", ID=002},
new Category {Name="Vegetables", ID=003},
new Category {Name="Grains", ID=004},
new Category {Name="Fruit", ID=005}
];
// Specify the second data source.
List<Product> products =
[
new Product {Name="Cola", CategoryID=001},
new Product {Name="Tea", CategoryID=001},
new Product {Name="Mustard", CategoryID=002},
new Product {Name="Pickles", CategoryID=002},
new Product {Name="Carrots", CategoryID=003},
new Product {Name="Bok Choy", CategoryID=003},
new Product {Name="Peaches", CategoryID=005},
new Product {Name="Melons", CategoryID=005},
];
#endregion
static void Main(string[] args)
{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
}
void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.ID, Product = prod.Name };
Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
}
Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup;
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
// A nested foreach statement is required to access group items.
foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
}
}
Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin3()
{
var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName = prod.Name };
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine(" {0}:{1}", item.ProductName, item.Category);
}
Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems);
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Left Outer Join:");
// A nested foreach statement is required to access group items
foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
// Store the count of total items
int totalItems = 0;
Console.WriteLine("Left Outer Join 2:");
// Groups have been flattened.
foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
}
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);
}
}
/*Output:
InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
InnerJoin: 8 items in 1 group.
Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
Unshaped GroupJoin: 8 items in 5 unnamed groups
GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupInnerJoin: 8 items in 5 named groups
GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
GroupJoin3: 8 items in 1 group
Left Outer Join:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
LeftOuterJoin: 9 items in 5 groups
LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/
Poznámky
join
Klauzule, za kterou není následovatinto
, se přeloží do Join volání metody. join
Klauzule, za into
kterou následuje, se přeloží na GroupJoin volání metody.