Нормализующий порядок / последовательность - PullRequest
0 голосов
/ 01 марта 2020

Я не могу найти решение по этому вопросу нигде в Google или иным образом. Возможно, моя терминология неверна.

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

| id | job   | action         | sequence |
| :- | :---- | :------------- | :------- |
| 1  | build | Procure Wood   | 1        |
| 2  | build | Grab Hammer    | 2        |
| 3  | build | Hammer         | 3        |
| 4  | drill | Get Screw      | 1        |
| 5  | drill | Charge Drill   | 2        |
| 6  | drill | Start Drilling | 3        |

Пожалуйста, игнорируйте тот факт, что столбцы job и action не нормализованы, что не является частью вопроса - только часть примера.

Например, что если я хочу теперь вставить новую действие Get Nails для задания build. Это будет go после Grab Hammer, но до Hammer. Если бы он был явно вставлен (тогда ORDER BY job, sequence), вы бы получили следующее:

| id | job   | action         | sequence     |
| :- | :---- | :------------- | :----------- |
| 1  | build | Procure Wood   | 1            |
| 2  | build | Grab Hammer    | 2            |
| 7  | build | Get Nails      | 3            |
| 3  | build | Hammer         | 3 <- problem |
| 4  | drill | Get Screw      | 1            |
| 5  | drill | Charge Drill   | 2            |
| 6  | drill | Start Drilling | 3            |

Или, если я хочу переупорядочить действия Charge Drill и Get Screw в drill задание, я бы установил последовательность Charge Drill равной последовательности Get Screw. Но тогда два других действия не будут правильно упорядочены. Опции, которые я вижу:

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

  2. вместо sequence столбец behind. В этом столбце указывается id того ряда, который находится перед ним. Но я понятия не имею, как SQL будет сортировать это (то есть в ORDER BY), и на самом деле все равно будет существовать та же проблема, что и выше (что, если две строки указывают на один и тот же id).

Я ищу, если возможно, более стандартный чистый метод SQL.

Ответы [ 2 ]

0 голосов
/ 01 марта 2020

Нормализация - это удаление избыточности и логических ошибок из базы данных. Избыточность часто приводит к логическим ошибкам, хотя в большинстве случаев эти ошибки являются результатом подхода без проектирования или из-за прямого перехода к физическому проектированию.

Вот пример одного возможного логического проектирования.

-- Job named JOB_NAME exists
--
job {JOB_NAME}
 PK {JOB_NAME}


-- Action named ACTION_NAME exists.
--
action {ACTION_NAME}
    PK {ACTION_NAME}


-- Job JOB_NAME involves action ACTION_NAME.
--
job_action {JOB_NAME, ACTION_NAME}
        PK {JOB_NAME, ACTION_NAME}
       FK1 {JOB_NAME}    REFERENCES job    {JOB_NAME}
       FK2 {ACTION_NAME} REFERENCES action {ACTION_NAME}


-- Action ACTION_NAME has associated step number STEP_NO in job JOB_NAME.
--
job_step {JOB_NAME, STEP_NO, ACTION_NAME}
      PK {JOB_NAME, STEP_NO}

FK1 {JOB_NAME, ACTION_NAME} REFERENCES job_action {JOB_NAME, ACTION_NAME}

С этого момента вы можете начать думать о физическом дизайне: размеры индексов столбцов и т. Д. c. И решить добавить числовое значение IDs для использования в PKs. В зависимости от размера БД, СУБД и используемого оборудования, вы можете оставить все как есть. Я сознательно избегая добавления IDs на этом этапе.


Примером является PostgreSQL (если он установлен) - его легко настроить для MySQL. Для MySQL используйте varchar(25) вместо TEXT, и пример должен работать нормально (не проверено).

CREATE TABLE job (
    JOB_NAME TEXT NOT NULL

  , CONSTRAINT pk_job PRIMARY KEY (JOB_NAME)
);


CREATE TABLE action_ (
    ACTION_NAME TEXT NOT NULL

  , CONSTRAINT pk_action PRIMARY KEY (ACTION_NAME)
);


CREATE TABLE job_action (
    JOB_NAME    TEXT NOT NULL
  , ACTION_NAME TEXT NOT NULL

, CONSTRAINT pk_job_action PRIMARY KEY (JOB_NAME, ACTION_NAME)

, CONSTRAINT fk1_job_action
    FOREIGN KEY (JOB_NAME) REFERENCES
            job (JOB_NAME)

, CONSTRAINT fk2_job_action
    FOREIGN KEY (ACTION_NAME) REFERENCES
        action_ (ACTION_NAME)
);


CREATE TABLE job_step (
      JOB_NAME    TEXT   NOT NULL
    , ACTION_NAME TEXT   NOT NULL
    , STEP_NO     BIGINT NOT NULL

, CONSTRAINT pk_job_step PRIMARY KEY (JOB_NAME, STEP_NO)

, CONSTRAINT fk1_job_step
    FOREIGN KEY (JOB_NAME, ACTION_NAME) REFERENCES
     job_action (JOB_NAME, ACTION_NAME)
);

Вставьте некоторые тестовые данные; обратите внимание, что номера шагов не являются последовательными, оставляя пробелы для будущих промежуточных шагов.

INSERT INTO job (JOB_NAME)
VALUES
    ('build')
  , ('drill')
;

INSERT INTO action_ (ACTION_NAME)
VALUES
    ('procure wood')
  , ('grab hammer')
  , ('hammer')
  , ('get screw')
  , ('charge drill')
  , ('start drilling')
;

INSERT INTO job_action (JOB_NAME, ACTION_NAME)
VALUES
    ('build','procure wood')
  , ('build','grab hammer')
  , ('build','hammer')
  , ('drill','get screw')
  , ('drill','charge drill')
  , ('drill','start drilling')
;

INSERT INTO job_step (JOB_NAME, ACTION_NAME, STEP_NO)
VALUES
    ('build','procure wood', 1000000)
  , ('build','grab hammer' , 2000000)
  , ('build','hammer'      , 3000000)

  , ('drill','get screw'     , 1000000)
  , ('drill','charge drill'  , 2000000)
  , ('drill','start drilling', 3000000)
;

Запрос возвращает правильные результаты

SELECT JOB_NAME
     , rank() OVER (PARTITION BY JOB_NAME ORDER BY STEP_NO ASC) AS STEP
     , ACTION_NAME
FROM job_step
ORDER BY JOB_NAME ;
+----------+------+----------------+
| job_name | step | action_name    |
+----------+------+----------------+
| build    | 1    | procure wood   |
| build    | 2    | grab hammer    |
| build    | 3    | hammer         |
| drill    | 1    | get screw      |
| drill    | 2    | charge drill   |
| drill    | 3    | start drilling |
+----------+------+----------------+

И для вставки get nails в задание на сборку.

-- first define the new action
INSERT INTO action_ (ACTION_NAME) VALUES ('get nails');

-- associate it with the build job
INSERT INTO job_action (JOB_NAME, ACTION_NAME)
VALUES ('build','get nails');

-- insert between grab hammer and hammer
INSERT INTO job_step (JOB_NAME, ACTION_NAME, STEP_NO)
VALUES ('build','get nails', 2500000) ;

А теперь запрос возвращает:

+----------+------+----------------+
| job_name | step | action_name    |
+----------+------+----------------+
| build    | 1    | procure wood   |
| build    | 2    | grab hammer    |
| build    | 3    | get nails      |
| build    | 4    | hammer         |
| drill    | 1    | get screw      |
| drill    | 2    | charge drill   |
| drill    | 3    | start drilling |
+----------+------+----------------+
0 голосов
/ 01 марта 2020

Рассмотрим это как двухэтапный процесс, поэтому сначала добавьте (вставьте) новое действие в таблицу с нулевым порядковым номером. Затем «переместите» его в правильную позицию (обновление), поэтому установите его идентификатор на 3 и установите идентификаторы всех связанных действий (больше или равных 3) в последовательность + 1 ...

  DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table  (
      `id` Serial primary key,
      `job` varchar(12),
      `action` VARCHAR(20),
      `sequence` int);

    INSERT INTO my_table 
      (`id`, `job`, `action`, `sequence`)
    VALUES
      ('1', 'build', 'Procure Wood',1),
      ('2', 'build', 'Grab Hammer',2),
      ('3', 'build', 'Hammer',3),
      ('4', 'drill', 'Get Screw',1),
      ('5', 'drill', 'Charge Drill',2),
      ('6', 'drill', 'Start Drilling',3);

SELECT * FROM my_table ORDER BY job,sequence; 
+----+-------+----------------+----------+
| id | job   | action         | sequence |
+----+-------+----------------+----------+
|  1 | build | Procure Wood   |        1 |
|  2 | build | Grab Hammer    |        2 |
|  3 | build | Hammer         |        3 |
|  4 | drill | Get Screw      |        1 |
|  5 | drill | Charge Drill   |        2 |
|  6 | drill | Start Drilling |        3 |
+----+-------+----------------+----------+

      insert into my_table
      (job,action,sequence) values 
      ('build','grab nails',null);

      UPDATE my_table x 
        JOIN 
           ( SELECT a.id
                  , CASE WHEN a.sequence IS NULL THEN 3 
                         WHEN a.sequence >=3 THEN a.sequence +1 ELSE a.sequence 
                     END new_seq     
               FROM my_table a
               JOIN my_table b
                 ON b.job = a.job
              WHERE b.id = LAST_INSERT_ID()
           ) y 
          ON y.id = x.id 
         SET x.sequence = y.new_seq; 

SELECT * FROM my_table ORDER BY job,sequence;
+----+-------+----------------+----------+
| id | job   | action         | sequence |
+----+-------+----------------+----------+
|  1 | build | Procure Wood   |        1 |
|  2 | build | Grab Hammer    |        2 |
|  7 | build | grab nails     |        3 |
|  3 | build | Hammer         |        4 |
|  4 | drill | Get Screw      |        1 |
|  5 | drill | Charge Drill   |        2 |
|  6 | drill | Start Drilling |        3 |
+----+-------+----------------+----------+

https://www.db-fiddle.com/f/mBAYoVB69UNk76GcU1QCrb/1

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...