태그 시스템 구현 방법


90

SO에서 사용되는 것과 같은 태그 시스템을 구현하는 가장 좋은 방법이 무엇인지 궁금합니다. 나는 이것을 생각하고 있었지만 좋은 확장 가능한 솔루션을 찾을 수 없습니다.

tags테이블, articles테이블 및 테이블을 갖는 기본적인 3 개의 테이블 솔루션을 생각하고있었습니다 tag_to_articles.

이것이이 문제에 대한 최선의 해결책입니까, 아니면 대안이 있습니까? 이 방법을 사용하면 테이블이 시간이 지남에 따라 매우 커지며 검색하는 것이 너무 효율적이지 않다고 생각합니다. 반면에 쿼리가 빠르게 실행되는 것은 그다지 중요하지 않습니다.


답변:


119

이 블로그 게시물이 흥미로울 것이라고 생각합니다. 태그 : 데이터베이스 스키마

문제 : 원하는만큼의 태그로 책갈피 (또는 블로그 게시물 등)에 태그를 지정할 수있는 데이터베이스 스키마를 원합니다. 나중에 쿼리를 실행하여 책갈피를 태그의 결합 또는 교차로 제한하려고합니다. 검색 결과에서 일부 태그를 제외 (예 : 빼기) 할 수도 있습니다.

"MySQLicious"솔루션

이 솔루션에서 스키마에는 테이블이 하나만 있으며 비정규 화됩니다. MySQLicious는 del.icio.us 데이터를이 구조의 테이블로 가져 오기 때문에이 유형을 "MySQLicious 솔루션"이라고합니다.

여기에 이미지 설명 입력여기에 이미지 설명 입력

"search + webservice + semweb"에 대한 교차 (AND) 쿼리 :

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

"search | webservice | semweb"에 대한 통합 (OR) 쿼리 :

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

“search + webservice-semweb”에 대한 마이너스 쿼리

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

"Scuttle"솔루션

Scuttle 은 데이터를 두 개의 테이블로 구성합니다. "scCategories"테이블은 "태그"테이블이며 "책갈피"테이블에 대한 외래 키를 가지고 있습니다.

여기에 이미지 설명 입력

“bookmark + webservice + semweb”에 대한 교차 (AND) 쿼리 :

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

먼저 모든 북마크-태그 조합이 검색됩니다. 여기서 태그는 "bookmark", "webservice"또는 "semweb"(c.category IN ( 'bookmark', 'webservice', 'semweb'))입니다. 검색된 세 개의 태그가 모두 고려됩니다 (HAVING COUNT (b.bId) = 3).

"bookmark | webservice | semweb"에 대한 Union (OR) 쿼리 : HAVING 절을 생략하고 union이 있습니다.

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

마이너스 (제외) "bookmark + webservice-semweb"쿼리, 즉 semweb이 아니라 북마크 및 웹 서비스입니다.

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

HAVING COUNT를 생략하면“bookmark | webservice-semweb”에 대한 쿼리가 표시됩니다.


"Toxi"솔루션

Toxi 는 3 개의 테이블 구조를 생각해 냈습니다. 테이블 "태그 맵"을 통해 북마크와 태그는 n-to-m 관련됩니다. 각 태그는 서로 다른 북마크와 함께 사용할 수 있으며 그 반대의 경우도 마찬가지입니다. 이 DB 스키마는 워드 프레스에서도 사용됩니다. 쿼리는 "scuttle"솔루션에서와 매우 동일합니다.

여기에 이미지 설명 입력

“bookmark + webservice + semweb”에 대한 교차 (AND) 쿼리

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

"bookmark | webservice | semweb"에 대한 통합 (OR) 쿼리

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

마이너스 (제외) "bookmark + webservice-semweb"쿼리, 즉 semweb이 아니라 북마크 및 웹 서비스입니다.

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

HAVING COUNT를 생략하면“bookmark | webservice-semweb”에 대한 쿼리가 표시됩니다.


3
여기에 해당 블로그 게시물의 작성자입니다. 블로그는 더 이상 Chrome에 의해 차단되지 않습니다 (어리석은 워드 프레스 취약점, 이제 tumblr로 이동). 마크 다운으로 전환 한 것에 대한 명성
hansaplast 2013 년

안녕하세요 @Philipp. 좋아, 내 대답을 수정했습니다. BTW, 데이터베이스 태그 시스템에 대한 훌륭한 게시물에 감사드립니다.
Nick Dandoulakis 2013 년

1
참고 사항 : 'bookmark'및 'webservice'를 검색 한 경우 Toxi 솔루션에 대한 교차 쿼리가 책갈피도 표시하도록하려면 "HAVING COUNT (b.id) = 3"을 다음에서 변경해야합니다. 3- "sizeof (array ( 'bookmark', 'webservice'))". 이것을 다이내믹 태그 쿼리 함수로 사용할 계획이라면 사소한 세부 사항입니다.
toxicate20

3
게시물에 언급 된 다른 솔루션에 대한 성능 비교 링크가 있습니까?
kampta

@kampta, 아니요, 링크가 없습니다.
Nick Dandoulakis 2011

8

3 테이블 솔루션에 문제가 없습니다.

또 다른 옵션은 기사에 적용 할 수있는 태그 수를 제한하고 (SO의 5와 같이) 기사 테이블에 직접 추가하는 것입니다.

DB 정규화에는 장점과 단점이 있습니다. 마치 하나의 테이블에 하드 와이어 링하는 것에는 장점과 단점이 있습니다.

둘 다 할 수 없다고 말하는 것은 없습니다. 정보를 반복하는 것은 관계형 DB 패러다임에 위배되지만 성능이 목표라면 패러다임을 깨뜨려야 할 수도 있습니다.


예,이 방법에는 몇 가지 단점이 있지만 태그를 기사 테이블에 직접 넣는 것이 옵션이 될 것입니다. (tag1,2,3,4)와 같이 쉼표로 구분 된 필드에 5 개의 태그를 저장하면 이것은 쉬운 방법입니다. 문제는 검색이 더 빨리 진행되는지 여부입니다. 예를 들어 누군가가 tag1로 모든 것을보고 싶다면 전체 기사 테이블을 살펴 봐야합니다. 이것은 tag_to_article 테이블을 살펴 보는 것보다 덜 철저 할 것입니다. 그러나 다시 한 번 tags_to_article 테이블이 더 얇습니다. 또 다른 것은 PHP에서 매번 폭발해야한다는 것입니다. 시간이 걸리는지 모르겠습니다.
Saif Bechan

두 가지를 모두 수행하면 (아티클과 별도의 테이블에 태그 포함) 포스트 중심 검색과 태그 중심 검색 모두에 대한 성능을 제공합니다. 트레이드 오프는 반복되는 정보를 유지하는 부담입니다. 또한 태그 수를 제한하여 각 태그를 고유 한 열에 넣을 수 있습니다. 기사에서 *를 선택하십시오. Where XXXXX and go; 폭발 할 필요가 없습니다.
John

6

제안 된 세 가지 테이블 구현이 태그 지정에 적합합니다.

그러나 스택 오버플로는 다른 구현을 사용합니다. 태그를 posts 테이블의 varchar 열에 일반 텍스트로 저장하고 전체 텍스트 인덱싱을 사용하여 태그와 일치하는 게시물을 가져옵니다. 예를 들면 posts.tags = "algorithm system tagging best-practices". 나는 Jeff가 이것을 어딘가에서 언급했다고 확신하지만 나는 어디에 있는지 잊는다.


4
이것은 매우 비효율적으로 보입니다. 태그 순서는 어떻습니까? 아니면 관련 태그? (예 : "프로세스" "알고리즘"유사되는 등의 일 등)
리처드 Duerr

3

제안 된 솔루션은 태그와 기사 사이의 다 대다 관계를 해결하기 위해 내가 생각할 수있는 유일한 방법은 아니지만 가장 좋은 방법입니다. 그래서 내 투표는 '예, 여전히 최고입니다.' 그래도 대안에 관심이 있습니다.


나는 동의한다. 이러한 태그 및 TagMap 테이블은 레코드 크기가 작으며 올바르게 인덱싱 된 경우 성능이 크게 저하되지 않습니다. 항목 당 태그 수를 제한하는 것도 좋은 생각 일 수 있습니다.
PanJanek

2

데이터베이스가 인덱싱 가능한 배열 (예 : PostgreSQL)을 지원하는 경우 완전히 비정규 화 된 솔루션을 권장합니다. 태그를 동일한 테이블에 문자열 배열로 저장합니다. 그렇지 않은 경우 개체를 태그에 매핑하는 보조 테이블이 최상의 솔루션입니다. 태그에 대한 추가 정보를 저장해야하는 경우 별도의 태그 테이블을 사용할 수 있지만 모든 태그 조회에 대해 두 번째 조인을 도입 할 필요가 없습니다.


POstgreSQL은 정수 배열의 인덱스 만 지원합니다. postgresql.org/docs/current/static/intarray.html
Mike Chamberlain

1
Nowadys는 텍스트도 지원합니다 : postgresql.org/docs/9.6/static/arrays.html
luckydonald

2

더 나은 성능을 위해 최적화 된 MySQLicious를 제안하고 싶습니다. 그 전에 Toxi (3 테이블) 솔루션의 단점은

수백만 개의 질문이 있고 각각에 5 개의 태그가있는 경우 태그 맵 테이블에는 5 백만 개의 항목이 있습니다. 따라서 먼저 태그 검색을 기반으로 1 만 개의 태그 맵 항목을 필터링 한 다음 해당 1 만 개의 일치하는 질문을 다시 필터링해야합니다. 따라서 아티 컬 ID가 단순한 숫자이면 필터링하는 동안 괜찮지 만 UUID (32 varchar)의 경우 필터링하면 인덱싱되지만 더 큰 비교가 필요합니다.

내 솔루션 :

새 태그가 생성 될 때마다 counter ++ (base 10)을 사용하고 해당 카운터를 base64로 변환합니다. 이제 각 태그 이름에는 base64 ID가 있습니다. 이 ID를 이름과 함께 UI에 전달합니다. 이렇게하면 시스템에 4095 개의 태그가 생성 될 때까지 최대 2 개의 문자 ID를 갖게됩니다. 이제 이러한 여러 태그를 각 질문 테이블 태그 열에 연결합니다. 구분 기호도 추가하고 정렬하십시오.

그래서 테이블은 다음과 같습니다

여기에 이미지 설명 입력

쿼리하는 동안 실제 태그 이름 대신 id를 쿼리합니다. 이 때문에 SORTED , and태그의 상태가 더 효율적입니다 ( LIKE '%|a|%|c|%|f|%).

참고 하나의 공간 구분이 충분하지 않다고 우리는 같은 차별화 태그 더블 구분이 필요 sql하고 mysql있기 때문에를 LIKE "%sql%"반환합니다 mysql아니라 결과를. 해야한다LIKE "%|sql|%"

검색이 인덱싱되지 않음을 알고 있지만 여전히 author / dateTime과 같은 기사와 관련된 다른 열에 인덱싱되었을 수 있습니다. 그렇지 않으면 전체 테이블 스캔으로 이어질 것입니다.

마지막으로이 솔루션을 사용하면 조인 조건에서 백만 개의 레코드와 5 백만 개의 레코드를 비교해야하는 내부 조인이 필요하지 않습니다.


팀, 의견 에이 솔루션의 단점에 대한 의견을 제공하십시오.
Kanagavelu Sugumar

@Nick Dandoulakis 위의 솔루션에 대한 의견을 제공하여 도와주세요.
Kanagavelu Sugumar

@Juha Syrjälä 위의 솔루션이 괜찮습니까?
Kanagavelu Sugumar

0
CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

메모:

  • 최적화를 어렵게 만드는 추가 다 대다 테이블을 거치지 않는다는 점에서 TOXI보다 낫습니다.
  • 물론 중복 태그로 인해 내 접근 방식이 TOXI보다 약간 더 클 수 있지만 전체 데이터베이스 의 작은 비율이며 성능 향상이 상당 할 수 있습니다.
  • 확장 성이 뛰어납니다.
  • 대리 AUTO_INCREMENTPK 가 없습니다 (필요하지 않기 때문에) . 따라서 Scuttle보다 낫습니다.
  • 이 인덱스를 사용할 수 없기 때문에 MySQLicious 짜증 ( LIKE최고의 와일드 카드를 거짓 안타를 문자열에)
  • MySQL의 경우 '클러스터링'효과를 얻으려면 ENGINE = InnoDB를 사용해야합니다.

관련 토론 (MySQL 용) :
다 : 다 매핑 테이블 최적화
순서 목록

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.