Получение правильной строки при использовании наследования таблиц в PostgreSQL - PullRequest
3 голосов
/ 22 июля 2011

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

Учение

Одна вещь, которая мне понравилась в Doctrine - это наследование, которое я использовал для нескольких ролей в веб-приложении. Существует базовая таблица / класс Person, и каждая роль (например, администратор, разработчик, пользователь) расширяет этот класс. Все пользователи используют одну базовую таблицу, поэтому она помогает сохранить уникальный логин / идентификатор пользователя (в моем случае это электронная почта). Но получение информации о человеке из доктрины привело к финальному занятию со всеми его свойствами. Например:

$user = $em->getRepository('Entities\Person')->findOneBy(array('email' => 'john.doe@example.com'));
if ( $user instanceof Entities\Developer) {
    ... 
}

Хорошая особенность, но при наличии множества ролей результирующий SQL-запрос был очень неэффективным: выбор из оставленного базового класса, объединяющего все роли, а затем по определенному классу дискриминатора построил конечный класс из базовой и финальной таблиц.

PostgreSQL

Я обнаружил, что в postgres реализовано наследование таблиц, и оно работает хорошо. Но я хотел бы смоделировать поведение Doctrine, получив роль от db (не зная его роли и, следовательно, финального стола).

Для лучших примеров мои таблицы выглядят так:

--
-- base people table
--
CREATE TABLE people
(
  id serial NOT NULL,
  first_name character varying(25) NOT NULL,
  last_name character varying(25) NOT NULL,
  email character varying(50) NOT NULL,
  "password" character varying(150),
  CONSTRAINT people_pkey PRIMARY KEY (id)
);


--
-- role developer (does not have any role specific info)
-- 

CREATE TABLE developer
(
-- Inherited from table people:  id integer NOT NULL DEFAULT nextval('people_id_seq'::regclass),
-- Inherited from table people:  first_name character varying(25) NOT NULL,
-- Inherited from table people:  last_name character varying(25) NOT NULL,
-- Inherited from table people:  email character varying(50) NOT NULL,
-- Inherited from table people:  "password" character varying(150),
  CONSTRAINT developer_pkey PRIMARY KEY (id)
)
INHERITS (people);

--
-- role user
-- 

CREATE TABLE installer
(
-- Inherited from table people:  id integer NOT NULL DEFAULT nextval('people_id_seq'::regclass),
-- Inherited from table people:  first_name character varying(25) NOT NULL,
-- Inherited from table people:  last_name character varying(25) NOT NULL,
-- Inherited from table people:  email character varying(50) NOT NULL,
  client character varying(50),
-- Inherited from table people:  "password" character varying(150),
  CONSTRAINT installer_pkey PRIMARY KEY (id)
)
INHERITS (people);

Решение 1 -> 2 запроса

Довольно просто выяснить роль людей из базовой таблицы, а затем выбрать непосредственно из таблицы роли:

-- returns name of table (and role) 'developer'
SELECT pg.relname 
FROM people p, pg_class pg
WHERE pg.oid=p.tableoid and p.email = 'john.doe@example.com';

-- getting roles full info
SELECT *
FROM developer
WHERE email = 'kracmar@dannax.sk';

Это решение хорошо, но я искал более подходящее решение.

Решение 2 -> 1 запрос с использованием процедуры

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

Это один из результатов, функция возвращает запись, но это не запрос, один столбец со всеми полями в нем, разделенными запятой.

    CREATE OR REPLACE FUNCTION get_person_by_email(person_email VARCHAR) 
            RETURNS record 
            LANGUAGE plpgsql 
            STABLE STRICT AS
    $BODY$
            DECLARE 
                    role varchar;
                    result record;

            BEGIN

                    SELECT pg.relname 
                    INTO role
                    FROM people p,
                            pg_class pg
                    WHERE pg.oid=p.tableoid
                            AND p.email = person_email;

                    IF NOT FOUND THEN
                            RAISE exception 'Person with email % does not exists.', person_email;
                    END IF;

                    CASE 
                            WHEN role = 'developer' THEN
                                    SELECT * 
                                    INTO result 
                                    FROM developer
                                    WHERE email = person_email;
                            WHEN ROLE = 'installer' THEN
                                    SELECT * 
                                    INTO result
                                    FROM installer
                                    WHERE email = person_email;

                    END CASE;

                    RETURN result;
            END;
    $BODY$;

Выбор из этой функции невозможен, так как отсутствует определение столбцов. Может быть, я усложняю вещи и должен использовать решение 1, но таким образом я ничему не научусь. Любая помощь будет оценена.

1 Ответ

1 голос
/ 22 июля 2011

Полагаю, вас может заинтересовать вилка Doctrine2 zyxist :

Fork of Doctrine 2 Object Relational Mapper с целью создания поддержки наследования реальных таблиц для PostgreSQL

Подробнее читайте в блоге автора .

...