2

I have the following two tables:

CREATE TABLE IF NOT EXISTS `skills` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `skill_category` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `skill` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `icon_filename` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `display_priority` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `skill_category` (`skill_category`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `skills_categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `skill_category` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `display_priority` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `IDX_95FF1C8D47E90E27` (`skill_category`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

ALTER TABLE `skills`
  ADD CONSTRAINT `skills_ibfk_1` FOREIGN KEY (`skill_category`) REFERENCES `skills_categories` (`skill_category`);

Note the foreign key relation between the two skill_category columns.

table structure

I am trying to create a manytoone/onetomany relation between these tables. A one unique skill_category should have many skills. This structure seems to work fine, except when I try and verify the mapping I get the error:

The referenced column name 'skill_category' has to be a primary key column on the target entity class 'Resume\ResumeBundle\Entity\SkillsCategories'.

There is a requirement by doctrine that the referenced column be a primary key. If I make this key a primary key, my id column is no longer auto incremented, which I want. So mySQL seems to be ok with the fact that the foreign key is a primary key, however Doctrine complains about this case. Someone said they fixed it by just recreating the columns:

http://goo.gl/vvq0tu

i've tried this, and it didn't seem to help for me. So either this is some sort of bug or I have a fundamental misunderstanding of RDBMS (completely possible). Is my problem that I shouldn't be using an 'id' column at all? That if I expect my 'skills_category' column to be unique that I should make THAT the primary key and just remove the 'id' column? That would solve my problems, but is there any problems with making a VARCHAR the primary key? Google's answer seems to be 'not really', but I would appreciate some perspective from someone else.

jpmorris
  • 882
  • 2
  • 10
  • 22

2 Answers2

2

This is exactly the same issue I had when I first started using Doctrine, and yes, it comes from a slight misunderstanding about how Doctrine maps objects to relational databases.

Let's start by stepping out of the "relational" world, and treating your entities like the objects they are. In your case, there are two classes of objects here: You have a Skill, and you have a Category.

Those are the only two objects at play. There is no such thing as a SkillsCategories entity... it doesn't make sense as an object -- it's a relationship. And as such, it doesn't really mean much to give it an ID.

So what do these two entities actually look like?

Skill:

  • An ID
  • A Name
  • An icon filename
  • A display priority
  • The Category entity to which it belongs

Category:

  • An ID
  • A Name
  • A list of Skill entities that belong to it

And when you put these entities in the database, it looks just like you'd expect (except that we don't map the list of Skills belonging to a Category -- that'll be handled by Doctrine later on). I'd recommend you update your schema to reflect this change:

CREATE TABLE IF NOT EXISTS `Skill` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `categoryId` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `iconFilename` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `displayPriority` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `skill_category` (`categoryId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `Category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `displayPriority` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `IDX_95FF1C8D47E90E27` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Now you can define the relationship through Doctrine. Here's what it might look like with YAML:

Path\To\Entity\Skill:
    type: entity
    table: Skill
    fields:
        id:
            id: true
            type: integer
            generator
                strategy: AUTO
        name:
            type: string
            length: 255
        iconFilename:
            type: string
            length: 50
        displayPriority:
            type: integer
    manyToOne:
        category:
            targetEntity: Category
            inversedBy: skills
            joinColumn:
                name: categoryId
                referencedColumnName: id

Path\To\Entity\Category:
    type: entity
    table: Category
    fields:
        id:
            id: true
            type: integer
            generator
                strategy: AUTO
        name:
            type: string
            length: 255
        displayPriority:
            type: integer
    oneToMany:
        skills:
            targetEntity: Skill
            mappedBy: category

And now you have a fully working model!

Some examples...

Say you wanted to get the displayPriority of a Skill's Category:

$skill->getCategory()->getDisplayPriority();

...or if you wanted to get a list of Skill names under a given Category:

foreach ($category->getSkills() as $skill) {
    echo $skill->getName();
}

Hope this helps clear things up a little...

Thomas Kelley
  • 10,187
  • 1
  • 36
  • 43
  • Thanks for your help, I implemented your solution, but then realized you're joining 'skillId' to an existing 'id' primary key . I can do this, but for ease of data insertion (I'm inserting with phpmyadmin and I'd rather not lookup the corresponding id for each category I want to use; I'd rather just use the category directly and not have to mess with ids. If I do this (join the tables through the category and not the ids) then, I need the category to be the primary key (doctrine requirement), if I do that, then the ids wont increment if they are not the primary key (mysql requirement?) – jpmorris Aug 07 '13 at 10:03
  • Whoops, yes... I meant for the `Skill` -> `category` join column name to be `categoryId`, not `skillId`. Sorry about that! – Thomas Kelley Aug 07 '13 at 15:21
  • And as for your manual database updates, can you do this in phpMyAdmin: `INSERT INTO Skill(name, iconFilename, displayPriority, categoryId) SELECT "some skill name", "the icon file", 5, id FROM Category WHERE name = "some category name"`? This uses the results from your `SELECT` as the basis for your `INSERT`. I'm just not sure it's a good idea to weaken your model for the sake of making data entry easier. – Thomas Kelley Aug 07 '13 at 15:32
  • Thanks again for your help. Well I'd rather just CHANGE the model, and use the phpmyadmin ui to insert data. Can't I just get rid of the ids all together. What need is there to have an id on Category table? I am already creating unique category names, cant THOSE be my primary key? Then the number of columns drop off by 2, I can use phpadmin the way I want, and doctrine wont give me the error above – jpmorris Aug 07 '13 at 22:22
  • Sorry, I thought one of your requirements was to have a Category id that auto-incremented? – Thomas Kelley Aug 07 '13 at 22:33
  • Yes ONLY IF I keep the column, which I'm questioning whether I need to. So If i keep that Id column, I want it to be auto_incremented. But if I don't need the 'id' at all then there's no real need to auto_increment a key that doesn't exist, in fact the 'id' never add vital information. So scrapping the column would work. The only problem is it seems to go against the idea of having a surrogate key: http://stackoverflow.com/questions/3747730/relational-database-design-question-surrogate-key-or-natural-key – jpmorris Aug 07 '13 at 22:40
  • So I decided to just use your solution because my alternative violates the 'best practice' of having surrogate keys in all tables recommended by 70% of the people out there. I really don't know why this is suggested, but I trust that the experts have a reason for it, so I'll just deal with looking up the category from the corresponding id. – jpmorris Aug 07 '13 at 23:08
0

There is no problem making a varchar a primary key. If you really have a space or key size requirement you can set up a lookup code table, but it will make your database and code more complicated than it needs to be.

I personally use auto increment keys only for input from processes I don't control: orders, tickets, events, etc. Generally the things are you are tracking from a web process.

Jason M
  • 239
  • 2
  • 10
  • Yeah, I'm thinking if I talked to a real expert in RDBMS they would say 'why are you using auto-increment ids if you are creating unique categories--there's no need for a unique id when the category provides this uniqueness'. But then again, I just learned more about surrogate v..s natural keys, and some advocate always using surrogate keys: http://stackoverflow.com/questions/707657/picking-the-best-primary-key-numbering-system – jpmorris Aug 07 '13 at 10:18