user_resources
の場合 (t1)は、user => resource
ごとに1行の「正規化されたテーブル」でした 組み合わせて、答えを取得するためのクエリは、joining
と同じくらい簡単です。 テーブルを一緒に。
残念ながら、denormalized
resources
を持つことによって 列として:'リソースIDのリスト'';'で区切られますキャラクター。
'resources'列を行に変換できれば、テーブルの結合が単純になるため、多くの問題が解消されます。
要求された出力を生成するためのクエリ:
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
出力:
user data
---------- --------
sampleuser abcde
sampleuser azerty
sampleuser qwerty
stacky qwerty
testuser abcde
testuser azerty
方法:
「トリック」は、1からある制限までの数を含むテーブルを持つことです。私はそれをintegerseries
と呼んでいます 。これは、次のような「水平」のものを変換するために使用できます。';' delimited strings
rows
に 。
これが機能する方法は、integerseries
に「参加」するときです。 、cross join
を実行しています 、これは「内部結合」で「自然に」起こることです。
各行は、integerseries
からの異なる「シーケンス番号」で複製されます そのrow
に使用するリストの「リソース」の「インデックス」として使用するテーブル 。
アイデアは次のとおりです。
- リスト内のアイテムの数を数えます。
- リスト内の位置に基づいて各アイテムを抽出します。
-
integerseries
を使用する 1つの行を一連の行に変換してuser
から個々の「リソースID」を抽出します 。resources
進むにつれて。
2つの関数を使用することにしました:
-
'区切り文字列リスト'と'インデックス'を指定した関数は、リスト内の位置の値を返します。私はそれを呼んでいます:
VALUE_IN_SET
。つまり、「A; B; C」と「インデックス」が2の場合、「B」を返します。 -
'区切り文字列リスト'を指定した関数は、リスト内のアイテム数のカウントを返します。私はそれを呼んでいます:
COUNT_IN_SET
。つまり、「A;B;C」を指定すると3が返されます
これらの2つの関数とintegerseries
delimited items list in a column
に対する一般的な解決策を提供する必要があります 。
機能しますか?
';' delimited string in column
。 'cross_join'(isequence.id
によって生成された値を含むすべての列が表示されます 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
「正規化された」テーブル出力:
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
上記の「正規化された」user_resources
を使用する テーブルの場合、必要な出力を提供するための単純な結合です:
必要な機能 (これらはどこでも使用できる一般的な関数です )
注:これらの関数の名前は、mysql FIND_IN_SET関数 。つまり、文字列リストに関して同様のことをしますか?
COUNT_IN_SET
関数:character delimited items
を返します 列に。
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
関数:delimited list
を処理します one based array
として 指定された「インデックス」の値を返します。
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 ;
関連情報:
-
最後に、SQLFiddle-動作するコード を取得する方法を考え出しました。 関数をコンパイルします。
-
SQLite
で機能するバージョンがあります データベースもSQLite-連結フィールドを正規化して結合しますか?
テーブル(データ付き):
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);
リソース:
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');