Nếu user_resources
(t1) là một 'bảng chuẩn hóa' với một hàng cho mỗi user => resource
kết hợp thì truy vấn để nhận câu trả lời sẽ đơn giản như chỉ cần joining
các bảng với nhau.
Than ôi, nó denormalized
bằng cách có resources
cột dưới dạng:'danh sách id tài nguyên' được phân tách bằng dấu ';' nhân vật.
Nếu chúng ta có thể chuyển đổi cột 'tài nguyên' thành các hàng thì rất nhiều khó khăn sẽ biến mất vì việc nối bảng trở nên đơn giản.
Truy vấn để tạo đầu ra được yêu cầu cho:
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
Đầu ra:
user data
---------- --------
sampleuser abcde
sampleuser azerty
sampleuser qwerty
stacky qwerty
testuser abcde
testuser azerty
Cách thức:
'Bí quyết' là có một bảng chứa các số từ 1 đến một số giới hạn. Tôi gọi nó là integerseries
. Nó có thể được sử dụng để chuyển đổi những thứ 'ngang' như:';' delimited strings
thành rows
.
Cách này hoạt động là khi bạn 'nối' với integerseries
, bạn đang thực hiện cross join
, đó là điều xảy ra 'tự nhiên' với 'liên kết bên trong'.
Mỗi hàng được sao chép với một 'số thứ tự' khác từ integerseries
bảng mà chúng tôi sử dụng làm 'chỉ mục' của 'tài nguyên' trong danh sách mà chúng tôi muốn sử dụng cho rows
đó .
Ý tưởng là:
- đếm số lượng các mục trong danh sách.
- trích xuất từng mục dựa trên vị trí của nó trong danh sách.
- Sử dụng
integerseries
để chuyển đổi một hàng thành một tập hợp các hàng trích xuất 'id tài nguyên' riêng lẻ từuser
.resources
khi chúng ta đồng hành.
Tôi quyết định sử dụng hai chức năng:
-
hàm đưa ra 'danh sách chuỗi được phân tách' và 'chỉ mục' sẽ trả về giá trị tại vị trí trong danh sách. Tôi gọi nó là:
VALUE_IN_SET
. tức là đã cho 'A; B; C' và 'chỉ số' là 2 thì nó trả về 'B'. -
hàm đã cho một 'danh sách chuỗi được phân tách' sẽ trả về số lượng các mục trong danh sách. Tôi gọi nó là:
COUNT_IN_SET
. tức là đã cho 'A; B; C' sẽ trả về 3
Hóa ra hai hàm đó và integerseries
nên cung cấp một giải pháp chung cho delimited items list in a column
.
Nó có hoạt động không?
Truy vấn để tạo bảng 'chuẩn hóa' từ ';' delimited string in column
. Nó hiển thị tất cả các cột, bao gồm cả các giá trị được tạo do 'cross_join' (isequence.id
dưới dạng 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
Đầu ra của bảng 'chuẩn hóa':
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
Sử dụng user_resources
'chuẩn hóa' ở trên bảng, nó là một phép nối đơn giản để cung cấp đầu ra được yêu cầu:
Các chức năng cần thiết ( đây là những chức năng chung có thể được sử dụng ở mọi nơi )
lưu ý:Tên của những hàm này có liên quan đến mysql hàm FIND_IN_SET . tức là họ làm những việc tương tự liên quan đến danh sách chuỗi?
COUNT_IN_SET
function:trả về số lượng các mục được phân tách bằng ký tự character delimited items
trong cột.
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
function:xử lý delimited list
dưới dạng one based array
và trả về giá trị tại 'chỉ mục' đã cho.
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 ;
Thông tin liên quan:
-
Cuối cùng đã tìm ra cách lấy SQLFiddle - mã làm việc để biên dịch các hàm.
-
Có một phiên bản của cái này hoạt động cho
SQLite
cơ sở dữ liệu cũng như SQLite- Chuẩn hóa một trường được nối và kết hợp với nó?
Các bảng (có dữ liệu):
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);
Nguồn:
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');