2010-07-30 1 views
18

У меня есть немного SQL, который почти делает то, что я хочу, чтобы он делал. Я работаю с тремя таблицами, пользователями, UserPhoneNumbers и UserPhoneNumberTypes. Я пытаюсь получить список пользователей с их номерами телефонов для экспорта.Как ограничить LEFT JOIN 1-м результатом SQL Server?

Сама база данных устарела и имеет некоторые проблемы с целостностью. Моя проблема заключается в том, что в базе данных должно быть только 1 тип каждого номера телефона, но это не так. Когда я запускаю это, я получаю многострочные результаты для каждого человека, если они содержат, например, два «домашних» номера.

Как я могу изменить SQL, чтобы перенести первый номер телефона и проигнорировать оставшиеся номера? Я нахожусь в SQL Server, и я знаю о заявлении TOP. Но если я добавлю «ТОП-1» в оператор выбора LEFT JOIN, он просто даст мне 1-ю запись в базе данных, а не 1-ю запись для каждого пользователя.

Это для SQL Server 2000.

Спасибо,

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

Зависит от той версии SQL, о которой вы говорите. Если это SQL Server 2005+, у вас есть много вариантов, включая запросы RANK. – 2010-07-30 21:23:30

+0

SQL Server - версия 2000. – Justin808

+0

Решение [здесь] [1], просто замените join на левое соединение. [1]: http://stackoverflow.com/questions/2043259/sql-server-how-to-join-to-first-row – qub1n

ответ

5

Поскольку это SQL Server 2000 и ранжирования функций, вы могли бы сделать ваш подзапрос ВЫБИРАЕТ агрегировать:

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

тогда и только тогда вы не Care WHICH номеров домашних пользователей возвращаются ...

+0

Это определенно просто, и я использовал это довольно часто. –

+0

Спасибо, для меня результирующее число не проблема, так что есть только один. – Justin808

0

Вы должны определить, что вы имеете в виду под «первым», когда есть два числа одного и того же типа, а затем добавить условие к вашему соединению, так что только правильная запись соответствует критериям. Для этого нет другого ярлыка.

+0

Первых - первая строка, возвращенный оператор выбора. В таблице нет других критериев для ограничения набора результатов. – Justin808

7

Предполагая, что SQL Server 2005+, используйте 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 

виду what? заполнитель для определения первого числа. Опустим ORDER BY, если вы не заботитесь вообще ...

1

Я предполагаю, что у вас есть поле первичного ключа на каждой объединенной таблице, так как UserID не уникален. Я предполагаю, что ваш первичный ключ называется ID. Мы возьмем записи с самым низким ID. Это соответствует вашим «первым» критериям.

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 

Существует целая глава об этом типе выпуска, называется «Неоднозначный Gruops», в книге SQL Antipatterns.

0

Держите, просто чтобы понять вопрос.

У вас есть две таблицы:

пользователей (USERID -> х) UserPhones (Идентификатор_пользователя, PHONETYPE -> номер телефона) и Идентификатор_пользователя/PHONETYPE не является уникальным.

Во-первых, нет никакой необходимости для временных таблиц:

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 

Добавить внутренние соединения по мере необходимости.

Или я что-то упускаю?

+0

Хорошо, я думаю, теперь я понимаю вас лучше ... Есть ли поле идентификатора на телефонном столе? Возможно, вы можете присоединиться к max (id), я знаю, что я сделал подобный до – Gary

+0

. Это не настоящие временные таблицы, а только подзапросы, но вы правы, они вам не нужны. –

0

Вы можете просто использовать 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 

Вместо мин(), вы можете использовать максимум(), а также.

Или вы могли бы сделать это в одной группы:

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 

В этом решении для каждого типа пользователя и номер телефона, я выбираю самое низкое значение первичного ключа из таблицы UserPhoneNumbers (Я догадался, что столбец был назван UserPhoneNumberId).

7

Всякий раз, когда вы хотите выбрать только верхнюю строку из левой таблицы для каждой строки в правой таблице вы должны рассмотреть возможность использования ОТНОСИТЬСЯ оператор вместо соединения, и переместить условие присоединиться к внутри левого присоединения:

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

Действительно ли это в MS SQL 2000? – Justin808

+0

Нет, это SQL 2005 и только после. Я пропустил вас, просил только SQL 2K. –

 Смежные вопросы

  • Нет связанных вопросов^_^