Qual è il modo migliore per ottenere dati correlati dai loro ID in un'unica query?
-
23-08-2019 - |
Domanda
Ho una tabella in cui ogni riga ha alcuni campi con ID relativi ad altri dati di altre tabelle.
Diciamo che si chiama people
, e ogni persona ha l'ID di a city
, state
E country
.
Quindi ci saranno altri tre tavoli, cities
, states
E countries
dove ciascuno ha un ID e un nome.
Quando seleziono una persona, qual è il modo più semplice per ottenere il file nomi del city
, state
E country
in un'unica query?
Nota: So che questo è possibile con i join, tuttavia, poiché ci sono più tabelle correlate, i join nidificati rendono la query difficile da leggere e mi chiedo se esiste un modo più semplice.Dovrebbe anche essere possibile per la persona avere questi campi vuoti.
Soluzione
Supponendo che le seguenti tabelle:
create table People
(
ID int not null primary key auto_increment
,FullName varchar(255) not null
,StateID int
,CountryID int
,CityID int
)
;
create table States
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
create table Countries
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
create table Cities
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
Con i seguenti dati:
insert into Cities(Name) values ('City 1'),('City 2'),('City 3');
insert into States(Name) values ('State 1'),('State 2'),('State 3');
insert into Countries(Name) values ('Country 1'),('Country 2'),('Country 3');
insert into People(FullName,CityID,StateID,CountryID) values ('Has Nothing' ,null,null,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has City' , 1,null,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has State' ,null, 2,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has Country' ,null,null, 3);
insert into People(FullName,CityID,StateID,CountryID) values ('Has Everything', 3, 2, 1);
Poi questa query dovrebbe darvi che cosa siete dopo.
select
P.ID
,P.FullName
,Ci.Name as CityName
,St.Name as StateName
,Co.Name as CountryName
from People P
left Join Cities Ci on Ci.ID = P.CityID
left Join States St on St.ID = P.StateID
left Join Countries Co on Co.ID = P.CountryID
Altri suggerimenti
si unisce sono l'unico modo per fare proprio questo.
Si potrebbe essere in grado di cambiare lo schema, ma il problema sarà lo stesso indipendentemente.
(una città è sempre in uno stato, che è sempre in un Paese -. Così la persona potrebbe semplicemente avere un riferimento al city_id piuttosto che tutti e tre Hai ancora bisogno di unire le 3 tavoli però).
Non c'è un modo più pulito di join. Se i campi sono autorizzati a essere vuoto, l'uso outer join
SELECT c.*, s.name AS state_name
FROM customer c
LEFT OUTER JOIN state s ON s.id = c.state
WHERE c.id = 10
Secondo la descrizione dello schema che avete dato si dovrà utilizzare unisce a una singola query.
SELECT
p.first_name
, p.last_name
, c.name as city
, s.name as state
, co.name as country
FROM people p
LEFT OUTER JOIN city c
ON p.city_id = c.id
LEFT OUTER JOIN state s
ON p.state_id = s.id
LEFT OUTER JOIN country co
ON p.country_id = co.id;
Il LEFT OUTER JOIN vi permetterà di recuperare i dettagli di persona, anche se alcuni ID sono vuoti o vuoto.
Un altro modo è quello di ridisegnare le vostre tabelle di ricerca. Una città è sempre in uno stato e uno stato in un paese. Quindi il vostro tavolo city
avrà colonne: Id, Name
e state_id
. Il vostro tavolo state
sarà: Id, Name
e country_id
. E tavolo country
rimarrà lo stesso: Id
e Name
.
La tabella person
ora avrà solo 1 ID: city_id
Ora la query sarà:
SELECT
p.first_name
, p.last_name
, c.name as city
, s.name as state
, co.name as country
FROM people p
LEFT OUTER JOIN city c
ON p.city_id = c.id
LEFT OUTER JOIN state s
ON c.state_id = s.id
LEFT OUTER JOIN country co
ON s.country_id = co.id;
Si noti la differenza negli ultimi due outer join
Se le tabelle coinvolte sono le tabelle di riferimento (cioè in possesso dei dati di ricerca che non sta per cambiare nel corso del tempo di vita di una sessione), a seconda della natura della vostra applicazione, è possibile pre-caricare i dati di riferimento durante il vostro applicazione start up. Poi la query non ha bisogno di fare i join, ma restituisce i valori ID, e nell'applicazione e si fa una decodifica degli ID quando è necessario per visualizzare i dati.
La soluzione più semplice è quella di utilizzare i nomi, come le chiavi primarie in city
, state
e country
. Allora la vostra tabella di person
li può fare riferimento con il nome invece del pseudokey "id
". In questo modo, non c'è bisogno di fare si unisce, dal momento che il tavolo person
ha già i valori necessari.
Ci vuole più spazio per memorizzare una stringa invece di un pseudokey 4 byte. Ma si può trovare il compromesso vale la pena, se sono minacciati da unisce tanto quanto ti sembra di essere (che, tra l'altro, è come un programmatore PHP essere riluttanti a utilizzare foreach
- si unisce sono fondamentali a SQL nello stesso modo) .
Inoltre ci sono molti nomi di città che appaiono in più di uno stato. Così il vostro tavolo city
dovrebbe fare riferimento alla tabella state
e utilizzare queste due colonne come chiave primaria.
CREATE TABLE cities (
city_name VARCHAR(30),
state CHAR(2),
PRIMARY KEY (city_name, state),
FOREIGN KEY (state) REFERENCES states(state)
);
CREATE TABLE persons (
person_id SERIAL PRIMARY KEY,
...other columns...
city_name VARCHAR(30),
state CHAR(2),
country_name VARCHAR(30),
FOREIGN KEY (city_name, state) REFERENCES cities(city_name, state),
FOREIGN KEY (country_name) REFERENCES countries(country_name)
);
Questo solo un esempio della tecnica. Naturalmente è più complessa di questa, perché si può avere i nomi delle città in più di un paese, si possono avere i paesi, senza stati, e così via. Il punto è SQL non ti costringe ad utilizzare pseudokeys interi, in modo da utilizzare i tasti CHAR e VARCHAR, se del caso.
Uno svantaggio dell'SQL standard è che i dati restituiti devono essere in formato tabellare.Tuttavia, alcuni fornitori di database hanno aggiunto funzionalità che rendono possibile selezionare i dati in formato non tabellare.Non so se MySQL conosca tali funzionalità.
Creare una vista che fa la persona, Città, Stato, Nazione e si unisce per voi. Poi basta fare riferimento alla vista in tutti gli altri join.
Qualcosa di simile:
CREATE VIEW FullPerson AS
SELECT Person.*, City.Name, State.Name, Country.Name
FROM
Person LEFT OUTER JOIN City ON Person.CityId = City.Id
LEFT OUTER JOIN State ON Person.StateId = State.Id
LEFT OUTER JOIN Country ON Person.CountryId = Country.Id
Poi in altre query, puoi
SELECT FullPerson.*, Other.Value
FROM FullPerson LEFT OUTER JOIN Other ON FullPerson.OtherId = Other.Id
Tutte le grandi risposte, ma l'interrogante specificato che non volevano usare si unisce. Come uno degli intervistati ha dimostrato, supponendo che il Cities
, States
e tabelle Countries
hanno un Id
e un campo Description
si potrebbe essere in grado di fare qualcosa di simile:
SELECT
p.Name, c.Description, s.Description, ct.Description
FROM
People p, Cities c, States s, Countries ct
WHERE
p.Id = value AND
c.Id = value AND
s.Id = value AND
ct.Id = value;
I join lo sono IL risposta.Con la pratica ti diventeranno più leggibili.
Potrebbero esserci casi speciali in cui la creazione di una funzione potrebbe aiutarti, ad esempio potresti fare quanto segue (in Oracle, non conosco nessun mysql):
Potresti creare una funzione per restituire un indirizzo formattato dati i codici stato della città e paese, quindi la tua query diventa
SELECT first_name, last_name, formated_address(city_id, state_id, country_id)
FROM people
WHERE some_where_clause;
Dove formated_address
esegue ricerche individuali sulle tabelle dello stato della città e del paese e inserisce separatori tra i valori decodificati o restituisce "nessun indirizzo" se sono tutti vuoti, ecc.