sql >> Databasteknik >  >> RDS >> Mysql

Kan jag lösa detta med ren mysql? (sammanfogar på '' separerade värden i en kolumn)

Om user_resources (t1) var en 'normaliserad tabell' med en rad för varje user => resource kombination så skulle frågan för att få svaret vara så enkel som att bara joining borden tillsammans.

Tyvärr är den denormalized genom att ha resources kolumn som en:'lista över resurs-id' separerad av ett ';' tecken.

Om vi ​​kunde konvertera kolumnen "resurser" till rader försvinner många av svårigheterna eftersom tabellen blir enklare.

Frågan för att generera utdata som efterfrågas:

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

Utgången:

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

Hur:

"Knepet" är att ha en tabell som innehåller siffrorna från 1 till någon gräns. Jag kallar det integerseries . Den kan användas för att konvertera "horisontella" saker som:';' delimited strings i rows .

Sättet detta fungerar är att när du "ansluter" med integerseries , gör du en cross join , vilket är vad som händer "naturligt" med "inre sammanfogningar".

Varje rad dupliceras med ett annat "sekvensnummer" från integerseries tabell som vi använder som ett 'index' för 'resursen' i listan som vi vill använda för den row .

Tanken är att:

  • räkna antalet objekt i listan.
  • extrahera varje objekt baserat på dess position i listan.
  • Använd integerseries att konvertera en rad till en uppsättning rader som extraherar det individuella "resurs-id" från user .resources allt eftersom.

Jag bestämde mig för att använda två funktioner:

  • funktion som givet en 'avgränsad stränglista' och ett 'index' kommer att returnera värdet på positionen i listan. Jag kallar det:VALUE_IN_SET . d.v.s. ges 'A;B;C' och ett 'index' på 2 så returnerar det 'B'.

  • funktion som ger en 'avgränsad stränglista' returnerar antalet objekt i listan. Jag kallar det:COUNT_IN_SET . d.v.s. givet 'A;B;C' kommer att returnera 3

Det visar sig att dessa två funktioner och integerseries bör tillhandahålla en allmän lösning på delimited items list in a column .

Fungerar det?

Frågan för att skapa en 'normaliserad' tabell från en ';' delimited string in column . Den visar alla kolumner, inklusive de genererade värdena på grund av "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

Den "normaliserade" tabellutgången:

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                

Använder ovanstående "normaliserade" user_resources tabell, är det en enkel koppling för att tillhandahålla den utdata som krävs:

De funktioner som behövs (detta är allmänna funktioner som kan användas var som helst )

notera:Namnen på dessa funktioner är relaterade till mysql FIND_IN_SET-funktionen . dvs de gör liknande saker när det gäller stränglistor?

COUNT_IN_SET funktion:returnerar antalet character delimited items i kolumnen.

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:behandlar den delimited list som en one based array och returnerar värdet vid det givna 'indexet'.

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 ;

Relaterad information:

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

Resurs:

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. Bästa praxis flerspråkig webbplats

  2. Hur COUNT() fungerar i SQL Server

  3. Hur fixar man FEL:kolumnen c.relhasoids finns inte i Postgres?

  4. Använda Docker på Azure Container Service med Swarm Cluster