Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Czy mogę rozwiązać ten problem za pomocą czystego mysql? (łączenie na „” rozdzielonych wartościach w kolumnie)

Jeśli user_resources (t1) był „znormalizowaną tabelą” z jednym wierszem na każdy zasób user => resource kombinację, wtedy zapytanie w celu uzyskania odpowiedzi byłoby tak proste, jak po prostu joining stoły razem.

Niestety, jest denormalized mając resources kolumna jako:'lista identyfikatorów zasobu' oddzielona znakiem ';' znak.

Gdybyśmy mogli przekonwertować kolumnę „zasoby” na wiersze, wiele trudności zniknęłoby, gdy łączenie tabel stałoby się proste.

Zapytanie do wygenerowania danych wyjściowych wymagane:

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

Wynik:

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

Jak:

„Sztuczka” polega na tym, aby mieć tabelę zawierającą liczby od 1 do pewnego limitu. Nazywam to integerseries . Może być używany do konwersji „poziomych” rzeczy, takich jak:';' delimited strings w rows .

Działa to tak, że kiedy 'dołączasz się' za pomocą integerseries , robisz cross join , co dzieje się „naturalnie” w przypadku „połączeń wewnętrznych”.

Każdy wiersz zostaje zduplikowany z innym „numerem sekwencyjnym” z integerseries tabela, której używamy jako „indeks” „zasobu” na liście, którego chcemy użyć dla tego row .

Chodzi o to, aby:

  • policz liczbę pozycji na liście.
  • wyodrębnij każdy element na podstawie jego pozycji na liście.
  • Użyj integerseries przekonwertować jeden wiersz na zestaw wierszy, wyodrębniając indywidualny „identyfikator zasobu” od user .resources jak idziemy dalej.

Postanowiłem skorzystać z dwóch funkcji:

  • funkcja, która podała „listę rozdzielanych ciągów” i „indeks” zwróci wartość na pozycji na liście. Nazywam to:VALUE_IN_SET . tzn. biorąc pod uwagę 'A;B;C' i 'indeks' 2, to zwraca 'B'.

  • funkcja, która podała „listę rozdzielanych ciągów” zwróci liczbę elementów na liście. Nazywam to:COUNT_IN_SET . np. podane 'A;B;C' zwróci 3

Okazuje się, że te dwie funkcje i integerseries powinien zapewnić ogólne rozwiązanie delimited items list in a column .

Czy to działa?

Zapytanie do utworzenia 'znormalizowanej' tabeli z ';' delimited string in column . Pokazuje wszystkie kolumny, w tym wartości wygenerowane przez „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

Znormalizowane dane wyjściowe tabeli:

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                

Korzystanie z powyższych „znormalizowanych” user_resources tabeli, jest to proste złączenie w celu dostarczenia wymaganych danych wyjściowych:

Potrzebne funkcje (są to ogólne funkcje, których można używać wszędzie )

uwaga:Nazwy tych funkcji są powiązane z mysql funkcja ZNAJDŹ_IN_SET . tj. robią podobne rzeczy w odniesieniu do list ciągów?

COUNT_IN_SET funkcja:zwraca liczbę character delimited items w kolumnie.

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 funkcja:traktuje delimited list jako one based array i zwraca wartość w podanym 'indeksie'.

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 ;

Informacje pokrewne:

Tabele (z danymi):

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

Zasób:

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

Zasób_użytkownika:

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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wybierz datę MySQL równą dzisiejszej

  2. Cytat pojedynczy, cytat podwójny i znaki wsteczne w zapytaniach MySQL

  3. Pobierz bieżący rok, bieżący miesiąc i bieżący dzień w MySQL

  4. Połącz się z mysql na Amazon EC2 ze zdalnego serwera

  5. BULK INSERT w MYSQL