Union e Union ALL

martedì 17 aprile 2012 - 12.08
Tag Elenco Tags  SQL Server 2008 R2

mmjc23 Profilo | Newbie

Buongiorno a tutti

Mi spiace aprire un thread per queste domande semplici, ma in Internet non ho trovato risposta.

Nel mio Database, ho 2 tabelle identiche contenenti una colonna "ID_APPARATO" e una colonna "INDIRIZZO_IP" (i record, sono già univoci)

Passato un Indirizzo IP, il mio obiettivo, sarebbe quello di ottenere un solo record che contiene il recor di "Tabella 1" se è presente, altrimenti, quello di Tabella 2

Ho quindi creato la seguente query:
[CODE] DECLARE @ID_APPARATO SELECT TOP 1 @ID_APPARATO=ID_APPARATO FROM ( SELECT ID_APPARATO FROM TABELLA1 WHERE INDIRIZZO_IP='127.0.0.1' UNION SELECT ID_APPARATO FROM TABELLA2 WHERE INDIRIZZO_IP='127.0.0.1') AS T [/CODE]

Il problema è che come risultato, mi restituisce il record presente in TABELLA2.
Ho fatto un po' di prove, ed ho notato che la UNION, mi ordinava i record in base all'id_apparato e non in base all'ordine delle Select.

La query funziona correttamente, se al posto di UNION, utilizzo UNION ALL.

Volevo quindi chiederVI:
-E' corretto che la UNION si comporti così? Va bene che mi faccia la DISTINCT ma perchè mi cambia l'ordinamento?
-Con la UNION ALL, sono sicuro che funzioni o potrei avere in futuro sorprese?
-Questo modo di lavorare/ragionare (in termini di sviluppo corretto), è corretto oppure andrebbero utilizzate altre soluzioni (ad esempio un IF sulla prima tabella ELSE seconda tabella)? (mi sembrava il metodo più veloce.

Grazie

alx_81 Profilo | Guru

>Buongiorno a tutti
Ciao

>Volevo quindi chiederVI:
>-E' corretto che la UNION si comporti così? Va bene che mi faccia
>la DISTINCT ma perchè mi cambia l'ordinamento?
UNION, se trova doppioni, raggruppa il risultato. UNION ALL li mostra tutti indistintamente.
Quindi l'ordinamento conta e devi definirlo tu se vuoi ottenere un record preciso in base ad un criterio preciso.

>-Questo modo di lavorare/ragionare (in termini di sviluppo corretto),
>è corretto oppure andrebbero utilizzate altre soluzioni (ad esempio
>un IF sulla prima tabella ELSE seconda tabella)? (mi sembrava il metodo più veloce.
mah, non so se ho capito bene il tuo caso.. però dovresti passare una create delle due strutture, i dati in input e quello che vorresti ottenere..
Magari si trova un altro metodo per risolvere il problema (non è detto che sia il migliore o il più performante).

>Grazie
di nulla!
--
Alessandro Alpi | SQL Server MVP
MCP|MCITP|MCTS|MCT

http://www.alessandroalpi.net
http://blogs.dotnethell.it/suxstellino
http://mvp.support.microsoft.com/profile/Alessandro.Alpi

mmjc23 Profilo | Newbie

Uhmmm...Scusami, ma non ho capito cosa intendi dire

Vedo se riesco a spiegarmi meglio con un esempio.

Ho due tabelle (Tabella1 e Tabella2) costituite dalle stesse colonne "ID_APPARATO" e "INDIRIZZO_IP"

Tabella1 è valorizzata (supponiamo sempre) con i rispettivi valori "12345678" e "127.0.0.1"
Tabella2, invece, potrebbe (oppure no) essere riempita con i valori "87654321" e "127.0.0.1"

Quello che si vuole è, passato l'indirizzo IP ad una funzione, ottenere l'"ID_APPARATO" di TABELLA2 se questa contiene quell'indirizzo IP, altrimenti, l'ID_APPARATO contenuto in TABELLA1.

Visto che ognuna delle 2 tabelle non può contenere ID_APPARATO e INDIRIZZO_IP duplicati, mi sono detto faccio una select su TABELLA2 e, al recordset risultato (vuoto in caso in cui non contenga l'IP), accodo la select su TABELLA1 e infine, vado a prendere il primo record.

In questo modo, se sia TABELLA2 che TABELLA1 contengono l'IP, otterrò 2 Record ma prenderò quello di TABELLA2 (in quanto risultato dalla prima select), mentre, se TABELLA2, non contiene l'Indirizzo IP, il risultato sarà un solo record costituito dal contenuto di TABELLA1

Ho usato quindi questa funzione:
DECLARE @ID_APPARATO SELECT TOP 1 @ID_APPARATO=ID_APPARATO FROM (SELECT ID_APPARATO FROM TABELLA2 WHERE INDIRIZZO_IP='127.0.0.1' UNION SELECT ID_APPARATO FROM TABELLA1 WHERE INDIRIZZO_IP='127.0.0.1') AS T

La funzione, funziona :-) correttamente...nel senso che, se entrambe le tabelle sono riempite, la Union mi restituisce i due record mentre se TABELLA2 non contiene l'IP, mi restituisce solo l'id apparato di TABELLA1. Il problema è che poi, quando vado a fare il SELECT TOP 1, la UNIO delle due tabelle (entrambe riempite), mi ordina i risultati in base all'ID_APPARATO e non in base all'ordine in cui ho messo le tabelle nella SELECT e quindi mi restituisce il record di TABELLA1 invece che di TABELLA2.
Per farlo funzionare, devo mettere, invece di UNION, "UNION ALL"

La mia domanda è....con UNION ALL, è corretto (e quindi mi restitiusce i risultati in base all'ordine delle Select) o potrei avere altre future sorprese?
Grazie di nuovo
Ciao

alx_81 Profilo | Guru

>La funzione, funziona :-) correttamente...nel senso che, se entrambe
>le tabelle sono riempite, la Union mi restituisce i due record
>mentre se TABELLA2 non contiene l'IP, mi restituisce solo l'id
>apparato di TABELLA1. Il problema è che poi, quando vado a fare
>il SELECT TOP 1, la UNIO delle due tabelle (entrambe riempite),
>mi ordina i risultati in base all'ID_APPARATO e non in base all'ordine
>in cui ho messo le tabelle nella SELECT e quindi mi restituisce
>il record di TABELLA1 invece che di TABELLA2.
>Per farlo funzionare, devo mettere, invece di UNION, "UNION ALL"
Come ti dicevo, siccome nel primo post parlavi proprio di soluzione, sembra che la problematica possa essere affrontata anche tramite altri costrutti. Tuttavia, per dare altre soluzioni (anche se da questo post non sembra servirti) ti consiglio, anche per il futuro di passare le CREATE TABLE e qualche dato di prova, con quanto ti serve in output, per aiutare chi poi prova coi tuoi dati una situazione simile.
A prescindere da questo, siccome la funzione che hai fatto funziona , ti illustravo l'unica caratteristica che distingue la UNION dalla UNION ALL. Il fatto che tu ottenga un ordinamento differente non è legato strettamente all'utilizzo della UNION o UNION ALL, ma piuttosto al piano che viene generato differente nei due casi. Visto che poi metti TOP 1, e non indichi una ORDER BY, non è detto che l'ordinamento sia sempre lo stesso per quella query. Se ci pensi, che senso ha una TOP 1 se non esiste un criterio secondo cui un record è il primo?
Quello che voglio dire è che non dovresti lasciare al motore la decisione dell'ordinamento se ti serve un ordine particolare. In quel caso l'order by è necessaria per pilotare la scelta del record in modo corretto.
Spero di essermi spiegato meglio

>Ciao
Ciao!

--
Alessandro Alpi | SQL Server MVP
MCP|MCITP|MCTS|MCT

http://www.alessandroalpi.net
http://blogs.dotnethell.it/suxstellino
http://mvp.support.microsoft.com/profile/Alessandro.Alpi

mmjc23 Profilo | Newbie

Grazie di nuovo per la risposta...
..ma ho ancora qualche difficoltà a capire.

Aldilà del fatto che la funzione con l'utilizzo della "UNION ALL" funzioni correttamente, vorrei evitare di avere "spiacevoli sorprese" in futuro, del tipo che, a causa di qualche strano motivo, mi viene restituito per primo il record di tabella1 e non quello di tabella2.

Il fatto che con "UNION ALL", non significa che in tutte le diverse occasioni la funzione funzionerà (se, l'utilizzo fatto di "UNION ALL" è sbagliato). Magari, al momento funziona perchè nelle due tabelle ho dei particolari dati che restituiscono i dati in modo corretto ma con altri dati, potrebbe non funzionare.

La mia domanda quindi era...
L'utilizzo di UNION ALL, in questo caso, è corretto?
In caso contrario, come potrei ottenere il risultato desiderato (descritto nei post precedenti)? Oppure...come posso fare in modo che il (eventuale) risultato della prima SELECT venga posto come primo Record rispetto al risultato della seconda Select?

Grazie davvero per la pazienza...
Ciao

alx_81 Profilo | Guru

>La mia domanda quindi era...
>L'utilizzo di UNION ALL, in questo caso, è corretto?
ok, non mi sono ancora spiegato..
le "spiacevoli sorprese" le puoi trovare eccome, perchè se non indichi un CORRETTO ORDER BY non potrai mai essere sicuro che dalla tua query in union tornerà sempre lo stesso criterio.

>In caso contrario, come potrei ottenere il risultato desiderato
>(descritto nei post precedenti)? Oppure...come posso fare in
>modo che il (eventuale) risultato della prima SELECT venga posto
>come primo Record rispetto al risultato della seconda Select?
impostando un ORDER BY che ti consenta SEMPRE di avere lo stesso ordine.
Siccome mi sembra di capire che l'ordine è la provenienza della tabella, aggiungi un campo costante che vale 1 per Tabella1 e 2 per Tabella2 e così puoi fare l'order by su quel campo per essere sicuro di avere SEMPRE lo stesso resultset ordinato nello stesso modo.

è un po' più chiaro?
--
Alessandro Alpi | SQL Server MVP
MCP|MCITP|MCTS|MCT

http://www.alessandroalpi.net
http://blogs.dotnethell.it/suxstellino
http://mvp.support.microsoft.com/profile/Alessandro.Alpi

mmjc23 Profilo | Newbie

Si....ora ho capito...

In sostanza, quello che ho fatto, è..."logicamente" sbagliato

Aggiungere però una colonna ad entrambe le tabelle solo per permettere ad una funzione del Database di sapere come ordinare i Dati, non mi sembra "bello" (intendo dire, che non mi sembra elegante come soluzione...non che sia sbagliato...ovviamente).

A questo punto, quindi, visto che la mia soluzione è concettualmente sbagliata, prenderò in considerazione l'utilizzo di una "IF-THEN-ELSE"....
...quindi, se il record è contenuto "nella tabella che mi interessa maggiormente", restituisci quel record, altrimenti, restituisci il record dell'altra tabella.

Ti ringrazio per la spiegazione (so de coccio)
Grazie
Ciao

alx_81 Profilo | Guru

>Aggiungere però una colonna ad entrambe le tabelle solo per permettere
>ad una funzione del Database di sapere come ordinare i Dati,
>non mi sembra "bello" (intendo dire, che non mi sembra elegante
>come soluzione...non che sia sbagliato...ovviamente).
Per quello avrei pensato ad una soluzione JOIN + COALESCE.
Ma anche l'approccio "IF", essendo solo due le tabelle.. non pesa tanto ed è leggibile.
Alla fine basta mettere un valore di una select in una var e poi il valore dell'altra query in un'altra var.
poi COALESCE ed è fatta. Guarda questo semplicissimo esempio:

USE tempdb; GO CREATE TABLE #tempIPs1 (ip varchar(15), id int) GO CREATE TABLE #tempIPs2 (ip varchar(15), id int) GO INSERT INTO #tempIPs1 ( ip, id ) VALUES ( '10.10.10.10', 1 ) INSERT INTO #tempIPs1 ( ip, id ) VALUES ( '10.10.10.20', 2 ) INSERT INTO #tempIPs1 ( ip, id ) VALUES ( '10.10.10.30', 3 ) GO INSERT INTO #tempIPs2 ( ip, id ) VALUES ( '10.10.10.10', 10 ) INSERT INTO #tempIPs2 ( ip, id ) VALUES ( '50.50.50.50', 20 ) INSERT INTO #tempIPs2 ( ip, id ) VALUES ( '50.50.50.70', 30 ) GO DECLARE @val1 int = NULL DECLARE @val2 int = NULL SELECT @val1 = id FROM #tempIPs1 WHERE ip = '50.50.50.50' SELECT @val2 = id FROM #tempIPs2 WHERE ip = '50.50.50.50' SELECT Valore1 = @val1 , Valore2 = @val2 -- prio su val1 ma torna val2, la coalesce torna il primo valore non null fra quelli passati SELECT COALESCE(@val1, @val2) DROP TABLE #tempIPs1 DROP TABLE #tempIPs2 GO

>Ciao
Ciao
--
Alessandro Alpi | SQL Server MVP
MCP|MCITP|MCTS|MCT

http://www.alessandroalpi.net
http://blogs.dotnethell.it/suxstellino
http://mvp.support.microsoft.com/profile/Alessandro.Alpi

mmjc23 Profilo | Newbie

Grazie mille Alex...
Non sapevo dell'esistenza della funzione "COALESCE"

Quante cose che si imparano....tutto chiarissimo

Grazie ancora

mmjc23 Profilo | Newbie

ehm...riprendo un attimo il thread...perchè mi serve per un'altra questione

La soluzione da te postata, funziona correttamente a livello SQL.

Aldilà della soluzione per questo determinato esempio, non esiste proprio un metodo che consenta di ottenere da due SELECT aventi come risultato lo stesso numero (e tipo) di campi, un "recordset" costituito prima dai record della prima tabella e poi dai record della seconda tabella senza dover aggiungere altri campi?

Direi che si tratta più di un "accodamento record"...mentre la UNION, sembra che, unisca/mischi i dati delle due tabelle.

Grazie

EDIT:
Mi rispondo da solo...
Non avevo valutato l'ipotesi di aggiungere una colonna di Ordinamento "Runtime", senza necessariamente aggiungere una colonna alla tabella vera e propria...

Quindi posso fare:

SELECT ID_APPARATO, 1 AS C_ORDER FROM TABELLA2 WHERE INDIRIZZO_IP='127.0.0.1' UNION SELECT ID_APPARATO, 2 AS C_ORDER FROM TABELLA1 WHERE INDIRIZZO_IP='127.0.0.1' ORDER BY C_ORDER

E così, dovrebbe andare....strano questo UNION però
Ciao

alx_81 Profilo | Guru

>E così, dovrebbe andare....strano questo UNION però
evvai! sì, quello.
--
Alessandro Alpi | SQL Server MVP
MCP|MCITP|MCTS|MCT

http://www.alessandroalpi.net
http://blogs.dotnethell.it/suxstellino
http://mvp.support.microsoft.com/profile/Alessandro.Alpi

mmjc23 Profilo | Newbie



Grazie infinite
Ciao
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5