Нужен лучший вариант - внешнее соединение 32 раз за одним столом - PullRequest
6 голосов
/ 12 мая 2011

У меня неприятная проблема с SQL-запросами, и я бы хотел помочь с элегантным решением.Я пытаюсь избежать 32 внешних левых соединений к одной и той же таблице.

База данных - это Teradata.

У меня есть таблица с 14 миллионами записей и 33 столбцами.Первичный ключ (назовем его Trans_Id) и 32 кодированных поля (назовем их encoded_1 ... encoded_32).Примерно так:

CREATE SET TABLE BigTable ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
     (
      TRANS_ID VARCHAR(20) CHARACTER SET LATIN NOT CASESPECIFIC,
      ENCODED_1 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      ENCODED_2 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      ENCODED_3 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      ...
      ENCODED_32 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC )
PRIMARY INDEX ( TRANS_ID );

У меня тоже есть одна таблица с закодированными / декодированными значениями.Допустим, в этой таблице 100 записей.

CREATE SET TABLE LookupTable ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
     (
      UNIQ_PK { just random numbers }
      ENCODED_VAR VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      DECODED_DESC VARCHAR(50) CHARACTER SET LATIN NOT CASESPECIFIC)
PRIMARY INDEX ( UNIQ_PK );

Я хочу избежать неприятного соединения, подобного этому (я использовал эллипсы вместо показа всех 32 внешних объединений):

SELECT 
TRANS_ID
, a.ENCODED_1
, b1.DECODED_DESC DECODED_DESC_1
, a.ENCODED_2
, b2.DECODED_DESC DECODED_DESC_2
...
, a.ENCODED_31
, b31.DECODED_DESC DECODED_DESC_31
, a.ENCODED_32
, b32.DECODED_DESC DECODED_DESC_32
FROM BigTable a
LEFT OUTER JOIN LookupTable b1 ON a.ENCODED_1 = b1.ENCODED
LEFT OUTER JOIN LookupTable b2 ON a.ENCODED_2 = b1.ENCODED
...
LEFT OUTER JOIN LookupTable b31 ON a.ENCODED_31 = b31.ENCODED
LEFT OUTER JOIN LookupTable b32 ON a.ENCODED_32 = b32.ENCODED

Любая помощь будет оценена.Я чувствую, что внешнее объединение 14M записей 32 раза - не самый эффективный способ сделать это!

Ответы [ 6 ]

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

Вы можете создать функцию, которая принимает в качестве параметра VARCHAR (10) encoded_var и возвращает VARCHAR (50) decoded_desc, тогда ваш выбор будет выглядеть примерно так:

SELECT TRANS_ID,
     ENCODED_1, somefunc(ENCODED_1) AS DECODED_DESC_1,
     ENCODED_2, somefunc(ENCODED_2) AS DECODED_DESC_2,
     etc.

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

2 голосов
/ 12 мая 2011

Если кодированные_1, кодированные_2 и т. Д. Все используются в качестве ключей поиска для одной и той же таблицы, звучит так, будто все они являются «одной и той же идеей».Но я сначала подумал, что лучший дизайн в этом случае будет:

big_table (trans_id, var_id, encoded_var)
lookup_table (encoded_var, decoded_desc)

Тогда запрос просто становится:

select trans_id, var_id, encoded_var, decoded_desc
from big_table
join lookup_table on lookup_table.encoded_var=big_table.encoded_var

Я не знаю, является ли это реальным полемимя или если вы просто пытаетесь опустить не относящиеся к делу детали.Вы можете опустить соответствующие детали здесь.В чем разница между encoded_1 и encoded_2 и т. Д.?Если они являются взаимозаменяемыми, нет никаких причин иметь отдельные поля для них.Действительно, это вызывает много проблем.Даже если есть семантическая разница, если они все используют одну и ту же таблицу поиска, все они должны приходить из одного домена.

Например, несколько лет назад я работал над системой для управления техническими руководствами, которыенаша организация производится и используется.В каждом руководстве было 3 менеджера.(Администратор, который обрабатывал бюджеты и графики, менеджер по запасам, который отслеживал, кому нужны копии, и следил за тем, чтобы они были получены, и менеджер по контенту, отвечающий за фактический текст.) Но все они были взяты из одного и того же списка людей,как часто один и тот же человек может иметь более одной из этих ролей или может иметь разные роли для разных руководств.Поэтому мы составили таблицу «людей» с идентификатором, именем, адресом электронной почты и т. Д., А затем в основной ручной записи я создал 3 столбца, по одному для каждого типа менеджера.

Это была огромная ошибка.Я должен был создать отдельную таблицу с ручным идентификатором, идентификатором типа менеджера и идентификатором человека, а затем иметь 3 ЗАПИСИ для 3 типов менеджера, а не 3 поля в одной записи.

Почему?С тремя столбцами я столкнулся с той же проблемой, которую вы описываете, хотя и в меньшем масштабе: мне пришлось трижды присоединиться к таблице ручного режима к таблице личных данных.Вопрос типа "за какие книги отвечает Боб Смит?"потребовался удивительный сложный запрос, что-то вроде

select ... whatever ...
from manual
join person p1 on p1.person_id=manual.admin_id
join person p2 on p2.person_id=manual.stockmanager_id
join person p3 on p3.person_id=manual.contentmanager_id
where p1.name='Bob Smith'
 or p2.name='Bob Smith'
 or p3.name='Bob Smith'

С одним столбцом это было бы просто

select ... whatever ...
from manual
join manual_manager on manual_manager.manual_id=manual.manual_id
join person on person.person_id=manual_manager.person_id
where person.name='Bob Smith'"

При всех повторениях, было не удивительно, что было нескольковремя, когда программист случайно проверял только 2 поля вместо всех 3. С 1 полем эта ошибка была бы невозможна.С 3 полями, если бы мы добавили менеджер 4-го типа, нам пришлось бы добавить еще один столбец, а затем изменить каждый запрос, который просматривал эти поля.С 1 полем мы, вероятно, не будем.И т. Д.

С 3 полями нам потребовалось 3 индекса, и есть другие факторы, влияющие на производительность.

Я подозреваю, что к вам относится тот же тип мышления.все они полностью взаимозаменяемы, тогда таблице потребуется только порядковый номер для создания уникального pk.Если между ними есть какая-то разница, вы можете создать код для их различения.

1 голос
/ 13 мая 2011

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

Для производительности я бы предложил следующее:

  • Как рекомендовал Роб Паллер, измените первичный индекс для LookupTable на ENCODED_VAR.
  • В LookupTable добавьте новую запись с DECODED_DESC = null и ENCODED_VAR = некоторым значением, которое вы никогда не будете использовать. Обновите BigTable, чтобы заменить все нулевые ENCODED_ * на это значение. Затем вы можете изменить свой запрос на использование всех внутренних объединений и получить тот же результат.
1 голос
/ 12 мая 2011

Я бы поменял PI для таблицы LookUp на Encoded_Var для начинающих.Вы уже должны перераспределить большую таблицу по каждому из столбцов Encoded_Var, чтобы присоединиться к таблице LookUp.Зачем беспокоиться о необходимости каждый раз перераспределять таблицу LookUp.

Есть ли причина, по которой дизайн вашей таблицы не похож на

CREATE SET TABLE BigTable ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
     (
      TRANS_ID    VARCHAR(20) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      ENCODED_VAR VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL
UNIQUE PRIMARY INDEX ( TRANS_ID, ENCODED_VAR );

Это создаст более подходящую 1:M отношения между trans_id и encoded_var.Если не будут указаны соответствующие подробности, объясняющие, почему это не сработает.Фактически, если необходимо, вы можете построить эту таблицу как таблицу отношений и получить другую таблицу, которая выглядит следующим образом:

    CREATE SET TABLE BigTable2 ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
         (
          TRANS_ID    VARCHAR(20) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
          OtherData1  VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
          OtherData2  SMALLINT NOT NULL,
          ....,
          OtherDataN  VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL
   UNIQUE PRIMARY INDEX ( TRANS_ID );

Надеюсь, что это поможет.

0 голосов
/ 29 июля 2011

Я столкнулся с той же проблемой, также на Teradata. Коллега сообщил мне об элегантном решении для вас, использующем один LEFT OUTER JOIN и несколько операторов CASE.

Однако ваш пример немного сбивает с толку, потому что вы объединяете столбец, которого не существует (столбец «ENCODED» в «LookupTable», который, как я предполагаю, должен быть «ENCODED_VAR»?).

SELECT     TRANS_ID    
           , a.ENCODED_1    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_1
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_1    
           , a.ENCODED_2    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_2
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_2    
           ...    
           , a.ENCODED_31    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_31
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_31    
           , a.ENCODED_32    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_32
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_32    
FROM BigTable a
LEFT OUTER JOIN LookupTable b 
    ON a.ENCODED_1 = b.ENCODED_VAR
    OR a.ENCODED_2 = b.ENCODED_VAR
    ...
    OR a.ENCODED_31 = b.ENCODED_VAR
    OR a.ENCODED_32 = b.ENCODED_VAR
GROUP BY a.TRANS_ID 

Это зависит от отношения 1: 1 между ENCODED_n в BigTable и ENCODED_VAR в LookupTable.

Кроме того, кроме того, вы не должны использовать случайное число в качестве ОСНОВНОГО ИНДЕКСА в таблице Teradata. Хотя это даст вам отличное распределение таблиц, оно будет совершенно бесполезным при поиске таблиц. Если вы используете общедоступное поле для PI, база данных может перейти непосредственно к AMP, в котором хранятся данные. Однако без этого СУБД должна каждый раз выполнять сканирование полной таблицы. Скорее всего, вы могли бы использовать ENCODED_VAR в качестве своего ПЕРВИЧНОГО ИНДЕКСА и увидеть значительно улучшенную производительность, если распределение все еще остается разумным.

Я надеюсь, что это работает. Я думаю, что это будет работать в вашем случае. Я не проверял, что мой код правильный, но он очень похож на мое собственное решение.

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

Неужели это не может быть так:

SELECT 
TRANS_ID
, a.ENCODED_1
, CASE a.ENCODED_1 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_1
, a.ENCODED_2
, CASE a.ENCODED_2 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_2
...
, a.ENCODED_31
, CASE a.ENCODED_31 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_31
, a.ENCODED_32
, CASE a.ENCODED_32 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_32
FROM BigTable a
 LEFT JOIN LookupTable b ON (
   a.ENCODED_1 = b.ENCODED OR
   a.ENCODED_2 = b.ENCODED OR
   ...
   a.ENCODED_31 = b.ENCODED OR
   a.ENCODED_32 = b.ENCODED
 )

У меня также может возникнуть желание переписать условие соединения следующим образом:

...ON b.ENCODED IN (a.ENCODED_1, a.ENCODED_2, ... a.ENCODED_31, a.ENCODED_32)

Но я не совсем уверен, что он не может быть медленнее, чем предыдущая версия. На самом деле, я думаю, что это действительно будет медленнее, но я все равно проверю это.

...