Wprowadzenie
T-SQL pozwala nam łączyć rekordy z więcej niż jednej tabeli i zwracać je jako jeden zestaw wyników. Osiąga się to dzięki koncepcji złączeń w SQL Server.
Taka możliwość jest często konieczna, ponieważ dane w relacyjnych bazach danych są zazwyczaj znormalizowane. Na przykład mamy dane pracowników rozmieszczone w dwóch lub więcej tabelach. Pierwsza tabela zawierałaby podstawowe dane klienta i nazywała się pracownik. Drugi stół to dział .
Spójność danych wymaga prawidłowej relacji między klientem a działem. Zwrócenie pełnych danych dla zbioru pracowników i ich działów wymaga połączenia obu tabel.
Operacje łączenia SQL mogą również obejmować więcej niż dwie tabele.
Innym przypadkiem istnienia takich relacji kluczy obcych między tabelami jest podsumowanie i szczegóły tabele.
Osoby, które pracowały z przykładowymi bazami danych AdventureWorks lub WideWorldImporters, znają Sales.Orders tabele Sales.OrderDetails. W tym przypadku ta ostatnia zawiera szczegóły każdego zamówienia zapisanego w Sales.Orders stół. Dwie tabele mają relację opartą na kolejności. W ten sposób możemy pobrać dane z obu tabel jako jeden zestaw wyników za pomocą JOINS.
Typy sprzężeń SQL Server
T-SQL umożliwia następujące typy złączeń:
- Łączenie wewnętrzne zwraca wszystkie rekordy wspólne dla wszystkich tabel biorących udział w zapytaniu.
- Połączenie lewe (zewnętrzne) zwraca wszystkie rekordy z lewej tabela i wszystkie rekordy z prawej tabeli, które występują również w lewej tabeli. Terminy po lewej i w prawo odnoszą się do pozycji tabeli względem klauzuli JOIN.
- Połączenie prawe (zewnętrzne) zwraca wszystkie rekordy z prawej tabela i wszystkie rekordy z lewej tabeli, które występują również w lewej tabeli. Warunki są podobne do poprzedniego przypadku.
- Pełne połączenie zewnętrzne zwraca wszystkie rekordy wspólne dla obu tabel oraz wszystkie inne rekordy z obu tabel. Kolumny, które nie mają odpowiadających wierszy w drugiej tabeli, zwracają NULL
- Połączenie krzyżowe , zwany także złączem kartezjańskim , zwraca iloczyn kartezjański danych z obu tabel. Dlatego ostateczny zestaw wyników dla każdego wiersza w tabeli A będzie zawierał mapowanie wszystkich wierszy w tabeli B i na odwrót.
W tym artykule skupimy się na SQL INNER JOIN.
Przykładowe tabele
Aby zademonstrować koncepcję sprzężeń wewnętrznych, używamy trzech powiązanych tabel z bazy danych TSQLV4 zbudowanej przez Itzika Ben-Gana.
Poniższe zestawienia pokazują strukturę tych tabel.
-- Listing 1: Structure of the Sales.Customers Table
CREATE TABLE [Sales].[Customers](
[custid] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[companyname] [nvarchar](40) NOT NULL,
[contactname] [nvarchar](30) NOT NULL,
[contacttitle] [nvarchar](30) NOT NULL,
[address] [nvarchar](60) NOT NULL,
[city] [nvarchar](15) NOT NULL,
[region] [nvarchar](15) NULL,
[postalcode] [nvarchar](10) NULL,
[country] [nvarchar](15) NOT NULL,
[phone] [nvarchar](24) NOT NULL,
[fax] [nvarchar](24) NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
(
[custid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
Zwróć uwagę na relację klucza obcego między kolumną custid w Sales.Orders i kolumna custid w Sales.Customers .
Aby wykonać JOIN, musimy określić taką wspólną kolumnę, jaką jest podstawa JOIN.
Nie wymaga ściśle relacji klucza obcego do wykonywania zapytań JOIN, ale kolumny określające zestaw wyników muszą być porównywalne.
Klucze obce mogą również pomóc ulepszyć zapytania JOIN, zwłaszcza jeśli kolumna klucza obcego jest indeksowana.
-- Listing 2: Structure of the Sales.Orders Table
CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[orderdate] [date] NOT NULL,
[requireddate] [date] NOT NULL,
[shippeddate] [date] NULL,
[shipperid] [int] NOT NULL,
[freight] [money] NOT NULL,
[shipname] [nvarchar](40) NOT NULL,
[shipaddress] [nvarchar](60) NOT NULL,
[shipcity] [nvarchar](15) NOT NULL,
[shipregion] [nvarchar](15) NULL,
[shippostalcode] [nvarchar](10) NULL,
[shipcountry] [nvarchar](15) NOT NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
(
[orderid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [Sales].[Orders] ADD CONSTRAINT [DFT_Orders_freight] DEFAULT ((0)) FOR [freight]
GO
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid])
REFERENCES [Sales].[Customers] ([custid])
GO
ALTER TABLE [Sales].[Orders] CHECK CONSTRAINT [FK_Orders_Customers]
GO
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid])
REFERENCES [HR].[Employees] ([empid])
GO
ALTER TABLE [Sales].[Orders] CHECK CONSTRAINT [FK_Orders_Employees]
GO
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])
REFERENCES [Sales].[Shippers] ([shipperid])
GO
ALTER TABLE [Sales].[Orders] CHECK CONSTRAINT [FK_Orders_Shippers]
GO
-- Listing 3: Structure of the Sales.OrderDetails Table
CREATE TABLE [Sales].[OrderDetails](
[orderid] [int] NOT NULL,
[productid] [int] NOT NULL,
[unitprice] [money] NOT NULL,
[qty] [smallint] NOT NULL,
[discount] [numeric](4, 3) NOT NULL,
CONSTRAINT [PK_OrderDetails] PRIMARY KEY CLUSTERED
(
[orderid] ASC,
[productid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [Sales].[OrderDetails] ADD CONSTRAINT [DFT_OrderDetails_unitprice] DEFAULT ((0)) FOR [unitprice]
GO
ALTER TABLE [Sales].[OrderDetails] ADD CONSTRAINT [DFT_OrderDetails_qty] DEFAULT ((1)) FOR [qty]
GO
ALTER TABLE [Sales].[OrderDetails] ADD CONSTRAINT [DFT_OrderDetails_discount] DEFAULT ((0)) FOR [discount]
GO
ALTER TABLE [Sales].[OrderDetails] WITH CHECK ADD CONSTRAINT [FK_OrderDetails_Orders] FOREIGN KEY([orderid])
REFERENCES [Sales].[Orders] ([orderid])
GO
ALTER TABLE [Sales].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_Orders]
GO
ALTER TABLE [Sales].[OrderDetails] WITH CHECK ADD CONSTRAINT [FK_OrderDetails_Products] FOREIGN KEY([productid])
REFERENCES [Production].[Products] ([productid])
GO
ALTER TABLE [Sales].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_Products]
GO
ALTER TABLE [Sales].[OrderDetails] WITH CHECK ADD CONSTRAINT [CHK_discount] CHECK (([discount]>=(0) AND [discount]<=(1)))
GO
ALTER TABLE [Sales].[OrderDetails] CHECK CONSTRAINT [CHK_discount]
GO
ALTER TABLE [Sales].[OrderDetails] WITH CHECK ADD CONSTRAINT [CHK_qty] CHECK (([qty]>(0)))
GO
ALTER TABLE [Sales].[OrderDetails] CHECK CONSTRAINT [CHK_qty]
GO
ALTER TABLE [Sales].[OrderDetails] WITH CHECK ADD CONSTRAINT [CHK_unitprice] CHECK (([unitprice]>=(0)))
GO
ALTER TABLE [Sales].[OrderDetails] CHECK CONSTRAINT [CHK_unitprice]
GO
Przykładowe zapytania z SQL INNER JOIN
Wykonajmy kilka przykładowych zapytań za pomocą SQL INNER JOIN.
Na liście 4 wykonujemy zapytanie, które pobiera WSZYSTKIE wiersze wspólne dla tabel Sprzedaż.Klienci i Sprzedaż.Zamówienia. Używamy kolumny custid jako warunku połączenia.
Zauważ, że klauzula ON jest filtrem bardzo podobnym do klauzuli WHERE. Użyliśmy również aliasów do rozróżniania tabel.
-- Listing 4: Customer Orders
use TSQLV4
go
select * from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid;
Na liście 5 zawężamy zapytanie do określonych kolumn do Sales.Customers tabeli i Sales.Orders stół. Używamy custid kolumna jako warunek połączenia.
Zauważ, że klauzula ON jest filtrem bardzo podobnym do klauzuli WHERE. Użyliśmy również aliasów do rozróżniania tabel.
-- Listing 5: Customer Orders with specific Rows
use TSQLV4
go
select
contactname
, contacttitle
, address
, orderid
, orderdate
, shipaddress
, shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid;
W Listingu 6 rozszerzyliśmy tę myśl, wprowadzając klauzulę WHERE, która filtruje dane dla pojedynczego klienta. Do listy kolumn dodaliśmy również aliasy.
Chociaż nie jest to konieczne w tym przykładzie, istnieją przypadki, w których konieczne jest rzutowanie kolumn o tej samej nazwie z obu tabel. Następnie kolumny będą wymagały wyrażenia w postaci dwuczęściowych nazw, używając aliasów lub nazw tabel.
-- Listing 6: Customer Orders for a Single Customer
use TSQLV4
go
select
sc.contactname
, sc.contacttitle
, sc.address
, so.orderid
, so.orderdate
, so.shipaddress
, so.shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid
where sc.contactname='Allen, Michael';
W listingu 7 wprowadzamy kolumnę custid. Możemy rozróżnić kolumny za pomocą aliasu, ale nie możemy rozróżnić dwóch custid kolumn w danych wyjściowych (patrz rysunek 4). Możemy to naprawić za pomocą aliasów:
-- Listing 7: Customer Orders for a Single Customer with Common Column
use TSQLV4
go
select
sc.custid
, sc.contactname
, sc.contacttitle
, sc.address
, so.custid
, so.orderid
, so.orderdate
, so.shipaddress
, so.shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid
where sc.contactname='Allen, Michael';
-- Listing 8: Customer Orders for a Single Customer with Aliased Column
use TSQLV4
go
select
sc.custid customer_custid
, sc.contactname
, sc.contacttitle
, sc.address
, so.custid order_custid
, so.orderid
, so.orderdate
, so.shipaddress
, so.shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid
where sc.contactname='Allen, Michael';
Na listingu 9 dodajemy do zestawu tabelę Sales.OrderDetails. W przypadku dołączania do więcej niż dwóch stołów, zestaw wyników z pierwszych dwóch stołów JOIN staje się lewym tabela dla następnej tabeli. Jednak kolejność tabel w zapytaniu JOIN nie wpływa na ostateczny wynik.
Pamiętaj, że używamy symbolu wieloznacznego, aby pobrać WSZYSTKIE kolumny z tabeli Sales.OrderDetails.
-- Listing 9: Inner Join with Three Tables
use TSQLV4
go
select
sc.custid customer_custid
, sc.contactname
, sc.contacttitle
, sc.address
, so.custid order_custid
, so.orderid
, so.orderdate
, so.shipaddress
, so.shipcountry
, sod.*
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid
inner join Sales.OrderDetails sod
on so.orderid=sod.orderid
where sc.contactname='Allen, Michael';
Listing 10 przedstawia tabelę Production.Product pokazującą szczegóły produktu powiązane z zamówieniem.
-- Listing 10: Inner Join with Four Tables
use TSQLV4
go
select
sc.custid customer_custid
, sc.contactname
, sc.contacttitle
, sc.address
, so.custid order_custid
, so.orderid
, so.orderdate
, so.shipaddress
, so.shipcountry
, sod.*
, pp.productname
, pp.unitprice
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid
inner join Sales.OrderDetails sod
on so.orderid=sod.orderid
inner join Production.Products pp
on sod.productid=pp.productid
where sc.contactname='Allen, Michael';
Dołączenia inne niż Equi
Ponieważ klauzula ON jest filtrem, możemy używać operatorów innych niż operator „=”. Sprzężenia generalnie obsługują użycie nierówności, takich jak <,>, !=, = w klauzuli ON. Listing 11 to pokazuje.
Uruchomienie tych zapytań zwróci różne zestawy wyników.
-- Listing 11: Non-Equi JOINs, "Equal to"
use TSQLV4
go
select
contactname
, contacttitle
, address
, orderid
, orderdate
, shipaddress
, shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid=so.custid;
-- Listing 12: Non-Equi JOINs, "Not Equal to"
use TSQLV4
go
select
contactname
, contacttitle
, address
, orderid
, orderdate
, shipaddress
, shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid<>so.custid;
-- Listing 13: Non-Equi JOINs, "Less than OR Equal to"
use TSQLV4
go
select
contactname
, contacttitle
, address
, orderid
, orderdate
, shipaddress
, shipcountry
from Sales.Customers sc
inner join Sales.Orders so
on sc.custid<=so.custid;
Wniosek
W tym artykule omówiono SQL INNER JOIN i przedstawiono przykłady jego użycia. Obejmuje scenariusze z dwiema, trzema i czterema tabelami w tym samym zapytaniu.
Korzystając z powiązanych tabel, zilustrowaliśmy również, w jaki sposób możemy zmienić strukturę zapytania, aby wyświetlić wynik zgodnie z naszymi wymaganiami. Dodaliśmy również krótkie przykłady JOIN Non-Equi.