Отримайте верхній 1 ряд кожної групи


527

У мене є таблиця, в якій я хочу отримати останню запис для кожної групи. Ось таблиця:

DocumentStatusLogs Таблиця

|ID| DocumentID | Status | DateCreated |
| 2| 1          | S1     | 7/29/2011   |
| 3| 1          | S2     | 7/30/2011   |
| 6| 1          | S1     | 8/02/2011   |
| 1| 2          | S1     | 7/28/2011   |
| 4| 2          | S2     | 7/30/2011   |
| 5| 2          | S3     | 8/01/2011   |
| 6| 3          | S1     | 8/02/2011   |

Таблиця буде групуватися DocumentIDта сортуватись DateCreatedу порядку зменшення. Для кожного DocumentIDя хочу отримати останній статус.

Мій бажаний вихід:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • Чи є якась сукупна функція, щоб отримати лише верх від кожної групи? Дивіться псевдо-код GetOnlyTheTopнижче:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
  • Якщо такої функції не існує, чи можна досягти потрібного результату?

  • Або, по-перше, це може бути викликано ненормалізованою базою даних? Я думаю, оскільки те, що я шукаю, - це лише один ряд, чи повинен він statusтакож розташовуватися в батьківській таблиці?

Будь ласка, дивіться батьківську таблицю для отримання додаткової інформації:

Поточна Documentsтаблиця

| DocumentID | Title  | Content  | DateCreated |
| 1          | TitleA | ...      | ...         |
| 2          | TitleB | ...      | ...         |
| 3          | TitleC | ...      | ...         |

Чи повинна така батьківська таблиця бути такою, щоб я міг легко отримати доступ до її статусу?

| DocumentID | Title  | Content  | DateCreated | CurrentStatus |
| 1          | TitleA | ...      | ...         | s1            |
| 2          | TitleB | ...      | ...         | s3            |
| 3          | TitleC | ...      | ...         | s1            |

ОНОВЛЕННЯ Я щойно навчився використовувати "застосувати", що полегшує вирішення таких проблем.


2
Для більш детального обговорення та порівняння можливих рішень рекомендую прочитати подібне питання на dba.se: Отримання n рядків у групі .
Володимир Баранов

Я подивився на пост і спробував його. Використання групи по StoreID створило помилку.
UltraJ

Відповіді:


753
;WITH cte AS
(
   SELECT *,
         ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC) AS rn
   FROM DocumentStatusLogs
)
SELECT *
FROM cte
WHERE rn = 1

Якщо ви очікуєте 2 записи на день, то це буде довільно вибрати один. Щоб отримати обидві записи на день, замість цього використовуйте DENSE_RANK

Що стосується нормалізованого чи ні, то це залежить від того, чи хочете ви:

  • підтримувати статус у 2-х місцях
  • зберегти історію статусу
  • ...

На даний момент ви зберігаєте історію статусу. Якщо ви також хочете останнього статусу в батьківській таблиці (це денормалізація), вам знадобиться тригер, щоб підтримувати "статус" у батьківській. або опустити цю таблицю історії статусу.


5
І ... Що таке Partition By? Withдля мене також нова :( Я все одно використовую mssql 2005.
dpp,

6
@domanokz: Partition By скидає підрахунок. Тож у цьому випадку йдеться про підрахунок за DocumentID
gbn

1
Гм, я хвилююся про продуктивність, буду запитувати мільйони рядків. Чи впливає SELECT * FROM (SELECT ...) на продуктивність? Також чи є ROW_NUMBERякийсь підзапит для кожного рядка?
dpp

1
@domanokz: ні, це не підзапит. Якщо ви маєте правильні індекси, мільйони не повинні бути проблемою. Так чи інакше, існує лише два способи, засновані на множині: це і сукупність (рішення Аріеля). Тож спробуйте їх обоє ...
gbn

1
@domanokz: Просто змініть ЗАМОВЛЕННЯ DateCreate DESC на ORDER BY ID DESC
gbn

184

Я тільки навчився користуватися cross apply. Ось як це використовувати в цьому сценарії:

 select d.DocumentID, ds.Status, ds.DateCreated 
 from Documents as d 
 cross apply 
     (select top 1 Status, DateCreated
      from DocumentStatusLogs 
      where DocumentID = d.DocumentId
      order by DateCreated desc) as ds

2
Це насправді не має ніякого значення, оскільки питання все ще вирішується.
dpp

19
Я щойно опублікував результати моїх випробувань на терміни проти всіх запропонованих рішень, і ваші вийшли на перше місце. Даючи вам голос :-)
Джон Фербенкс

3
+1 для величезного покращення швидкості. Це набагато швидше, ніж функція вікон, наприклад ROW_NUMBER (). Було б непогано, якби SQL розпізнав ROW_NUMBER () = 1 подібних запитів і оптимізував їх у додатки. Примітка. Я використовував ЗОВНІШНІ ЗАЯВКИ, оскільки мені потрібні результати, навіть якщо вони не існували в застосуванні.
TamusJRoyce

8
@TamusJRoyce ви не можете екстраполювати це лише тому, що це було швидше, коли це завжди так. Це залежить. Як описано тут sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
Мій коментар стосується наявності декількох рядків і бажаю лише одного з цих кількох рядків у групі. Приєднуйтесь до того, коли ви хочете одного з багатьма. Застосовується для тих випадків, коли у вас є один до багатьох, але ви хочете відфільтрувати всі, крім одного до одного. Сценарій: Для 100 членів дайте мені кожен свій найкращий номер телефону (де кожен може мати кілька номерів). Тут застосовується Excels. Менше читання = менший доступ до диска = краща продуктивність. З огляду на мій досвід роботи з погано розробленими ненормованими базами даних.
TamusJRoyce

53

Я зробив декілька термінів щодо різних рекомендацій тут, і результати дійсно залежать від розміру відповідної таблиці, але найбільш послідовним рішенням є використання CROSS APPLY. Ці тести проводилися проти SQL Server 2008-R2, використовуючи таблицю з 6.500 записів та ще одна (однакова схема) із 137 мільйонами записів. Стовпці, що запитуються, є частиною первинного ключа таблиці, а ширина таблиці дуже мала (близько 30 байт). Про час повідомляє SQL Server із фактичного плану виконання.

Query                                  Time for 6500 (ms)    Time for 137M(ms)

CROSS APPLY                                    17.9                17.9
SELECT WHERE col = (SELECT MAX(COL)…)           6.6               854.4
DENSE_RANK() OVER PARTITION                     6.6               907.1

Я думаю, що насправді дивовижна річ полягала в тому, наскільки послідовним був час КРОСНОЇ ЗАЯВКИ незалежно від кількості залучених рядків.


8
Все залежить від розподілу даних та наявних індексів. Він обговорювалося на великих довжинах на dba.se .
Володимир Баранов

48

Я знаю, що це стара тема, але TOP 1 WITH TIESрішення є дуже приємними і можуть бути корисними для читання рішень.

select top 1 with ties
   DocumentID
  ,Status
  ,DateCreated
from DocumentStatusLogs
order by row_number() over (partition by DocumentID order by DateCreated desc)

Більше про пункт TOP можна ознайомитись тут .


7
Це найелегантніше рішення imo
Джордж Менутіс

1
домовились - це найкраще повторює те, що дуже легко зробити в інших версіях SQL та інших мовах imo
Chris Umphlett

27

Якщо ви турбуєтесь про продуктивність, ви також можете це зробити за допомогою MAX ():

SELECT *
FROM DocumentStatusLogs D
WHERE DateCreated = (SELECT MAX(DateCreated) FROM DocumentStatusLogs WHERE ID = D.ID)

ROW_NUMBER () вимагає сортування всіх рядків у вашому операторі SELECT, тоді як MAX - ні. Слід різко прискорити ваш запит.


2
Не вдається вирішити проблеми з продуктивністю ROW_NUMBER () відповідним індексуванням? (Я відчуваю, що це потрібно зробити як завгодно)
Кристоффер L

8
З датою часу ви не можете гарантувати, що дві записи не будуть додані в одну і ту ж дату і час. Точність недостатньо висока.
TamusJRoyce

+1 для простоти. @TamusJRoyce має рацію. А як на рахунок? 'вибрати * з DocumentStatusLog D, де ID = (виберіть ідентифікатор з DocumentsStatusLog, де D.DocumentID = Порядок DocumentID за лімітом 1 DateCreate DESC);'
cibercitizen1

ВИБІР * ВІД EventScheduleTbl D ДЕРЖАВИ Вибрані дати = (ВИБРАТИ верхній 1 хв. (ДатиВибрано) ІЗ EventScheduleTbl ДЕ EventIDf = D.EventIDf і DatesPicked> = convert (дата, getdate ()))
Arun Prasad ES

Однозначно є випадки, коли це випереджає row_number()навіть при правильній індексації. Я вважаю це особливо цінним у сценаріях самостійного приєднання. Однак слід пам’ятати, що цей метод часто дає більшу кількість як логічних зчитувань, так і підрахунків сканувань, незважаючи на повідомлення про низьку вартість піддіаграму. Вам потрібно буде зважити вартість / вигоди у вашому конкретному випадку, щоб визначити, чи справді це краще.
pimbrouwers

26
SELECT * FROM
DocumentStatusLogs JOIN (
  SELECT DocumentID, MAX(DateCreated) DateCreated
  FROM DocumentStatusLogs
  GROUP BY DocumentID
  ) max_date USING (DocumentID, DateCreated)

Який сервер бази даних? Цей код працює не на всіх.

Що стосується другої половини вашого питання, мені здається розумним включати статус як стовпець. Можна пітиDocumentStatusLogs як журнал, але все ж зберігати останню інформацію в головній таблиці.

До речі, якщо у вас вже є DateCreatedстовпець у таблиці "Документи", ви можете просто приєднатись до DocumentStatusLogsцього (до тих пір, поки DateCreatedце унікально вDocumentStatusLogs ).

Редагувати: MsSQL не підтримує USING, тому змініть його на:

ON DocumentStatusLogs.DocumentID = max_date.DocumentID AND DocumentStatusLogs.DateCreated = max_date.DateCreated

5
Підказка була в назві: MSSQL. У SQL Server немає використання, але ідея в порядку.
gbn

7
@gbn Дурні модератори зазвичай видаляють важливі ключові слова із заголовків, як вони це робили тут. Це ускладнює пошук правильних відповідей в результатах пошуку або Google.
NickG

2
Джус зазначить, що це "рішення" все ще може дати вам кілька записів, якщо у вас є нічия наmax(DateCreated)
MoonKnight

12

Це одне з найбільш легко знайдених питань у цій темі, тому я хотів дати сучасну відповідь на це (як для моєї довідки, так і для того, щоб допомогти іншим). Використовуючи first_valueта overви можете зробити коротку роботу над вищезазначеним запитом:

Select distinct DocumentID
  , first_value(status) over (partition by DocumentID order by DateCreated Desc) as Status
  , first_value(DateCreated) over (partition by DocumentID order by DateCreated Desc) as DateCreated
From DocumentStatusLogs

Це має працювати в Sql Server 2008 і новіших версіях. First_valueможна розглядати як спосіб досягнення Select Top 1під час використання overпункту. Overдозволяє групувати у списку вибору, тому замість того, щоб писати вкладені підзапити (як це робиться у багатьох існуючих відповідях), це робить це більш читабельним способом. Сподіваюсь, це допомагає.


2
Це не працює в SQL Server 2008 R2. Я думаю, що first_value була представлена ​​у 2012 році!
ufo

1
Дуже швидко! Я використовував рішення Cross Apply, яке пропонував @dpp, але це швидше.
MattSlay

11

Це досить стара нитка, але я думав, що я кину два мої центи так само, як прийнята відповідь не спрацювала для мене особливо добре. Я спробував рішення gbn на великому наборі даних і виявив, що він дуже повільний (> 45 секунд на 5 мільйонів плюс записи в SQL Server 2012). Дивлячись на план виконання, очевидно, що проблема полягає в тому, що для нього потрібна операція SORT, яка значно сповільнює роботу.

Ось альтернатива, яку я підняв із структури сутності, яка не потребує операції SORT, і здійснює пошук в Індексі NON-Clustered Index. Це скорочує час виконання до <2 секунди для згаданого набору записів.

SELECT 
[Limit1].[DocumentID] AS [DocumentID], 
[Limit1].[Status] AS [Status], 
[Limit1].[DateCreated] AS [DateCreated]
FROM   (SELECT DISTINCT [Extent1].[DocumentID] AS [DocumentID] FROM [dbo].[DocumentStatusLogs] AS [Extent1]) AS [Distinct1]
OUTER APPLY  (SELECT TOP (1) [Project2].[ID] AS [ID], [Project2].[DocumentID] AS [DocumentID], [Project2].[Status] AS [Status], [Project2].[DateCreated] AS [DateCreated]
    FROM (SELECT 
        [Extent2].[ID] AS [ID], 
        [Extent2].[DocumentID] AS [DocumentID], 
        [Extent2].[Status] AS [Status], 
        [Extent2].[DateCreated] AS [DateCreated]
        FROM [dbo].[DocumentStatusLogs] AS [Extent2]
        WHERE ([Distinct1].[DocumentID] = [Extent2].[DocumentID])
    )  AS [Project2]
    ORDER BY [Project2].[ID] DESC) AS [Limit1]

Тепер я припускаю те, що не повністю вказано в оригінальному запитанні, але якщо дизайн таблиці такий, що ваш стовпець ідентифікатора є ідентифікатором автоматичного збільшення, а DateCreate встановлюється поточною датою з кожним вкладишем, то навіть без запуску з моїм запитом вище, ви насправді могли отримати значне підвищення продуктивності для рішення gbn (приблизно половину часу виконання) лише від замовлення на ID замість замовлення на DateCreate, оскільки це забезпечить ідентичний порядок сортування, і це швидше сортування.


5

Мій код, щоб вибрати топ-1 з кожної групи

виберіть. * з #DocumentStatusLogs a where 
 datecreate in (виберіть топ-1 datecreate з #DocumentStatusLogs b
де 
a.documentid = b.documentid
замовити за датою створення дес
)

3

Перевірка дивовижної та правильної відповіді Клінта зверху:

Продуктивність між двома запитами нижче є цікавою. 52% - це перше місце. І 48% - це другий. Покращення продуктивності на 4%, використовуючи DISTINCT замість ЗАМОВИТИ ЗА. Але ORDER BY має перевагу сортування за кількома стовпцями.

IF (OBJECT_ID('tempdb..#DocumentStatusLogs') IS NOT NULL) BEGIN DROP TABLE #DocumentStatusLogs END

CREATE TABLE #DocumentStatusLogs (
    [ID] int NOT NULL,
    [DocumentID] int NOT NULL,
    [Status] varchar(20),
    [DateCreated] datetime
)

INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (2, 1, 'S1', '7/29/2011 1:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (3, 1, 'S2', '7/30/2011 2:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (6, 1, 'S1', '8/02/2011 3:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (1, 2, 'S1', '7/28/2011 4:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (4, 2, 'S2', '7/30/2011 5:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (5, 2, 'S3', '8/01/2011 6:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (6, 3, 'S1', '8/02/2011 7:00:00')

Варіант 1:

    SELECT
    [Extent1].[ID], 
    [Extent1].[DocumentID],
    [Extent1].[Status], 
    [Extent1].[DateCreated]
FROM #DocumentStatusLogs AS [Extent1]
    OUTER APPLY (
        SELECT TOP 1
            [Extent2].[ID], 
            [Extent2].[DocumentID],
            [Extent2].[Status], 
            [Extent2].[DateCreated]
        FROM #DocumentStatusLogs AS [Extent2]
        WHERE [Extent1].[DocumentID] = [Extent2].[DocumentID]
        ORDER BY [Extent2].[DateCreated] DESC, [Extent2].[ID] DESC
    ) AS [Project2]
WHERE ([Project2].[ID] IS NULL OR [Project2].[ID] = [Extent1].[ID])

Варіант 2:

SELECT 
    [Limit1].[DocumentID] AS [ID], 
    [Limit1].[DocumentID] AS [DocumentID], 
    [Limit1].[Status] AS [Status], 
    [Limit1].[DateCreated] AS [DateCreated]
FROM (
    SELECT DISTINCT [Extent1].[DocumentID] AS [DocumentID] FROM #DocumentStatusLogs AS [Extent1]
) AS [Distinct1]
    OUTER APPLY  (
        SELECT TOP (1) [Project2].[ID] AS [ID], [Project2].[DocumentID] AS [DocumentID], [Project2].[Status] AS [Status], [Project2].[DateCreated] AS [DateCreated]
        FROM (
            SELECT 
                [Extent2].[ID] AS [ID], 
                [Extent2].[DocumentID] AS [DocumentID], 
                [Extent2].[Status] AS [Status], 
                [Extent2].[DateCreated] AS [DateCreated]
            FROM #DocumentStatusLogs AS [Extent2]
            WHERE [Distinct1].[DocumentID] = [Extent2].[DocumentID]
        )  AS [Project2]
        ORDER BY [Project2].[ID] DESC
    ) AS [Limit1]

M $ 's Management Studio: Після виділення та запуску першого блоку виділіть як Варіант 1, так і Варіант 2, клацніть правою кнопкою миші -> [Відобразити передбачуваний план виконання]. Потім запустіть всю справу, щоб побачити результати.

Варіант 1 Результати:

ID  DocumentID  Status  DateCreated
6   1   S1  8/2/11 3:00
5   2   S3  8/1/11 6:00
6   3   S1  8/2/11 7:00

Варіант 2 Результати:

ID  DocumentID  Status  DateCreated
6   1   S1  8/2/11 3:00
5   2   S3  8/1/11 6:00
6   3   S1  8/2/11 7:00

Примітка:

Я схильний використовувати APPLY, коли хочу, щоб приєднання було 1-до- (1 з багатьох).

Я використовую ПРИЄДНУЙСЯ, якщо хочу, щоб приєднання було 1-до-багатьох або багато-до-багатьох.

Я уникаю CTE з ROW_NUMBER (), якщо мені не потрібно зробити щось просунуте і я не в порядку з покаранням продуктивності вікна.

Я також уникаю запитів EXISTS / IN у пункті WHERE або ON, оскільки я відчував це, викликаючи жахливі плани виконання. Але пробіг змінюється. Перегляньте план виконання та ефективність профілю там, де і коли це потрібно!


3

Це рішення можна використовувати для отримання найновіших рядків TOP N для кожного розділу (у прикладі N - 1 у операторі WHERE, а розділ - doc_id):

SELECT doc_id, status, date_created FROM 
(
    SELECT a.*, ROW_NUMBER() OVER (PARTITION BY doc_id ORDER BY date_created DESC) AS rnk FROM doc a
)
WHERE rnk = 1;

2
SELECT o.*
FROM `DocumentStatusLogs` o                   
  LEFT JOIN `DocumentStatusLogs` b                   
  ON o.DocumentID = b.DocumentID AND o.DateCreated < b.DateCreated
 WHERE b.DocumentID is NULL ;

Якщо ви хочете повернути лише нещодавнє замовлення документа до DateCreate, він поверне лише документ першої першої сторони від DocumentID


2

CROSS APPLYбув методом, який я використовував для свого рішення, оскільки він працював і для мене, і для потреб моїх клієнтів. І з того, що я читав, слід забезпечити найкращу загальну ефективність, якщо їх база даних значно зросте.


1

Ось 3 окремі підходи до цієї проблеми разом із найкращим вибором індексації для кожного із цих запитів (будь ласка, спробуйте самі індекси та побачите логічне прочитане, минулий час, план виконання. Я надав пропозиції зі свого досвіду щодо такі запити без виконання цієї конкретної проблеми).

Підхід 1 : Використання ROW_NUMBER (). Якщо індекс rowstore не в змозі підвищити ефективність, ви можете спробувати некластеризований / кластеризований індекс зберігання стовпців, як для запитів з агрегуванням та групуванням, так і для таблиць, які впорядковані у різних стовпцях постійно, індекс зберігання стовпців зазвичай є найкращим вибором.

;WITH CTE AS
    (
       SELECT   *,
                RN = ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
       FROM     DocumentStatusLogs
    )
    SELECT  ID      
        ,DocumentID 
        ,Status     
        ,DateCreated
    FROM    CTE
    WHERE   RN = 1;

Підхід 2 : Використання FIRST_VALUE. Якщо індекс rowstore не в змозі підвищити ефективність, ви можете спробувати некластеризований / кластеризований індекс зберігання стовпців, як для запитів з агрегуванням та групуванням, так і для таблиць, які впорядковані у різних стовпцях постійно, індекс зберігання стовпців зазвичай є найкращим вибором.

SELECT  DISTINCT
    ID      = FIRST_VALUE(ID) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
    ,DocumentID
    ,Status     = FIRST_VALUE(Status) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
    ,DateCreated    = FIRST_VALUE(DateCreated) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
FROM    DocumentStatusLogs;

Підхід 3 : Використання CROSS APPLY. Створення індексу зберігання рядків у таблиці DocumentStatusLogs, що охоплює стовпці, використані в запиті, повинно бути достатньо для покриття запиту без необхідності індексу зберігання стовпців.

SELECT  DISTINCT
    ID      = CA.ID
    ,DocumentID = D.DocumentID
    ,Status     = CA.Status 
    ,DateCreated    = CA.DateCreated
FROM    DocumentStatusLogs D
    CROSS APPLY (
            SELECT  TOP 1 I.*
            FROM    DocumentStatusLogs I
            WHERE   I.DocumentID = D.DocumentID
            ORDER   BY I.DateCreated DESC
            ) CA;

1

Я вважаю, що це можна зробити саме так. Це може потребувати певного налаштування, але ви можете просто вибрати максимум із групи.

Ці відповіді є надмірними.

SELECT
  d.DocumentID,
  MAX(d.Status),
  MAX(d1.DateCreated)
FROM DocumentStatusLogs d, DocumentStatusLogs d1
USING(DocumentID)
GROUP BY d.DocumentID
ORDER BY DateCreated DESC

0

У сценаріях, коли ви хочете уникати використання row_count (), ви також можете використовувати ліву приєднання:

select ds.DocumentID, ds.Status, ds.DateCreated 
from DocumentStatusLogs ds
left join DocumentStatusLogs filter 
    ON ds.DocumentID = filter.DocumentID
    -- Match any row that has another row that was created after it.
    AND ds.DateCreated < filter.DateCreated
-- then filter out any rows that matched 
where filter.DocumentID is null 

Для прикладної схеми ви також можете використати "не в підзапиті", який, як правило, компілюється до того ж виходу, що і ліве з'єднання:

select ds.DocumentID, ds.Status, ds.DateCreated 
from DocumentStatusLogs ds
WHERE ds.ID NOT IN (
    SELECT filter.ID 
    FROM DocumentStatusLogs filter
    WHERE ds.DocumentID = filter.DocumentID
        AND ds.DateCreated < filter.DateCreated)

Зауважте, шаблон запиту не працюватиме, якби у таблиці не було принаймні одного унікального ключа / обмеження / індексу з одним стовпцем, у цьому випадку первинний ключ "Id".

Обидва ці запити, як правило, "дорожчі", ніж запит row_count () (як вимірюється Аналізатором запитів). Однак ви можете зіткнутися зі сценаріями, коли вони швидше повертають результати або дозволяють інші оптимізації.


0
SELECT documentid, 
       status, 
       datecreated 
FROM   documentstatuslogs dlogs 
WHERE  status = (SELECT status 
                 FROM   documentstatuslogs 
                 WHERE  documentid = dlogs.documentid 
                 ORDER  BY datecreated DESC 
                 LIMIT  1) 

0

Спробуйте це:

SELECT [DocumentID]
    ,[tmpRez].value('/x[2]', 'varchar(20)') AS [Status]
    ,[tmpRez].value('/x[3]', 'datetime') AS [DateCreated]
FROM (
    SELECT [DocumentID]
        ,cast('<x>' + max(cast([ID] AS VARCHAR(10)) + '</x><x>' + [Status] + '</x><x>' + cast([DateCreated] AS VARCHAR(20))) + '</x>' AS XML) AS [tmpRez]
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ) AS [tmpQry]

Ви завжди повинні описати свій оператор SQL, як він буде працювати і вирішити запит ОП.
Сурай Кумар

-1

Це сама ванільна TSQL, яку я можу придумати

    SELECT * FROM DocumentStatusLogs D1 JOIN
    (
      SELECT
        DocumentID,MAX(DateCreated) AS MaxDate
      FROM
        DocumentStatusLogs
      GROUP BY
        DocumentID
    ) D2
    ON
      D2.DocumentID=D1.DocumentID
    AND
      D2.MaxDate=D1.DateCreated

На жаль, MaxDate не унікальний. Можна вказати дві дати в той самий точний час. Таким чином, це може призвести до дублікатів на групу. Однак ви можете використовувати стовпець посвідчення чи GUID. Стовпець посвідчення отримає найновіший, який було введено (використовується виклик ідентифікації за замовчуванням, 1 ... x крок 1).
TamusJRoyce

Добре я згоден, але автор попросив останнього запису - який, якщо ви не включите стовпець з автоматичним збільшенням, означає, що два елементи, додані точно в один і той же час, однаково є "останніми"
багаті з

Останній рекорд буде одним записом. Так що так. Потрібно врахувати стовпчик ідентифікації автоматичного збільшення.
TamusJRoyce

-2

У SQLite перевірено, що ви можете використовувати наступний простий запит із GROUP BY

SELECT MAX(DateCreated), *
FROM DocumentStatusLogs
GROUP BY DocumentID

Тут MAX допомагає отримати максимум DateCreate ВІД кожної групи.

Але здається, що MYSQL не пов'язує *-стовпці зі значенням max DateCreate :(

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.