Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Kann ich das mit reinem MySQL lösen? (Zusammenfügen von '' getrennten Werten in einer Spalte)

Wenn die user_resources (t1) war eine 'normalisierte Tabelle' mit einer Zeile für jeden user => resource Kombination, dann wäre die Abfrage, um die Antwort zu erhalten, so einfach wie nur joining die Tische zusammen.

Leider ist es denormalized indem Sie die resources haben Spalte als:'Liste der Ressourcen-ID', getrennt durch ein ';' Zeichen.

Wenn wir die Spalte „Ressourcen“ in Zeilen umwandeln könnten, würden viele der Schwierigkeiten verschwinden, da die Tabellenverknüpfungen einfach werden.

Die Abfrage zum Generieren der angeforderten Ausgabe:

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

Die Ausgabe:

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

Wie:

Der „Trick“ besteht darin, eine Tabelle zu haben, die die Zahlen von 1 bis zu einem bestimmten Limit enthält. Ich nenne es integerseries . Es kann verwendet werden, um 'horizontale' Dinge umzuwandeln, wie zum Beispiel:';' delimited strings in rows .

Das funktioniert so, wenn Sie mit integerseries 'beitreten' , führen Sie einen cross join durch , was 'natürlich' mit 'Inner Joins' passiert.

Jede Zeile wird mit einer anderen 'Sequenznummer' aus der integerseries dupliziert Tabelle, die wir als 'Index' der 'Ressource' in der Liste verwenden, die wir für diese row verwenden möchten .

Die Idee ist:

  • Zählen Sie die Anzahl der Elemente in der Liste.
  • extrahieren Sie jedes Element basierend auf seiner Position in der Liste.
  • Verwenden Sie integerseries um eine Zeile in eine Reihe von Zeilen umzuwandeln, indem die individuelle 'Ressourcen-ID' aus user extrahiert wird .resources wie wir weitermachen.

Ich habe mich für zwei Funktionen entschieden:

  • Funktion, die bei einer 'getrennten Zeichenfolgenliste' und einem 'Index' den Wert an der Position in der Liste zurückgibt. Ich nenne es:VALUE_IN_SET . d.h. bei 'A;B;C' und einem 'Index' von 2 wird 'B' zurückgegeben.

  • Funktion, die bei einer 'begrenzten Zeichenfolgenliste' die Anzahl der Elemente in der Liste zurückgibt. Ich nenne es:COUNT_IN_SET . d.h. bei 'A;B;C' wird 3 zurückgegeben

Es stellt sich heraus, dass diese beiden Funktionen und integerseries sollte eine allgemeine Lösung für die delimited items list in a column bieten .

Funktioniert es?

Die Abfrage zum Erstellen einer 'normalisierten' Tabelle aus einem ';' delimited string in column . Es zeigt alle Spalten, einschließlich der generierten Werte aufgrund des 'cross_join' (isequence.id als 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

Die 'normalisierte' Tabellenausgabe:

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                

Unter Verwendung der obigen 'normalisierten' user_resources Tabelle ist es ein einfacher Join, um die erforderliche Ausgabe bereitzustellen:

Die benötigten Funktionen (dies sind allgemeine Funktionen, die überall verwendet werden können )

Hinweis:Die Namen dieser Funktionen beziehen sich auf die mysql FIND_IN_SET-Funktion . d.h. sie machen ähnliche Dinge in Bezug auf Stringlisten?

Der COUNT_IN_SET Funktion:gibt die Anzahl der character delimited items zurück in der Spalte.

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 ;

Der VALUE_IN_SET Funktion:behandelt die delimited list als one based array und gibt den Wert am angegebenen 'Index' zurück.

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 ;

Zugehörige Informationen:

Die Tabellen (mit Daten):

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

Ressource:

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

Benutzerressource:

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