开发者

Getting right row while using inheritance of tables in PostgreSQL

I'm recently trying to drop Doctrine due to performance and problems with abstraction and move to database driven logic. I'm using mostly PostgreSQL.

Doctrine

One thing that I liked about Doctrine was inheritance, which I used for multiple roles in web app. There is base table/class Person and every role (for example admin, developer, user) extends this class. All users share one base table, so it helps to keep unique login/idenfiticator (in my case it's email). But getting person's info from doctrine resulted in final class, with all it's properties. For example:

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

Nice feature, but when having many roles, resulting SQL query was very ineffective, selecting from base class left joining all roles and then by defined discriminator mapper builded final class from base table and final table.

PostgreSQL

I discovered that postgres has implemented table inheritance and it works well. But I'd like to simulate Doctrine's behaviour, getting role from db (without knowing it's role and therefore it's final table).

For better examples, my tables look like this:

--
-- 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);

Solution 1 -> 2 queries

It's quite simple to find out role from base table people and then select directly from role's table:

-- 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';

This solution is fine, but I was searching for nicer solution.

Solution 2 -> 1 query using procedure

It would be nice to get info about user in just single query. I went deep into docs of functions and dig something out, but couldn't reach finish. I thought that using return query would be way, but my problem is that I need to specify type of result to function, but it can change depending on user's role (different table with number of columns and types).

This is one of results, function returns record but it's not query, single column with all fields in it separated with comma.

    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开发者_C百科.', 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$;

Selecting from this function is no go as there is missing columns definition. Maybe I complicate things and should use solution 1, but that way I won't learn anything. Any help will be appreciated.


I suppose you might be interested in zyxist's Doctrine2 fork:

Fork of Doctrine 2 Object Relational Mapper aiming to create real table inheritance support for PostgreSQL

You can read more on author's blog.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜