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” oduser
.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:
-
W końcu opracowaliśmy, jak uzyskać SQLFiddle - działający kod do kompilacji funkcji.
-
Istnieje wersja tego, która działa dla
SQLite
ró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');