2010-07-30 13 views
18

Tengo un poco de SQL que casi hace lo que quiero que haga. Estoy trabajando con tres tablas, Usuarios, Números de teléfono de usuario y Tipos de número de teléfono de usuario. Estoy tratando de obtener una lista de usuarios con sus números de teléfono para una exportación.¿Cómo limito una UNIÓN IZQUIERDA al primer resultado en SQL Server?

La base de datos en sí es antiguo y tiene algunos problemas de integridad. Mi problema es que solo debería haber 1 tipo de cada número de teléfono en la base de datos, pero ese no es el caso. Cuando ejecuto esto obtengo resultados de varias líneas para cada persona si contienen, por ejemplo, dos números de "Inicio".

¿Cómo puedo modificar el SQL para tomar el primer número de teléfono que aparece e ignorar el resto de números? Estoy en SQL Server y sé sobre la declaración TOP. Pero si agrego 'TOP 1' a la declaración de selección LEFT JOIN es simplemente que me da la primera entrada en la base de datos, no la primera entrada para cada usuario.

Esto es para SQL Server 2000.

Gracias,

SELECT Users.UserID, 
    Users.FirstName, Users.LastName, 
    HomePhone, WorkPhone, FaxNumber 

FROM Users 

LEFT JOIN 
(SELECT UserID, PhoneNumber AS HomePhone 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Home') AS tmpHomePhone 
ON tmpHomePhone.UserID = Users.UserID 
LEFT JOIN 
(SELECT UserID, PhoneNumber AS WorkPhone 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Work') AS tmpWorkPhone 
ON tmpWorkPhone.UserID = Users.UserID 
LEFT JOIN 
(SELECT UserID, PhoneNumber AS FaxNumber 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax') AS tmpFaxNumber 
ON tmpFaxNumber.UserID = Users.UserID 
+0

Depende de qué versión de SQL estás hablando. Si se trata de SQL Server 2005+, tiene muchas opciones, incluidas las consultas de RANK. –

+0

SQL Server es la versión 2000. – Justin808

+0

La solución es [aquí] [1], simplemente reemplace join por left join. [1]: http://stackoverflow.com/questions/2043259/sql-server-how-to-join-to-first-row – qub1n

Respuesta

5

Ya que es SQL Server 2000 y las funciones de clasificación están fuera, usted podría hacer sus SELECTs subconsulta agregan:

SELECT UserID, MAX(PhoneNumber) AS HomePhone FROM [...] GROUP BY UserID 

si y sólo si no lo hace importa cuál de los números de personal de un usuario se devuelven ...

+0

Esto es definitivamente simple, y lo he usado con bastante frecuencia. –

+0

Gracias, para mí el número resultante no es un problema, así que solo hay uno. – Justin808

0

Usted tiene que definir lo que entendemos por "primera" cuando hay dos números del mismo tipo, y luego agregar una condición a su unión para que solo el registro correcto cumpla con los criterios. No hay otro atajo para esto.

+0

En primer lugar - la primera fila devuelta por la instrucción de selección. No hay otros criterios en la tabla para limitar el resultado establecido por. – Justin808

7

Suponiendo SQL Server 2005 +, utilice ROW_NUMBER:

LEFT JOIN (SELECT UserID, 
        PhoneNumber AS HomePhone, 
        ROW_NUMBER() OVER (PARTITION BY userid ORDER BY what?) AS rank 
      FROM UserPhoneNumbers upn 
     LEFT JOIN UserPhoneNumberTypes upnt ON upnt.UserPhoneNumberTypeID = upn.UserPhoneNumberTypeID 
              AND upnt.PhoneNumberType='Home') AS tmpHomePhone 
       ON tmpHomePhone.UserID = Users.UserID 
       AND tmpHomePhone.rank = 1 

mente el marcador de posición what? para determinar el primer número. Omitir el ORDER BY, si no le importa en absoluto ...

1

que se supone que tiene algún campo de clave principal en cada tabla unida, desde identificación de usuario no es única. Asumiré que su clave principal se llama ID. Tomaremos los registros con la ID más baja. Esto cumple con su "primer" criterio.

SELECT Users.UserID, Users.FirstName, Users.LastName, hp.HomePhone, 
     wp.WorkPhone, fn.FaxNumber 
FROM Users 
LEFT JOIN HomePhone hp ON hp.UserID = Users.UserID 
LEFT JOIN HomePhone hp2 ON hp2.UserID = Users.UserID AND hp2.ID < hp.ID 
LEFT JOIN WorkPhone wp ON wp.UserID = Users.UserID 
LEFT JOIN WorkPhone wp2 ON wp2.UserID = Users.UserID AND wp2.ID < wp.ID 
LEFT JOIN FaxNumber fn ON fn.UserID = Users.UserID 
LEFT JOIN FaxNumber fn2 ON fn2.UserID = Users.UserID AND fn2.ID < fn.ID 
WHERE hp2.ID IS NULL AND wp2.ID IS NULL AND fn2.ID IS NULL 

Hay todo un capítulo sobre este tipo de problema, denominado "gruops ambigua", en el libro SQL Antipatterns.

0

Espera, sólo para entender la pregunta.

Tienes dos tablas:

Usuarios (UserID -> x) UserPhones (ID de usuario, PHoneType -> Número de teléfono) y ID de usuario/PhoneType no es única.

En primer lugar no hay necesidad de tablas temporales:

Select 
x 
from 
Users 
inner join 
(
    Select 
    top 1 y 
    from 
    FoneTypes 
    where 
    UserID = users.UseriD 
    and phoneType = 'typex' 
) as PhoneTypex on phonetypex.UserID = users.userID 

Agregar combinaciones internas que sean necesarias.

O me estoy perdiendo algo?

+0

Ok, creo que ahora te entiendo mejor ... ¿Hay un campo de ID en la mesa del teléfono? Quizás puedas unirte a un máximo (id) Sé que lo he hecho de forma similar antes del – Gary

+0

Esas no son realmente tablas temporales, solo subconsultas, pero tienes razón, no las necesitas. –

0

Se podía usar GROUP BY:

SELECT Users.UserID, 
    Users.FirstName, Users.LastName, 
    HomePhone, WorkPhone, FaxNumber 

FROM Users 

LEFT JOIN 
(SELECT UserID, min(PhoneNumber) AS HomePhone 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Home' 
GROUP BY userID) AS tmpHomePhone 
ON tmpHomePhone.UserID = Users.UserID 
LEFT JOIN 
(SELECT UserID, min(PhoneNumber) AS WorkPhone 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Work' 
GROUP BY userID) AS tmpWorkPhone 
ON tmpWorkPhone.UserID = Users.UserID 
LEFT JOIN 
(SELECT UserID, min(PhoneNumber) AS FaxNumber 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax' 
GROUP BY userID) AS tmpFaxNumber 
ON tmpFaxNumber.UserID = Users.UserID 

En lugar de min(), se puede usar max() también.

O usted podría hacerlo en un grupo por:

SELECT Users.UserID, 
    Users.FirstName, Users.LastName, 
    max(HomePhone) as HomePhone, 
    max(WorkPhone) as WorkPhone, 
    max(FaxNumber) as FaxNumber 

FROM Users 

LEFT JOIN 
(SELECT UserID, PhoneNumber AS HomePhone 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Home') AS tmpHomePhone 
ON tmpHomePhone.UserID = Users.UserID 
LEFT JOIN 
(SELECT UserID, PhoneNumber AS WorkPhone 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Work') AS tmpWorkPhone 
ON tmpWorkPhone.UserID = Users.UserID 
LEFT JOIN 
(SELECT UserID, PhoneNumber AS FaxNumber 
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID 
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax') AS tmpFaxNumber 
ON tmpFaxNumber.UserID = Users.UserID 
1
Select Users.UserID, Users.FirstName, Users.LastName 
    , PhoneNumbers.HomePhone 
    , PhoneNumbers.WorkPhone 
    , PhoneNumbers.FaxNumber 
From Users 
    Left Join (
       Select UPN.UserId 
        , Min (Case When PN.PhoneNumberType = 'Home' Then UPN.PhoneNumber End) As HomePhone 
        , Min (Case When PN.PhoneNumberType = 'Work' Then UPN.PhoneNumber End) As WorkPhone 
        , Min (Case When PN.PhoneNumberType = 'Fax' Then UPN.PhoneNumber End) As FaxPhone 
       From UserPhoneNumbers As UPN 
         Join (
           Select Min(UPN1.UserPhoneNumberId) As MinUserPhoneNumberId 
            , UPNT1.PhoneNumberType 
           From UserPhoneNumbers As UPN1 
            Join UserPhoneNumberTypes As UPNT1 
             On UPNT1.UserPhoneNumberTypeID = UPN1.UserPhoneNumberTypeID 
           Where UPNT1.PhoneNumberType In('Home', 'Work', 'Fax') 
           Group By UPN1.UserID, UPNT.PhoneNumberType 
           ) As PN 
          On PN.MinUserPhoneNumberId = UPN.UserPhoneNumberId 
       Group By UPN.UserId 
       ) As PhoneNumbers 
    On PhoneNumbers.UserId = Users.UserId 

En esta solución, para cada tipo de usuario y número de teléfono, estoy recogiendo el valor de la clave primaria más bajo de la mesa UserPhoneNumbers (Supuse que la columna se llamaba UserPhoneNumberId).

7

Cada vez que desee seleccionar sólo una fila superior de una mesa izquierda para cada fila en la tabla de la derecha se debe considerar el uso del operador de aplicación en lugar de unirse, y mover la condición de unión dentro la izquierda de Ingreso:

SELECT u.UserID, 
    u.FirstName, u.LastName, 
    hn.PhoneNumber AS HomePhone 
FROM Users u 
OUTER APPLY (
SELECT TOP(1) PhoneNumber 
FROM UserPhoneNumbers upn 
LEFT JOIN UserPhoneNumberTypes upt 
    ON upn.UserPhoneNumberTypeID=upt.UserPhoneNumberTypeID 
WHERE upt.PhoneNumberType='Home' 
AND upn.UserID = u.UserID 
ORDER BY ...) as hn 
... 
+0

¿Es esto válido en MS SQL 2000? – Justin808

+0

No, es SQL 2005 y solo después. Extrañaba que pidieras solo SQL 2K. –

Cuestiones relacionadas