Jika user_resources
(t1) adalah 'tabel yang dinormalisasi' dengan satu baris untuk setiap user => resource
kombinasi maka kueri untuk mendapatkan jawabannya akan sesederhana hanya joining
tabel bersama.
Sayangnya, ini denormalized
dengan memiliki resources
kolom sebagai:'daftar id sumber daya' dipisahkan oleh ';' karakter.
Jika kita dapat mengubah kolom 'sumber daya' menjadi baris, maka banyak kesulitan akan hilang saat tabel bergabung menjadi sederhana.
Kueri untuk menghasilkan keluaran yang diminta:
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
Keluaran:
user data
---------- --------
sampleuser abcde
sampleuser azerty
sampleuser qwerty
stacky qwerty
testuser abcde
testuser azerty
Bagaimana:
'Triknya' adalah memiliki tabel yang berisi angka dari 1 hingga batas tertentu. Saya menyebutnya integerseries
. Ini dapat digunakan untuk mengonversi hal-hal 'horizontal' seperti:';' delimited strings
ke dalam rows
.
Cara kerjanya adalah ketika Anda 'bergabung' dengan integerseries
, Anda melakukan cross join
, itulah yang terjadi 'secara alami' dengan 'penggabungan batin'.
Setiap baris diduplikasi dengan 'nomor urut' yang berbeda dari integerseries
tabel yang kita gunakan sebagai 'indeks' dari 'sumber daya' dalam daftar yang ingin kita gunakan untuk rows
itu .
Idenya adalah untuk:
- hitung jumlah item dalam daftar.
- ekstrak setiap item berdasarkan posisinya dalam daftar.
- Gunakan
integerseries
untuk mengonversi satu baris menjadi satu set baris yang mengekstrak 'id sumber daya' individu dariuser
.resources
seiring berjalannya waktu.
Saya memutuskan untuk menggunakan dua fungsi:
-
fungsi yang diberi 'daftar string yang dibatasi' dan 'indeks' akan mengembalikan nilai pada posisi dalam daftar. Saya menyebutnya:
VALUE_IN_SET
. yaitu diberikan 'A;B;C' dan 'indeks' dari 2 kemudian mengembalikan 'B'. -
fungsi yang diberi 'daftar string yang dibatasi' akan mengembalikan hitungan jumlah item dalam daftar. Saya menyebutnya:
COUNT_IN_SET
. yaitu diberikan 'A;B;C' akan mengembalikan 3
Ternyata kedua fungsi tersebut dan integerseries
harus memberikan solusi umum untuk delimited items list in a column
.
Berhasil?
Kueri untuk membuat tabel 'dinormalisasi' dari ';' delimited string in column
. Ini menunjukkan semua kolom, termasuk nilai yang dihasilkan karena 'cross_join' (isequence.id
sebagai 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
Output tabel 'dinormalisasi':
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
Menggunakan user_resources
yang 'dinormalisasi' di atas tabel, ini adalah gabungan sederhana untuk memberikan output yang diperlukan:
Fungsi yang dibutuhkan (ini adalah fungsi umum yang dapat digunakan di mana saja )
catatan:Nama-nama fungsi ini terkait dengan mysql Fungsi FIND_IN_SET . yaitu mereka melakukan hal serupa terkait daftar string?
COUNT_IN_SET
fungsi:mengembalikan jumlah character delimited items
di kolom.
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
fungsi:memperlakukan delimited list
sebagai one based array
dan mengembalikan nilai pada 'indeks' yang diberikan.
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 ;
Informasi Terkait:
-
Akhirnya menemukan cara mendapatkan SQLFiddle - kode kerja untuk mengkompilasi fungsi.
-
Ada versi ini yang berfungsi untuk
SQLite
database juga SQLite- Menormalkan bidang gabungan dan bergabung dengannya?
Tabel (dengan data):
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);
Sumber:
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');
Sumber_pengguna:
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');