5

2 つのテーブル、場所、および場所グループがあります

CREATE TABLE locations (
    location_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(63) UNIQUE NOT NULL
);

INSERT INTO locations (name)
  VALUES
('london'),
('bristol'),
('exeter');

CREATE TABLE location_groups (
    location_group_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    location_ids VARCHAR(255) NOT NULL,
    user_ids VARCHAR(255) NOT NULL,
    name VARCHAR(63) NOT NULL,
);

INSERT INTO location_groups (location_ids, user_ids, name)
  VALUES
('1', '1,2,4', 'south east'),
('2,3', '2', 'south west');

私がやろうとしているのは、指定された user_id が存在するすべての location_group のすべての location_id を返すことです。CSV を使用して、location_ids と user_ids を location_groups テーブルに保存しています。これが正規化されていないことはわかっていますが、これがデータベースの状態であり、私の制御範囲外です。

私の現在のクエリは次のとおりです。

SELECT location_id 
  FROM locations 
  WHERE FIND_IN_SET(location_id, 
      (SELECT location_ids 
         FROM location_groups 
         WHERE FIND_IN_SET(2,location_groups.user_ids)) )

たとえば、user_id = 1 の場合 (1 つの location_group 行のみが返されるため)、これは正常に機能しますが、user_id = 2 を検索すると、サブクエリが複数の行を返すというエラーが表示されます。これはユーザー 2 として予期されます。 2 つの location_groups にあります。エラーがスローされる理由を理解しています。解決方法を考えています。

location_groups.user_ids で user_id 1 を検索するときに明確にするために、location_id 1 を返す必要があります。user_id 2 を検索すると、location_ids 1、2、3 が返されます。

これは複雑なクエリであることは承知していますので、不明な点があればお知らせください。どんな助けでも大歓迎です!ありがとうございました。

4

1 に答える 1

7

サブクエリで sGROUP_CONCATを組み合わせるために使用できます。location_id

SELECT location_id 
FROM locations 
WHERE FIND_IN_SET(location_id, 
    (SELECT GROUP_CONCAT(location_ids)
     FROM location_groups 
     WHERE FIND_IN_SET(2,location_groups.user_ids)) )

または、正規化が優れている理由の例として、クエリの記述に関する問題を使用します。このクエリを使用したとしても、適切に正規化されたテーブルに対するクエリよりも実行速度が遅くなります。それを使用して、テーブルを再構築する必要がある理由を示すことができます。


参考までに (および他の読者のために)、正規化されたスキーマは次のようになります (ベース テーブルへの追加の変更が含まれています)。

location_groupsテーブル内の複合フィールドは、単純に追加の行に分割して1NFを実現できますが、列は候補キーの一部のみに依存するため、これは2NFにはありません。(別の考え方としては、地域/グループ、場所、およびユーザー間の関係ではなく、地域の属性です。)namelocation(location, user)name

代わりに、これらの列は 1NF の 2 つの追加テーブルに分割されます。1 つは場所と地域を接続するためのもので、もう 1 つはユーザーと地域を接続するためのものです。後者は (地域ではなく) ユーザーと場所の間の関係である必要があるかもしれませんが、現在のスキーマには当てはまりません (これは、現在の正規化されていないスキーマの別の問題である可能性があります)。地域と場所の関係は 1 対多です (各場所が 1 つの地域にあるため)。サンプル データから、地域とユーザーの関係が多対多であることがわかります。その後location_groups、テーブルはテーブルになりregionます。

-- normalized from `location_groups`
CREATE TABLE regions (
    `id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(63) UNIQUE NOT NULL
);

-- slightly altered from original
CREATE TABLE locations (
    `id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(63) UNIQUE NOT NULL
);

-- missing from original sample
CREATE TABLE users (
    `id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(63) UNIQUE NOT NULL
);

-- normalized from `location_groups`
CREATE TABLE location_regions (
    `region` INT UNSIGNED,
    `location` INT UNSIGNED UNIQUE NOT NULL,
    PRIMARY KEY (`region`, `location`),
    FOREIGN KEY (`region`)
        REFERENCES regions (id)
        ON DELETE restrict ON UPDATE cascade,
    FOREIGN KEY (`location`)
        REFERENCES locations (id)
        ON DELETE cascade ON UPDATE cascade
);

-- normalized from `location_groups`
CREATE TABLE user_regions (
    `region` INT UNSIGNED NOT NULL,
    `user` INT UNSIGNED NOT NULL,
    PRIMARY KEY (`region`, `user`),
    FOREIGN KEY (`region`)
        REFERENCES regions (id)
        ON DELETE restrict ON UPDATE cascade,
    FOREIGN KEY (`user`)
        REFERENCES users (id)
        ON DELETE cascade ON UPDATE cascade
);

サンプルデータ:

INSERT INTO regions
  VALUES
('South East'),
('South West'),
('North East'),
('North West');

INSERT INTO locations (`name`)
  VALUES
('London'),
('Bristol'),
('Exeter'),
('Hull');

INSERT INTO users (`name`)
  VALUES
('Alice'),
('Bob'),
('Carol'),
('Dave'),
('Eve');

------ Location-Region relation ------
-- temporary table used to map natural keys to surrogate keys
CREATE TEMPORARY TABLE loc_rgns (
    `location` VARCHAR(63) UNIQUE NOT NULL
    `region` VARCHAR(63) NOT NULL,
);

-- Hull added to demonstrate correctness of desired query
INSERT INTO loc_rgns (region, location)
  VALUES
('South East', 'London'),
('South West', 'Bristol'),
('South West', 'Exeter'),
('North East', 'Hull');

-- map natural keys to surrogate keys for final relationship
INSERT INTO location_regions (`location`, `region`)
  SELECT loc.id, rgn.id
    FROM locations AS loc
      JOIN loc_rgns AS lr ON loc.name = lr.location
      JOIN regions AS rgn ON rgn.name = lr.region;

------ User-Region relation ------
-- temporary table used to map natural keys to surrogate keys
CREATE TEMPORARY TABLE usr_rgns (
    `user` INT UNSIGNED NOT NULL,
    `region` VARCHAR(63) NOT NULL,
    UNIQUE (`user`, `region`)
);

-- user 3 added in order to demonstrate correctness of desired query
INSERT INTO usr_rgns (`user`, `region`)
  VALUES
(1, 'South East'),
(2, 'South East'),
(2, 'South West'),
(3, 'North West'),
(4, 'South East');

-- map natural keys to surrogate keys for final relationship
INSERT INTO user_regions (`user`, `region`)
  SELECT user, rgn.id
    FROM usr_rgns AS ur
      JOIN regions AS rgn ON rgn.name = ur.region;

さて、正規化されたスキーマの目的のクエリは次のとおりです。

SELECT DISTINCT loc.id
FROM locations AS loc
  JOIN location_regions AS lr ON loc.id = lr.location
  JOIN user_regions AS ur ON lr.region = ur.region
;

結果:

+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
于 2012-01-17T19:28:39.433 に答える