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

Kan jeg løse dette med ren mysql? (sammenføjning på '' adskilte værdier i en kolonne)

Hvis user_resources (t1) var en 'normaliseret tabel' med én række for hver user => resource kombination, så ville forespørgslen for at få svaret være så simpel som bare at joining bordene sammen.

Ak, den er denormalized ved at have resources kolonne som en:'liste over ressource-id' adskilt af et ';' tegn.

Hvis vi kunne konvertere 'ressourcer'-kolonnen til rækker, så forsvinder mange af vanskelighederne, efterhånden som tabellen bliver enkel.

Forespørgslen til at generere det output, der blev bedt om:

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

Udgangen:

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

Hvordan:

'Tricket' er at have en tabel, der indeholder tallene fra 1 til en eller anden grænse. Jeg kalder det integerseries . Det kan bruges til at konvertere 'vandrette' ting såsom:';' delimited strings i rows .

Måden dette fungerer på er, når du 'join' med integerseries , laver du en cross join , hvilket er det, der sker 'naturligt' med 'indre sammenføjninger'.

Hver række bliver duplikeret med et andet 'sekvensnummer' fra integerseries tabel, som vi bruger som et 'indeks' af 'ressourcen' på listen, som vi ønsker at bruge til den row .

Ideen er at:

  • tæl antallet af elementer på listen.
  • udtræk hvert element baseret på dets placering på listen.
  • Brug integerseries at konvertere en række til et sæt rækker, der udtrækker det individuelle 'ressource-id' fra user .resources mens vi går.

Jeg besluttede at bruge to funktioner:

  • funktion, der givet en 'separeret strengliste' og et 'indeks' vil returnere værdien på positionen i listen. Jeg kalder det:VALUE_IN_SET . dvs. givet 'A;B;C' og et 'indeks' på 2, så returnerer det 'B'.

  • funktion, der givet en 'separeret strengliste' vil returnere antallet af elementer på listen. Jeg kalder det:COUNT_IN_SET . dvs. givet 'A;B;C' vil returnere 3

Det viser sig, at disse to funktioner og integerseries skal give en generel løsning på delimited items list in a column .

Virker det?

Forespørgslen om at oprette en 'normaliseret' tabel fra en ';' delimited string in column . Den viser alle kolonnerne, inklusive de genererede værdier på grund af 'cross_join' (isequence.id som 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

Det 'normaliserede' tabeloutput:

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                

Ved at bruge ovenstående 'normaliserede' user_resources tabel, er det en simpel join for at give det nødvendige output:

De nødvendige funktioner (disse er generelle funktioner, der kan bruges overalt )

bemærk:Navnene på disse funktioner er relateret til mysql FIND_IN_SET-funktion . dvs. de gør lignende ting med hensyn til strengelister?

COUNT_IN_SET funktion:returnerer antallet af character delimited items i kolonnen.

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 funktion:behandler den delimited list som en one based array og returnerer værdien ved det givne 'indeks'.

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 ;

Relaterede oplysninger:

Tabellerne (med data):

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

User_resource:

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. Konverter månedsnummer til månedsnavnfunktion i SQL

  2. Automatisering af sikkerhedsrevisioner til PostgreSQL

  3. INT vs Unique-Identifier for ID-felt i databasen

  4. Eksempelskemaer på GitHub