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
integerseriesprzekonwertować jeden wiersz na zestaw wierszy, wyodrębniając indywidualny „identyfikator zasobu” oduser.resourcesjak 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:
-
W końcu opracowaliśmy, jak uzyskać SQLFiddle - działający kod do kompilacji funkcji.
-
Istnieje wersja tego, która działa dla
SQLiterównież bazy danych SQLite- Normalizacja połączonego pola i łączenie z nim?
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');