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' zuser
.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:
-
Nakonec jsme zjistili, jak získat SQLFiddle – funkční kód kompilovat funkce.
-
Existuje verze, která funguje pro
SQLite
také databáze SQLite – Normalizace zřetězeného pole a spojení s ním?
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');