sql >> Database >  >> RDS >> Mysql

Kan ik dit oplossen met pure mysql? (aansluiten op '' gescheiden waarden in een kolom)

Als de user_resources (t1) was een 'genormaliseerde tabel' met één rij voor elke user => resource combinatie, dan is de vraag om het antwoord te krijgen zo simpel als gewoon joining de tafels bij elkaar.

Helaas, het is denormalized door de resources kolom als een:'lijst met resource-ID' gescheiden door een ';' karakter.

Als we de kolom 'resources' in rijen zouden kunnen omzetten, verdwijnen veel van de problemen als het samenvoegen van de tabel eenvoudig wordt.

De vraag om de gevraagde uitvoer te genereren:

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

De uitvoer:

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

Hoe:

De 'truc' is om een ​​tabel te hebben die de getallen van 1 tot een bepaalde limiet bevat. Ik noem het integerseries . Het kan worden gebruikt om 'horizontale' dingen te converteren, zoals:';' delimited strings in rows .

De manier waarop dit werkt, is dat wanneer je 'deelneemt' met integerseries , je doet een cross join , wat 'natuurlijk' gebeurt met 'inner joins'.

Elke rij wordt gedupliceerd met een ander 'volgnummer' uit de integerseries tabel die we gebruiken als een 'index' van de 'resource' in de lijst die we willen gebruiken voor die row .

Het idee is om:

  • tel het aantal items in de lijst.
  • extraheer elk item op basis van de positie in de lijst.
  • Gebruik integerseries om één rij om te zetten in een reeks rijen door de individuele 'resource-ID' uit user te extraheren .resources terwijl we verder gaan.

Ik besloot om twee functies te gebruiken:

  • functie die gegeven een 'delimited string list' en een 'index' de waarde op de positie in de lijst zal retourneren. Ik noem het:VALUE_IN_SET . d.w.z. gegeven 'A;B;C' en een 'index' van 2 dan retourneert het 'B'.

  • functie die gegeven een 'lijst met gescheiden tekenreeksen' de telling van het aantal items in de lijst zal retourneren. Ik noem het:COUNT_IN_SET . d.w.z. gegeven 'A;B;C' zal 3 teruggeven

Het blijkt dat die twee functies en integerseries zou een algemene oplossing moeten bieden voor delimited items list in a column .

Werkt het?

De query om een ​​'genormaliseerde' tabel te maken van een ';' delimited string in column . Het toont alle kolommen, inclusief de gegenereerde waarden vanwege de '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

De 'genormaliseerde' tabeluitvoer:

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                

Met behulp van de bovenstaande 'genormaliseerde' user_resources tabel, het is een simpele join om de vereiste output te leveren:

De benodigde functies (dit zijn algemene functies die overal kunnen worden gebruikt )

opmerking:de namen van deze functies zijn gerelateerd aan de mysql FIND_IN_SET functie . d.w.z. ze doen soortgelijke dingen met betrekking tot stringlijsten?

De COUNT_IN_SET functie:retourneert het aantal character delimited items in de kolom.

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 ;

De VALUE_IN_SET functie:behandelt de delimited list als een one based array en retourneert de waarde bij de opgegeven 'index'.

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 ;

Verwante informatie:

De tabellen (met gegevens):

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

Bron:

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. Voorkom automatische verhoging op MySQL dubbele invoeging

  2. Databaseback-upadviseurs gebruiken om onderhoudstaken te automatiseren

  3. Omgaan met onbetrouwbare netwerken bij het maken van een HA-oplossing voor MySQL of MariaDB

  4. Probleem voor invoegen met psycopg