Mysql
 sql >> Database >  >> RDS >> Mysql

Posso risolverlo con mysql puro? (unire su ''valori separati in una colonna)

Se il user_resources (t1) era una "tabella normalizzata" con una riga per ogni user => resource combinazione quindi la query per ottenere la risposta sarebbe semplice come joining i tavoli insieme.

Purtroppo, è denormalized avendo le resources colonna come:'elenco di ID risorsa' separato da ';' carattere.

Se potessimo convertire la colonna "risorse" in righe, molte delle difficoltà scompaiono man mano che i join delle tabelle diventano semplici.

La query per generare l'output richiesto:

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

Il risultato:

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

Come:

Il "trucco" è avere una tabella che contenga i numeri da 1 a un certo limite. Lo chiamo integerseries . Può essere usato per convertire cose 'orizzontali' come:';' delimited strings in rows .

Il modo in cui funziona è quando ti 'unisci' a integerseries , stai facendo un cross join , che è ciò che accade 'naturalmente' con 'inner join'.

Ogni riga viene duplicata con un 'numero di sequenza' diverso da integerseries tabella che usiamo come 'indice' della 'risorsa' nell'elenco che vogliamo usare per quella row .

L'idea è di:

  • conta il numero di elementi nell'elenco.
  • estrai ogni elemento in base alla sua posizione nell'elenco.
  • Usa integerseries per convertire una riga in un insieme di righe estraendo il singolo 'id risorsa' da user .resources mentre andiamo avanti.

Ho deciso di utilizzare due funzioni:

  • funzione che dato un 'elenco di stringhe delimitate' e un 'indice' restituirà il valore nella posizione nell'elenco. Lo chiamo:VALUE_IN_SET . cioè dato 'A;B;C' e un 'indice' di 2 allora restituisce 'B'.

  • funzione che dato un 'elenco di stringhe delimitate' restituirà il conteggio del numero di elementi nell'elenco. Lo chiamo:COUNT_IN_SET . cioè dato 'A;B;C' restituirà 3

Si scopre che queste due funzioni e integerseries dovrebbe fornire una soluzione generale per delimited items list in a column .

Funziona?

La query per creare una tabella 'normalizzata' da un ';' delimited string in column . Mostra tutte le colonne, inclusi i valori generati da 'cross_join' (isequence.id come 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

L'output della tabella 'normalizzato':

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                

Utilizzando le user_resources sopra "normalizzate". table, è un semplice join per fornire l'output richiesto:

Le funzioni necessarie (queste sono funzioni generali che possono essere utilizzate ovunque )

nota:i nomi di queste funzioni sono correlati a mysql funzione TROVA_IN_SET . cioè fanno cose simili per quanto riguarda gli elenchi di stringhe?

Il COUNT_IN_SET funzione:restituisce il conteggio di character delimited items nella colonna.

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 ;

Il VALUE_IN_SET funzione:tratta la delimited list come un one based array e restituisce il valore al dato 'indice'.

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 ;

Informazioni correlate:

Le tabelle (con dati):

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);

Risorsa:

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

Risorsa_utente:

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