Table relationships in MySQL

12

I have a big question about relationships in mysql. You see, I'm creating a site that looks like an e-commerce, it's for a colleague.

I'm creating a table called categories and another one called subcategories . In the table subcategories I have a column called categoria_id , which will contain the ID of the selected category. Then like this, I will be able to show the subcategories with their respective categories.

An example: Accessories for cell phones (category), Chargers (subcategory) and so on.

Is it necessary to create a relationship between the ID of the category, and the category_id of the table "subcategories"? I hope I have explained correctly.

Greetings.

    
asked by Blasito 19.07.2017 в 20:49
source

4 answers

13

If necessary you want to say mandatory , the answer is no , that is, you can create two tables and that even if they share information, have no relation to each other.

However, it is extremely advisable that SI establish a relationship between them and take care of Referential Integrity of your data, since otherwise it would be technically possible to insert data from subcategories that did not have an existing category .

I pass the MySQL documentation on the Foreign Keys .

EDITED : I give you an example that will prevent you from having a subcategory without a parent category .

CREATE TABLE categoria (
    id INT NOT NULL,
    nom_categoria VARCHAR(15) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE sub_categoria(
    id INT,
    nom_subcategoria VARCHAR(15) NOT NULL,
    categoria_id INT,
    INDEX par_ind (categoria_id),
    FOREIGN KEY (categoria_id)
        REFERENCES categoria(id)
        ON DELETE CASCADE
) ENGINE=INNODB;
    
answered by 19.07.2017 / 21:02
source
7
  

NEW ANSWER: A MUCH SIMPLE SOLUTION

The truth is that I was surprised by the response of @PatricioMoracho which helped me reconsider a much simpler solution to the problem of understanding and maintaining . His answer opened my eyes to a major limitation of the previous model: the possibility of nested categories and sub-categories!

Curiously, considering such complexity can lead us to solve the problem in a very simple way. It often happens that in life, what is complicated helps us to see that there is a simpler path .

Let's see:

Indeed, you can have a single table categorias to which we are going to add a new column called padre_id . In that column we will put the id of the father of each category. If it is a main category, we will set that value to zero ( 0 ). That will help us to recover the main categories only, if we wish ...

The code presents a simple query that will show each category and next to your children.

Obviously, more complex queries could be made, such as one that shows each category and its sub-categories by levels, or code that reaches the category grandpa through its parent category.

Código: Ver Demo

CREATE TABLE  IF NOT EXISTS categorias 
    (
      categoria_id          INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
      categoria_nombre      VARCHAR(50) NOT NULL,
      padre_id              INT NOT NULL

    );


INSERT INTO categorias (categoria_nombre, padre_id) 
            VALUES  
            ('Teléfonos',0),
            ('Celulares',1),
            ('Accesorios para celulares',2),
            ('Baterías',3),
            ('Protectores',3),
            ('Manos libres',3),
            ('Computadoras',0),
            ('Computadoras de Escritorio',7),
            ('Computadoras Portátiles',7),
            ('Teclado',8),
            ('Monitor',8),
            ('Mouse',8),                
            ('Funda de portátil',9);                

SELECT c.categoria_nombre as padre, 
       GROUP_CONCAT(hijos.categoria_nombre SEPARATOR ",") AS hijos     
       FROM categorias c
       INNER JOIN categorias hijos 
          ON c.categoria_id=hijos.padre_id    
       GROUP BY hijos.padre_id;

Resultado:

    padre                        hijos
1   Teléfonos                    Celulares
2   Celulares                    Accesorios para celulares
3   Accesorios para celulares    Baterías,Protectores,Manos libres
4   Computadoras                 Computadoras de Escritorio,Computadoras Portátiles
5   Computadoras de Escritorio   Teclado,Monitor,Mouse
6   Computadoras Portátiles      Funda de portátil
  

@deprecated   This is an old answer

What is usually done in these cases is something like this.

Note: This is a basic example . It would be necessary to add in the table creation the foreign key, referential integrity, indexes, according to the type of database (InnoDB, MyISAM).

Given that you are starting, and some have questioned that in my answer I have not put anything on referential integrity , I want to tell you that before moving on it is important to decide the type of engine that will have your tables.

In MySQL there are two: MyISAM e InnoDB . Both have their advantages and their disadvantages. For example, this article (and others) can orient you: MyISAM or InnoDB ? Choose your MySQL storage engine

You can also check the official MySQL documentation (in English):

The code presents a basic example and a query that would work if you want to get the sub_categories of a category x.

If you decide for InnoDB I recommend that you read the documentation of that engine, and how the table creation syntax works, how the primary keys are indicated, the references, the on delete , the on update .. . etc. It is a much broader issue than establishing a simple relationship. As you set the on delete for example, the child data will be deleted when the parent is deleted, so as not to leave orphan data. You need to find out how it works before implementing it ...

Código: Ver Demo

/*
-- ATENCIÓN NO USE DROP TABLE CON SUS TABLAS REALES
-- YA QUE DROP TABLE BORRARÁ SUS DATOS
-- DROP TABLE SE USA AQUÍ SÓLO PARA PODER PROBAR LOS DATOS */ 

DROP TABLE IF EXISTS categorias;
DROP TABLE IF EXISTS sub_categorias;



/* NO COPIE LA SENTENCIA ^ DROP TABLE ^ EN SUS DATOS REALES */



CREATE TABLE  IF NOT EXISTS categorias 
    (
      categoria_id          INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
      categoria_nombre      VARCHAR(50) NOT NULL
    );


CREATE TABLE  IF NOT EXISTS sub_categorias 
    (
      subcategoria_id          INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
      subcategoria_nombre      VARCHAR(50) NOT NULL,
      categoria_id             INT (10)  
    );



INSERT INTO categorias (categoria_nombre) 
            VALUES  ('Accesorios para celulares'), ('Accesorios PC');

INSERT INTO sub_categorias (subcategoria_nombre,categoria_id) 
            VALUES  
            ('Baterías', 1),
            ('Protectores', 1),
            ('Manos libres', 1),
            ('Teclado', 2),
            ('Monitor', 2),
            ('Mouse', 2)                
            ;   


SELECT  subcategoria_nombre FROM  sub_categorias sc
INNER JOIN categorias c 
ON sc.categoria_id=c.categoria_id
WHERE sc.categoria_id=1;

Resultado

    subcategoria_nombre
1   Baterías
2   Protectores
3   Manos libres
    
answered by 19.07.2017 в 21:03
4

Juance, your question has already been answered, both A. Cedano's and David Isla's answers are valid. The issue is that I noticed a limitation in the model that you were proposing that is the category / subcategory, and I consulted about that. Basically you can only manage two levels, you could not "hang" a subcategory of another subcategory, and in general only two levels of cataloging is usually little, just see sites like mercadolibre or others. My proposal is to manage a single table categorias without distinction which in a certain way is reasonable since all are categories and what changes is the hierarchy of how we want to see them, which will be handled by an attribute nodo . If at some point we need to change something or directly create a new level for all categories, this model is more flexible. It has a major complication : you have to take care of updating nodo , it's not complex but it has its logic.

I extend the example of A.Cedano a bit so you can see it:

DROP TABLE IF EXISTS categorias;

CREATE TABLE  IF NOT EXISTS categorias 
    (
      categoria_id          INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
      nodo                  VARCHAR(40),
      categoria_nombre      VARCHAR(40) NOT NULL
    );


INSERT INTO categorias (categoria_nombre, nodo) 
    VALUES  ('Categorias de Productos', '0001'), 
            ('Accesorios para celulares', '00010001'), 
            ('Celulares', '00010002'),
            ('Accesorios PC', '00010003'),
            ('Baterías', '000100010001'),
            ('Protectores', '000100010002'),
            ('Plásticos', '0001000100020001'),
            ('Vidrio', '0001000100020001'),
            ('Manos libres', '000100010003'),
            ('Teclado', '000100020001'),
            ('Monitor', '000100020001'),
            ('Mouse', '000100020001'),                
            ('Gama Alta', '000100020001'),
            ('Iphone 6', '0001000200010001'),
            ('Samsung S8', '0001000200010002'),
            ('Gama Media', '000100020002'),
            ('Gama Baja', '000100020002')
            ;   


SELECT  c1.nodo, 
        CONCAT(SPACE(LENGTH(c1.nodo)-LENGTH(c.nodo)),  c1.categoria_nombre) AS 'categoria_nombre'
    FROM categorias c
    INNER JOIN categorias c1
        ON LEFT(c1.nodo, LENGTH(c.nodo)) = c.nodo
    WHERE c.categoria_id = 1
    ORDER BY c1.nodo

The output would be something like this:

nodo             categoria_nombre
================ =====================================
0001             Categorias de Productos
00010001             Accesorios para celulares
000100010001             Baterías
000100010002             Protectores
0001000100020001             Plásticos
0001000100020001             Vidrio
000100010003             Manos libres
00010002             Celulares
000100020001             Gama Alta
0001000200010001             Iphone 6
0001000200010002             Samsung S8
000100020002             Gama Media
000100020002             Gama Baja
00010003             Accesorios PC
000100030001             Teclado
000100030001             Monitor
000100030001             Mouse

The indentation is only to visualize the hierarchy. Each product would have a categoria_id related to the categorias table that would point to the predefined level within the tree. The search for products below a certain category is very easy to implement, for example if you want all the Accessories for cell phones, you just have to indicate the categoria_id = 2 to the previous query. The maintenance of nodo if you will eventually create an ABM for it is not complicated but you have to be careful, a structure like this is easier if the maintenance is done by script.

In the example I am proposing, each branch or node could host up to 9999 categories, and up to 10 levels, but that is easily extensible by modifying the nodo field and the update logic. A somewhat more advanced model would be to manage additional tables jerarquia and categoria_jerarquia to eventually manage different hierarchical models that depend on some criterion, for example "simple categories" or "extended categories" but I do not want to complicate your life, maybe This model will serve you.

Additional information: How do we add more categories?

First of all, I recommend keeping a first base category in the first level, in the example "Categories of Products", any new item must always be under it, this simply because later logic is easier for handle the inserts. Let's see an example: suppose we want to add a new mobile accessory "Chargers", how do we do it?

  • First you have to establish which will be the parent category, in this example it will be "Accessories for cell phones", in the query that I put together, I am not showing the ID but all the categories have an id, that of the Accessories is the 2, doing the following we will obtain the node "father" and the length of it

    SELECT nodo AS NodoPadre,
            LENGTH(NODO) As LongitudNodoPadre
            FROM categorias
            WHERE categoria_id = 2 ;
    
  • The node "father" is 00010001 the new item we are going to insert below this and at the end of the categories of that branch so we need to calculate the final node that would be something like 00010001???? , to calculate justly the part of ???? we simply have to see which is the last node of that branch and add one, like this:

    SELECT  CONCAT('00010001',RIGHT(CAST((10000 + CAST(MAX(right(nodo,4)) AS UNSIGNED ) + 1) AS CHAR(5)),4))
       FROM categorias
        WHERE nodo LIKE '00010001%'
              AND LENGTH(nodo) = 12;
    
  • We are seeing the branch below the parent node, this for the LIKE '00010001%' and also because we verify that the length of the node is 4 + the length of the parent node by LENGTH(nodo) = 12 . The result would be: 000100010004 , if you see the branch:

    nodo            categoria_nombre
    =============== ===================
    000100010001    Baterías
    000100010002    Protectores
    000100010003    Manos libres
    

    It is clear that we should insert the new category as:

    INSERT INTO categorias (categoria_nombre, nodo) 
            VALUES  ('Cargadores', '000100010004'), 
    

With this model, the insertions are always in the last level of the branch, eventually the logic could be assembled to reorder the items sequentially, making an "exchange" of the node between items, in terms of delete , it is convenient that they are logical, using for example a FlagHabilitado (1/0) but eventually they could also be physical.

    
answered by 20.07.2017 в 22:09
0

Somehow with the structure you are considering, you already have a relationship between categories and subcategories , but this is a mere reference not physically in the database.

I consider dangerous to use without a physical relationship because the database will not handle the % In addition, you will not create a index to that field integridad referencial that you declared and in the future you can create performance problems in Query operations and updates if you use that column as a criterion.

In summary it is not necessary, but you assume unnecessary risks that you must cover from the programming.

I recommend reading about the concepto_id and llaves primarias first of all to understand better.

    
answered by 19.07.2017 в 21:14