Mysql
 sql >> база данни >  >> RDS >> Mysql

Мога ли да разреша това с чист mysql? (присъединяване към '' разделени стойности в колона)

Ако user_resources (t1) беше „нормализирана таблица“ с един ред за всеки user => resource комбинация, тогава заявката за получаване на отговора би била толкова проста, колкото просто joining таблиците заедно.

Уви, той е denormalized като разполагате с resources колона като:'списък с идентификатор на ресурс', разделен с ';' знак.

Ако можем да преобразуваме колоната „ресурси“ в редове, тогава много от трудностите изчезват, когато присъединяването на таблицата стане лесно.

Заявката за генериране на изхода поиска:

SELECT user_resource.user, 
       resource.data

FROM user_resource 
     JOIN integerseries AS isequence 
       ON isequence.id <= COUNT_IN_SET(user_resource.resources, ';') /* normalize */

     JOIN resource 
       ON resource.id = VALUE_IN_SET(user_resource.resources, ';', isequence.id)      
ORDER BY
       user_resource.user,  resource.data

Изходът:

user        data    
----------  --------
sampleuser  abcde   
sampleuser  azerty  
sampleuser  qwerty  
stacky      qwerty  
testuser    abcde   
testuser    azerty  

Как:

„Тръкът“ е да имате таблица, която съдържа числата от 1 до някакъв лимит. Наричам го integerseries . Може да се използва за преобразуване на "хоризонтални" неща като:';' delimited strings в rows .

Начинът, по който това работи, е, че когато се "присъедините" с integerseries , правите cross join , което се случва „естествено“ с „вътрешни съединения“.

Всеки ред се дублира с различен "пореден номер" от integerseries таблица, която използваме като "индекс" на "ресурса" в списъка, който искаме да използваме за този row .

Идеята е да:

  • пребройте броя на елементите в списъка.
  • извличане на всеки елемент въз основа на неговата позиция в списъка.
  • Използвайте integerseries за да конвертирате един ред в набор от редове, извличайки отделния „идентификатор на ресурса“ от user .resources докато вървим напред.

Реших да използвам две функции:

  • функция, която дава „списък с разделени низове“ и „индекс“ ще върне стойността на позицията в списъка. Наричам го:VALUE_IN_SET . т.е. ако има 'A;B;C' и 'индекс' от 2, тогава той връща 'B'.

  • функция, която дава 'списък с разделени низове' ще върне броя на броя на елементите в списъка. Наричам го:COUNT_IN_SET . т.е. дадено 'A;B;C' ще върне 3

Оказва се, че тези две функции и integerseries трябва да предостави общо решение за delimited items list in a column .

Работи ли?

Заявката за създаване на "нормализирана" таблица от ';' delimited string in column . Той показва всички колони, включително генерираните стойности поради 'cross_join' (isequence.id като resources_index ):

SELECT user_resource.user, 
       user_resource.resources,
       COUNT_IN_SET(user_resource.resources, ';')                AS resources_count, 
       isequence.id                                              AS resources_index,
       VALUE_IN_SET(user_resource.resources, ';', isequence.id)  AS resources_value
FROM 
     user_resource 
     JOIN  integerseries AS isequence 
       ON  isequence.id <= COUNT_IN_SET(user_resource.resources, ';')
ORDER BY
       user_resource.user, isequence.id

Изходът на „нормализираната“ таблица:

user        resources  resources_count  resources_index  resources_value  
----------  ---------  ---------------  ---------------  -----------------
sampleuser  1;2;3                    3                1  1                
sampleuser  1;2;3                    3                2  2                
sampleuser  1;2;3                    3                3  3                
stacky      2                        1                1  2                
testuser    1;3                      2                1  1                
testuser    1;3                      2                2  3                

Използване на горните "нормализирани" user_resources таблица, това е просто присъединяване за осигуряване на необходимия изход:

Необходимите функции (това са общи функции, които могат да се използват навсякъде )

забележка:Имената на тези функции са свързани с mysql функция FIND_IN_SET . т.е. правят ли подобни неща по отношение на списъците с низове?

COUNT_IN_SET функция:връща броя на character delimited items в колоната.

DELIMITER $$

DROP FUNCTION IF EXISTS `COUNT_IN_SET`$$

CREATE FUNCTION `COUNT_IN_SET`(haystack VARCHAR(1024), 
                               delim CHAR(1)
                               ) RETURNS INTEGER
BEGIN
      RETURN CHAR_LENGTH(haystack) - CHAR_LENGTH( REPLACE(haystack, delim, '')) + 1;
END$$

DELIMITER ;

VALUE_IN_SET функция:третира delimited list като one based array и връща стойността в дадения 'индекс'.

DELIMITER $$

DROP FUNCTION IF EXISTS `VALUE_IN_SET`$$

CREATE FUNCTION `VALUE_IN_SET`(haystack VARCHAR(1024), 
                               delim CHAR(1), 
                               which INTEGER
                               ) RETURNS VARCHAR(255) CHARSET utf8 COLLATE utf8_unicode_ci
BEGIN
      RETURN  SUBSTRING_INDEX(SUBSTRING_INDEX(haystack, delim, which),
                     delim,
                     -1);
END$$

DELIMITER ;

Свързана информация:

Таблиците (с данни):

CREATE TABLE `integerseries` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `integerseries` */

insert  into `integerseries`(`id`) values (1);
insert  into `integerseries`(`id`) values (2);
insert  into `integerseries`(`id`) values (3);
insert  into `integerseries`(`id`) values (4);
insert  into `integerseries`(`id`) values (5);
insert  into `integerseries`(`id`) values (6);
insert  into `integerseries`(`id`) values (7);
insert  into `integerseries`(`id`) values (8);
insert  into `integerseries`(`id`) values (9);
insert  into `integerseries`(`id`) values (10);

Ресурс:

CREATE TABLE `resource` (
  `id` int(11) NOT NULL,
  `data` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `resource` */

insert  into `resource`(`id`,`data`) values (1,'abcde');
insert  into `resource`(`id`,`data`) values (2,'qwerty');
insert  into `resource`(`id`,`data`) values (3,'azerty');

Потребителски ресурс:

CREATE TABLE `user_resource` (
  `user` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `resources` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`user`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `user_resource` */

insert  into `user_resource`(`user`,`resources`) values ('sampleuser','1;2;3');
insert  into `user_resource`(`user`,`resources`) values ('stacky','3');
insert  into `user_resource`(`user`,`resources`) values ('testuser','1;3');


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Сравнете датите в MySQL

  2. Пълно възстановяване на MySQL или MariaDB Galera клъстер от архивиране

  3. 1064 грешка в CREATE TABLE ... TYPE=MYISAM

  4. Как да открием дали дадена стойност съдържа поне една цифрова цифра в MySQL

  5. MySQL - ГРЕШКА 1045 - Достъпът е отказан