Подтвердить что ты не робот

Совокупная функция SQL для захвата только первой из каждой группы

У меня есть 2 таблицы - таблица учетных записей и таблица Users. У каждой учетной записи может быть несколько пользователей. У меня есть сценарий, в котором я хочу выполнить один запрос/объединение с этими двумя таблицами, но мне нужны все данные учетной записи (Account. *) И только первый набор пользовательских данных (в частности их имя).

Вместо того, чтобы делать "мин" или "максимум" в моей агрегированной группе, я хотел сделать "первый". Но, по-видимому, в TSQL нет "первой" совокупной функции.

Любые предложения о том, как получить этот запрос? Очевидно, что легко получить декартовое произведение Account x Users:

 SELECT User.Name, Account.* FROM Account, User
 WHERE Account.ID = User.Account_ID

Но как я могу получить только от первого пользователя от продукта на основе его User.ID?

4b9b3361

Ответ 1

Вместо того, чтобы группировать, обходите это так...

select
    *

from account a

join (
    select 
        account_id, 
        row_number() over (order by account_id, id) - 
            rank() over (order by account_id) as row_num from user
     ) first on first.account_id = a.id and first.row_num = 0

Ответ 2

Я знаю, что мой ответ немного запоздал, но это может помочь другим. Существует способ достижения First() и Last() в SQL Server, и вот он:

Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')

Используйте Min() для First() и Max() для Last(). Дата DATE_FIELD должна быть датой, определяющей, является ли она первой или последней записью. DESIRED_FIELD - это поле, в котором вы хотите получить первое или последнее значение. Что он делает:

  • Добавить дату в формате ISO в начале строки (длиной 23 символа)
  • Добавить DESIRED_FIELD в эту строку
  • Получить значение MIN/MAX для этого поля (начиная с даты, вы получите первую или последнюю запись)
  • Вещь, которая объединяет строку для удаления первых 23 символов (часть даты)

Здесь вы идете!

EDIT: у меня возникают проблемы с первой формулой: когда DATE_FIELD имеет .000 в миллисекундах, SQL Server возвращает дату как строку с NO миллисекундами вообще, удаляя первые 4 символа из DESIRED_FIELD. Я просто изменил формат на "20" (без миллисекунд), и он отлично работает. Единственный недостаток - если у вас есть два поля, которые были созданы за одни и те же секунды, сортировка может быть беспорядочной... в которой cas вы можете вернуться к "126" для формата.

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')

ИЗМЕНИТЬ 2: Мое первоначальное намерение состояло в том, чтобы вернуть последнюю (или первую) строку NON NULL. Меня спросили, как вернуть последний или первый ряд, иначе он будет нулевым или нет. Просто добавьте ISNULL в DESIRED_FIELD. Когда вы объединяете две строки с оператором +, когда один из них имеет значение NULL, результат равен NULL. Поэтому используйте следующее:

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')

Ответ 3

Select *
From Accounts a
Left Join (
    Select u.*, 
    row_number() over (Partition By u.AccountKey Order By u.UserKey) as Ranking
    From Users u
  ) as UsersRanked
  on UsersRanked.AccountKey = a.AccountKey and UsersRanked.Ranking = 1

Это можно упростить, используя предложение Partition By. В приведенном выше примере, если учетная запись имеет три пользователя, то подзапрос номера их 1,2 и 3, а для другого AccountKey он будет reset numnbering. Это означает, что для каждого уникального AccountKey всегда будет 1 и, возможно, 2,3,4 и т.д.

Таким образом, вы фильтруете Ranking = 1 для захвата первого из каждой группы.

Это даст вам одну строку для каждой учетной записи, и если для этой учетной записи есть хотя бы один пользователь, то она предоставит вам пользователя с самым низким ключом (потому что я использую левое соединение, вы всегда будете иметь список учетных записей даже если пользователь не существует). Замените Order By u.UserKey на другое поле, если вы предпочитаете, чтобы первый пользователь выбирался в алфавитном порядке или какие-либо другие критерии.

Ответ 4

Ответ STUFF от Доминика Гуле гладко. Но если ваш DATE_FIELD является SMALLDATETIME (вместо DATETIME), тогда длина ISO 8601 будет равна 19 вместо 23 (потому что SMALLDATETIME не имеет миллисекунд), поэтому соответствующим образом отрегулируйте параметр STUFF или будет возвращено возвращаемое значение из функции STUFF ( отсутствуют первые четыре символа).

Ответ 5

Первые и последние не существуют в Sql Server 2005 или 2008, но на Sql Server 2012 есть функция First_Value, Last_Value. Я попытался реализовать агрегат First и Last для Sql Server 2005 и столкнулся с тем препятствием, с которым сервер sql гарантирует калькуляцию агрегата в определенном порядке. (См. Свойство атрибута SqlUserDefinedAggregateAttribute.IsInvariantToOrder, которое не реализовано.) Возможно, это связано с тем, что анализатор запросов пытается выполнить вычисление совокупности по нескольким потокам и объединить результаты, что ускоряет выполнение, но не гарантирует порядок в какие элементы агрегированы.

Ответ 6

Вы можете использовать OUTER APPLY, см. документацию.

SELECT User1.Name, Account.* FROM Account
OUTER APPLY 
    (SELECT  TOP 1 Name 
    FROM [User]
    WHERE Account.ID = [User].Account_ID
    ORDER BY Name ASC) User1

Ответ 7

SELECT (SELECT TOP 1 Name 
        FROM User 
        WHERE Account_ID = a.AccountID 
        ORDER BY UserID) [Name],
       a.*
FROM Account a

Ответ 8

Я сравнивал все методы, самый простой и быстрый метод для достижения этого - использование внешнего/крестообразного применения

SELECT u.Name, Account.* FROM Account
OUTER APPLY (SELECT TOP 1 * FROM User WHERE Account.ID = Account_ID ) as u

CROSS APPLY работает так же, как INNER JOIN и выбирает строки, в которых связаны обе таблицы, а OUTER APPLY работает как LEFT OUTER JOIN и извлекает все строки из левой таблицы (здесь учетная запись)

Ответ 9

Есть несколько способов сделать это, здесь быстрый и грязный.

Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
    A.*
FROM Account A

Ответ 10

Определите "Первое". То, что вы считаете первым, - это совпадение, которое обычно имеет отношение к кластерному порядку индекса, но на него нельзя положиться (вы можете изобрести примеры, которые его нарушают).

Вы правы, чтобы не использовать MAX() или MIN(). В то время как заманчиво, рассмотрите сценарий, в котором вы используете имя и фамилию в разных полях. Вы можете получить имена из разных записей.

Так как кажется, что все, что вам действительно нужно, это то, что вы получаете ровно одну произвольную запись для каждой группы, то вы можете сделать только MIN или MAX поле идентификатора для этой записи, а затем присоединиться к таблице в запросе этого идентификатора.

Ответ 11

(Немного Off-Topic, но) Я часто запускаю агрегированные запросы, чтобы перечислять резюме резюме, а затем я хочу знать, ПОЧЕМУ клиент имеет результаты, поэтому используйте MIN и MAX, чтобы дать 2 полувариантных выборки, которые я могу подробнее см.

SELECT Customer.Id, COUNT(*) AS ProblemCount
      , MIN(Invoice.Id) AS MinInv, MAX(Invoice.Id) AS MaxInv
FROM Customer
INNER JOIN Invoice on Invoice.CustomerId = Customer.Id
WHERE Invoice.SomethingHasGoneWrong=1
GROUP BY Customer.Id

Ответ 12

Создайте и присоединитесь к подзапросу "FirstUser", который возвращает первого пользователя для каждой учетной записи.

SELECT User.Name, Account.* 
FROM Account, User, 
 (select min(user.id) id,account_id from User group by user.account_id) as firstUser
WHERE Account.ID = User.Account_ID 
 and User.id = firstUser.id and Account.ID = firstUser.account_id