Быстрый способ запросить последнюю запись? - PullRequest
5 голосов
/ 23 мая 2011

У меня есть таблица вида:

USER |  PLAN |  START_DATE  |   END_DATE
1    |  A    |  20110101    |   NULL
1    |  B    |  20100101    |   20101231
2    |  A    |  20100101    |   20100505

Таким образом, если END_DATE равно null, это означает, что у этого пользователя этот план в настоящее время активен.

Что я хочу запросить: (а) текущий план, который у него есть, или (б) последний план, которым он занимался. Мне нужна только одна строка, возвращаемая для каждого данного пользователя.

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

Спасибо

[EDIT] Большинство ответов здесь возвращают одно значение. Это было мое плохо. Я имел в виду возвращать одно значение для каждого пользователя, но для всех пользователей одновременно. Я адаптировал ответы, которые мог (и исправил вопрос), но пояснил это для дальнейшего использования.

Ответы [ 8 ]

3 голосов
/ 23 мая 2011

На этот вопрос довольно сложно ответить без дополнительной информации о данных и таблице. Когда вы говорите в своем комментарии, что у вас есть все нужные вам индексы, что это за индексы?

Кроме того, периоды времени примыкают и не перекрываются? Вы можете просто получить период с самой последней START_DATE?

Проблема с просмотром END_DATE состоит в том, что обычный индекс B-Tree не индексирует NULL. Таким образом, предикат вида where end_date is nulll вряд ли будет использовать индекс. Вы можете использовать растровый индекс со столбцом, так как индексы этого типа делают индекс пустыми, но это может быть не идеальным из-за некоторых других недостатков растровых индексов.

По причинам, указанным выше, я бы, вероятно, использовал запрос, подобный приведенному ниже:

select user, plan, start_date, end_date
from (
  select 
    user, 
    plan, 
    start_date, 
    end_date, 
    row_number() over (partition by user order start_date desc) as row_num_1,
    row_number() over (partition by user order end_date desc nulls first) as row_num_2
  from user_table
  where user = :userid
)
where row_num_1 = 1

Возможно, вы можете использовать столбец row_num_1 или row_num_2 здесь, в зависимости от точных требований.

OR

select user, plan, start_date, end_date
from (
  select 
    user, 
    plan, 
    start_date, 
    end_date, 
  from user_table
  where user = :userid
  order by start_date desc
)
where rownum = 1

Первый запрос должен работать независимо от того, пытаетесь ли вы вернуть всех пользователей или только одного. Второй запрос будет работать только с одним пользователем.

Если вы можете дополнить вопрос более подробной информацией о схеме (указатели, значения даты начала / окончания), вы, вероятно, получите лучшие ответы.

2 голосов
/ 23 мая 2011
CREATE TABLE XY
( USERID      INTEGER                 NOT NULL
, PLAN        VARCHAR2(8)             NOT NULL
, START_DATE  DATE                    NOT NULL
, END_DATE    DATE                    )
  TABLESPACE USERS;


INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 1, 'A', To_Date('22-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), To_Date('22-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS') );
INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 1, 'B', To_Date('01-04-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), NULL );
INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 2, 'A', To_Date('03-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), To_Date('04-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS') );
INSERT INTO XY ( USERID, PLAN, START_DATE, END_DATE )
       VALUES ( 2, 'B', To_Date('15-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), To_Date('20-05-2011 00:00:00', 'DD-MM-YYYY HH24:MI:SS') );
COMMIT WORK;

SELECT USERID, PLAN, END_DATE, START_DATE
  FROM (SELECT USERID,
               PLAN,
               END_DATE,
               START_DATE,
               ROW_NUMBER() OVER(PARTITION BY USERID ORDER BY END_DATE DESC) SEQUEN
          FROM XY)
 WHERE SEQUEN < 2
1 голос
/ 23 мая 2011

Это может помочь:

SELECT user,plan,end_date,start_date 
FROM ( SELECT users,plans,end_date,start_date, DENSE_RANK() OVER ( PARTITION BY user 
                                                                   ORDER BY end_date DESC) sequen 
        FROM table_name 
     ) 
WHERE sequen <= 2
0 голосов
/ 23 мая 2011

Я предлагаю следующее:

with t as 
(select 1 as col_id, 1 as USER_id, 'A' as PLAN , 20110101 as START_DATE, NULL as  END_DATE from dual union all
 select 2,1,'B', 20100101,20101231 from dual union all
 select 3,2,'A', 20100102,20100505 from dual union all
 select 4,2,'C', 20100101,20100102 from dual)
--
SELECT user_id, plan
  FROM (SELECT user_id,
               plan,
               MAX(nvl(END_DATE, 99999999)) over(PARTITION BY user_id) max_date,
               nvl(END_DATE, 99999999) END_DATE
          FROM t)
 WHERE max_date = end_date
0 голосов
/ 23 мая 2011

Как насчет этого?

select PLAN
from USER_TABLE
where END_DATE is null or END_DATE = (
        select max(END_DATE)
        from USER_TABLE
        where USER = 1 and END_DATE is not null)
    and USER = 1
0 голосов
/ 23 мая 2011

Это работает?

SELECT U.user 
,(SELECT Plan FROM t WHERE t.user=u.user AND end_date IS NULL LIMIT 1) AS Current_Plan
,(SELECT Plan FROM t WHERE t.user=u.user AND end_date IS NOT NULL ORDER BY end_date DESC LIMIT 1) AS Last_Plan
FROM 
( SELECT DISTINCT USER FROM t ) AS U

Если это медленно, пожалуйста, пришлите нам вывод EXPLAIN для запроса.

0 голосов
/ 23 мая 2011

AFAIK Использование CASE и подзапросов сделает ваш запрос очень медленным.Поэтому лучше использовать их с осторожностью.Как насчет:

SELECT User, Plan, start_Date, MAX(End_Date) FROM Plans WHERE User NOT IN 
(SELECT User FROM Plans WHERE End_Date IS NULL)
GROUP BY Start_Date, Plan, User  
UNION  
SELECT User,Plan,Start_Date FROM Plans WHERE End_Date IS NULL

Я не гуру SQL.рассматривайте это как предложение.
Надеюсь, это поможет.

0 голосов
/ 23 мая 2011

Вы пытались ограничить набор результатов с помощью rownum?

select  plan
from    (
        select  plan
        from    YourTable
        where   User = 1
        order by
                case when end_date is null then '99991231' else end_date end desc
        )
where   rownum < 2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...