2016-12-13 11 views
3

У меня есть 2 таблицы, давайте назовем их T_FATHER и T_CHILD, где каждый отец может иметь несколько Чайлдс, например, так:Подсчет записей в зависимости от внешнего ключа

T_FATHER 
-------------------------- 
ID - BIGINT, from Generator 

T_CHILD 
------------------------------- 
ID - BIGINT, from Generator 
FATHER_ID - BIGINT, Foreign Key 

Теперь я хочу, чтобы добавить счетчик в T_CHILD таблице , который начинается с 1 и добавляет 1 для каждого нового ребенка, но не глобально, а на отца, как:

ID | FATHER_ID | COUNTER | 
-------------------------- 
1 | 1   | 1  | 
-------------------------- 
2 | 1   | 2  | 
-------------------------- 
3 | 2   | 1  | 
-------------------------- 

Моя первая мысль была создать до-вкладышем-триггер, который подсчитывает, сколько Чайлдс присутствуют для отцу и добавить 1 для счетчика. Это должно работать нормально, если одновременно нет двух вставок, которые заканчиваются одним и тем же счетчиком. Шансы высоки, что на самом деле это никогда не происходит - но лучше экономить, чем сожалеть.

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

Мой текущий подход использует вышеупомянутый триггер и добавляет уникальный индекс к FATHER_ID + COUNTER, так что проходит только одна из одновременных вставок. Мне придется обрабатывать клиентскую часть исключения (и повторить попытку неудачной вставки).

Есть ли лучший способ справиться с этим прямо в Firebird?

PS: Никаких удалений в любой из двух таблиц не будет, поэтому это не проблема.

ответ

1

И когда вы пытаетесь вычислить поле и выбрать решение Thijs van Dien?

CREATE TABLE T_CHILD(
    ID INTEGER, 
    FATHER_ID INTEGER, 
    COUNTER COMPUTED BY (
    (SELECT 1 + COUNT(*) 
     FROM T_CHILD AS OTHERS 
     WHERE OTHERS.FATHER_ID = T_CHILD.FATHER_ID 
      AND OTHERS.ID < T_CHILD.ID) 
) 
); 
2

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

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

Вы можете решить не хранить счетчик как таковой - хранение счетчиков в базе данных часто является плохой идеей. Вместо этого отслеживайте только порядок вставки. Для этого генератор имеет значение, поскольку каждая новая запись будет иметь более высокое значение и пробелов не будет иметь значения. На самом деле вам не нужно ничего, кроме ID, который у вас уже есть. Определите нумерацию при выборе; для каждого ребенка вы хотите узнать, сколько детей есть с одним и тем же отцом, но ниже ID. В качестве бонуса удаление будет работать нормально.

Вот доказательство концепции с помощью вложенного запроса:

SELECT ID, FATHER_ID, 
     (SELECT 1 + COUNT(*) 
     FROM T_CHILD AS OTHERS 
     WHERE OTHERS.FATHER_ID = C.FATHER_ID 
      AND OTHERS.ID < C.ID) AS COUNTER 
FROM T_CHILD AS C 

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

SELECT * FROM (
    SELECT ID, FATHER_ID, 
     ROW_NUMBER() OVER(PARTITION BY FATHER_ID ORDER BY ID) AS COUNTER 
    FROM T_CHILD 
    -- Filtering that wouldn't affect COUNTER (e.g. WHERE FATHER_ID ... AND ID < ...) 
) 
-- Filtering that would affect COUNTER (e.g. WHERE ID > ...) 

Эти два варианта имеют совершенно разные характеристики. Какой из них, даже если он вообще подходит, зависит от вашего размера данных и шаблонов доступа.

+0

Благодарим вас за ответ. Я бы предпочел иметь счетчик в качестве поля, поэтому, наверное, сейчас буду придерживаться моей текущей попытки. Однако ваш альтернативный подход определенно интересен и может быть полезен для подобных проблем в будущем :) –

0

Во время вставки, вы должны просто сделать «Выбрать ... + 1 подсчитывать» непосредственно в этой области.

Но я, вероятно, передумал бы добавить это поле в первую очередь. Это похоже на избыточную информацию, которая может быть легко выведена в тот момент, когда вам это нужно. (Например, используя DENSE_RANK http://www.firebirdfaq.org/faq343/)