Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Posso resolver isso com mysql puro? (juntando em '' valores separados em uma coluna)


Se os user_resources (t1) era uma 'tabela normalizada' com uma linha para cada user => resource combinação, a consulta para obter a resposta seria tão simples quanto apenas joining as mesas juntas.

Infelizmente, é denormalized tendo os resources coluna como:'lista de id de recurso' separada por ';' personagem.

Se pudéssemos converter a coluna 'resources' em linhas, muitas das dificuldades desapareceriam à medida que as junções da tabela se tornassem simples.

A consulta para gerar a saída solicitada:
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

A saída:
user        data    
----------  --------
sampleuser  abcde   
sampleuser  azerty  
sampleuser  qwerty  
stacky      qwerty  
testuser    abcde   
testuser    azerty  

Como:

O 'truque' é ter uma tabela que contenha os números de 1 até algum limite. Eu chamo de integerseries . Ele pode ser usado para converter coisas 'horizontais' como:';' delimited strings em rows .

A maneira como isso funciona é que quando você 'junta' com integerseries , você está fazendo uma cross join , que é o que acontece 'naturalmente' com 'junções internas'.

Cada linha é duplicada com um 'número de sequência' diferente da integerseries tabela que usamos como um 'índice' do 'recurso' na lista que queremos usar para essa row .

A ideia é:
  • contar o número de itens na lista.
  • extraia cada item com base em sua posição na lista.
  • Usar integerseries para converter uma linha em um conjunto de linhas extraindo o 'id de recurso' individual de user .resources à medida que avançamos.

Resolvi usar duas funções:

  • função que fornece uma 'lista de strings delimitada' e um 'índice' retornará o valor na posição na lista. Eu chamo de:VALUE_IN_SET . ou seja, dado 'A;B;C' e um 'índice' de 2, ele retorna 'B'.

  • função que dada uma 'lista de strings delimitada' retornará a contagem do número de itens na lista. Eu chamo de:COUNT_IN_SET . ou seja, dado 'A;B;C' retornará 3

Acontece que essas duas funções e integerseries deve fornecer uma solução geral para delimited items list in a column .

Funciona?

A consulta para criar uma tabela 'normalizada' de um ';' delimited string in column . Mostra todas as colunas, incluindo os valores gerados devido ao 'cross_join' (isequence.id como 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

A saída da tabela 'normalizada':
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                

Usando os user_resources 'normalizados' acima table, é uma junção simples para fornecer a saída necessária:

As funções necessárias (estas são funções gerais que podem ser usadas em qualquer lugar )

nota:Os nomes dessas funções estão relacionados ao mysql função FIND_IN_SET . ou seja, eles fazem coisas semelhantes em relação às listas de strings?

O COUNT_IN_SET função:retorna a contagem de character delimited items na coluna.
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 ;

O VALUE_IN_SET função:trata a delimited list como um one based array e retorna o valor no 'índice' fornecido.
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 ;

Informações relacionadas:

As tabelas (com dados):
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);

Recurso:
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');

Recurso_usuário:
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');