sql >> Databáze >  >> RDS >> Mysql

Mohu to vyřešit pomocí čistého mysql? (spojení na oddělených hodnotách ve sloupci)

Pokud user_resources (t1) byla „normalizovaná tabulka“ s jedním řádkem pro každý user => resource kombinace, pak by dotaz k získání odpovědi byl stejně jednoduchý jako pouhé joining stoly dohromady.

Bohužel, je to denormalized tím, že budete mít resources sloupec jako:'seznam ID zdroje' oddělený ';' postava.

Pokud bychom mohli převést sloupec 'zdroje' na řádky, mnoho potíží zmizí, protože se spojování tabulky zjednoduší.

Požadavek na vygenerování výstupu:

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

Výstup:

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

Jak:

'Trik' je mít tabulku, která obsahuje čísla od 1 do nějakého limitu. Říkám tomu integerseries . Může být použit pro převod 'horizontálních' věcí, jako je:';' delimited strings do rows .

Funguje to tak, že když se „připojíte“ k integerseries , provádíte cross join , což se děje „přirozeně“ u „vnitřních spojení“.

Každý řádek se duplikuje s jiným „sekvenčním číslem“ z integerseries tabulku, kterou používáme jako „index“ „zdroje“ v seznamu, který chceme použít pro daný row .

Cílem je:

  • spočítejte počet položek v seznamu.
  • extrahujte každou položku na základě její pozice v seznamu.
  • Používejte integerseries převést jeden řádek na sadu řádků extrahováním individuálního 'resource id' z user .resources jak jdeme spolu.

Rozhodl jsem se použít dvě funkce:

  • funkce, která zadá 'seznam oddělených řetězců' a 'index' vrátí hodnotu na pozici v seznamu. Říkám tomu:VALUE_IN_SET . tj. pokud je dáno „A;B;C“ a „index“ 2, vrátí „B“.

  • funkce, která zadá 'seznam s oddělenými řetězci' vrátí počet položek v seznamu. Říkám tomu:COUNT_IN_SET . tj. zadané 'A;B;C' vrátí 3

Ukázalo se, že tyto dvě funkce a integerseries by měl poskytnout obecné řešení delimited items list in a column .

Funguje to?

Dotaz na vytvoření 'normalizované' tabulky z ';' delimited string in column . Zobrazuje všechny sloupce, včetně vygenerovaných hodnot díky 'cross_join' (isequence.id jako 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

Výstup 'normalizované' tabulky:

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                

Pomocí výše uvedených „normalizovaných“ user_resources tabulka, je to jednoduché spojení, které poskytuje požadovaný výstup:

Potřebné funkce (toto jsou obecné funkce, které lze použít kdekoli )

poznámka:Názvy těchto funkcí souvisí s mysql funkce FIND_IN_SET . tj. dělají podobné věci, pokud jde o seznamy řetězců?

COUNT_IN_SET funkce:vrací počet character delimited items ve sloupci.

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 funkce:zachází s delimited list jako one based array a vrátí hodnotu na daném 'indexu'.

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 ;

Související informace:

Tabulky (s daty):

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

Zdroj:

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. DROP INDEX MySQL

  2. Jak úplně odebrat MySQL 5.7 z Windows

  3. Pokračování transakce po chybě porušení primárního klíče

  4. Přístup odepřen uživateli „[email protected]“ (s použitím hesla:NE)