문제

MySQL 웹 분석 데이터베이스에는 새로운 활동이 가져 오면 하루 종일 업데이트되는 요약 테이블이 포함되어 있습니다. 요약이 이전 계산을 덮어 쓰기 위해 중복 키 업데이트에서 사용하지만 요약 테이블의 고유 키의 열 중 하나가 선택적 FK이므로 NULL 값을 포함하기 때문에 어려움이 있습니다.

이 널은 "존재하지 않으며 그러한 모든 경우는 동일합니다"를 의미합니다. 물론, MySQL은 일반적으로 널을 "알 수없는 것으로 간주하며 그러한 모든 경우는 동일하지 않다"는 것을 의미합니다.

기본 구조는 다음과 같습니다.

각 세션에 대한 항목이 포함 된 "활동"테이블, 각각 캠페인에 속하며 옵션 필터 및 일부 항목에 대한 트랜잭션 ID.

CREATE TABLE `Activity` (
    `session_id` INTEGER AUTO_INCREMENT
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `transaction_id` INTEGER DEFAULT NULL
    , PRIMARY KEY (`session_id`)
);

활동 테이블의 총 세션 수의 일일 롤업을 포함하는 "요약"테이블, 거래 ID가 포함 된 총 세션의 총 수. 이 요약은 캠페인과 (선택적) 필터의 모든 조합마다 하나로 나뉩니다. 이것은 MyISAM을 사용하는 비 트랜잭션 테이블입니다.

CREATE TABLE `Summary` (
    `day` DATE NOT NULL
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `sessions` INTEGER UNSIGNED DEFAULT NULL
    , `transactions` INTEGER UNSIGNED DEFAULT NULL
    , UNIQUE KEY (`day`, `campaign_id`, `filter_id`)
) ENGINE=MyISAM;

실제 요약 쿼리는 다음과 같은 것입니다. 세션 및 거래 수를 계산 한 다음 캠페인 및 (선택 사항) 필터로 그룹화합니다.

INSERT INTO `Summary` 
    (`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`)
    SELECT `day`, `campaign_id`, `filter_id
        , COUNT(`session_id`) AS `sessions`
        , COUNT(`transaction_id` IS NOT NULL) AS `transactions`
    FROM Activity
    GROUP BY `day`, `campaign_id`, `filter_id`
ON DUPLICATE KEY UPDATE
    `sessions` = VALUES(`sessions`)
    , `transactions` = VALUES(`transactions`)
;

filter_id가 null 인 경우의 요약을 제외하고는 모든 것이 잘 작동합니다. 이 경우 ON 중복 키 업데이트 절은 기존 행과 일치하지 않으며 매번 새 행이 작성됩니다. 이것은 "null! = null"이라는 사실 때문입니다. 그러나 우리가 필요한 것은 고유 키를 비교할 때 "null = null"입니다.

나는 지금까지 우리가 생각해 낸 사람들에 대한 해결 방법이나 피드백에 대한 아이디어를 찾고 있습니다. 우리가 생각한 해결 방법은 지금까지 따릅니다.

  1. 요약을 실행하기 전에 널 키 값이 포함 된 모든 요약 항목을 삭제하십시오. (이것은 우리가 지금하고있는 일입니다.) 요약 프로세스 중에 쿼리가 실행되는 경우 누락 된 데이터로 결과를 반환하는 부작용이 있습니다.

  2. 기본 널 열을 기본 0으로 변경하여 고유 키를 일관되게 일치시킬 수 있습니다. 이는 요약 테이블에 대한 쿼리 개발을 지나치게 복잡하게하는 부작용이 있습니다. 그것은 우리가 "case filter_id = 0 그런 다음 else filter_id end"를 많이 사용하도록 강요하고 다른 모든 테이블에는 filter_id에 대한 실제 널이 있기 때문에 어색한 결합을 만듭니다.

  3. "case filter_id = 0을 반환 한 다음 else filter_id end"를 반환하는 뷰를 작성하고 테이블 대신이보기를 직접 사용하십시오. 요약 테이블에는 수십만 행이 포함되어 있으며 View Performance가 상당히 열악하다고 들었습니다.

  4. 중복 항목을 생성하고 요약 완료 후 이전 항목을 삭제하십시오. 미리 삭제하는 것과 비슷한 문제가 있습니다.

  5. NULL에 0이 포함 된 대리 열을 추가하고 고유 키에 해당 대리를 사용할 수 있습니다 (실제로 모든 열이 NULL이 아닌 경우 기본 키를 사용할 수 있습니다).
    이 솔루션은 위의 예가 예제 일 뿐이라는 점을 제외하고는 합리적으로 보입니다. 실제 데이터베이스에는 6 개의 요약 테이블이 포함되어 있으며 그 중 하나에는 고유 키에 4 개의 무효 열이 포함되어 있습니다. 오버 헤드가 너무 많다는 우려가 있습니다.

더 나은 해결 방법, 테이블 구조, 업데이트 프로세스 또는 MySQL 모범 사례가 있습니까?

편집 : "널의 의미"를 명확히하기 위해

NULL 열을 포함하는 요약 행의 데이터는 요약 보고서에서 단일 "Catch-all"행이라는 의미에서만 해당 데이터 포인트가 존재하지 않거나 알려지지 않은 항목을 요약한다는 의미에서만 함께 속하는 것으로 간주됩니다. 따라서 요약 테이블 자체의 맥락에서 의미는 "가치가없는 항목의 합"입니다. 반면에 관계형 테이블 내에서는 실제로 무효 결과입니다.

요약 테이블에서 고유 한 키에 넣는 유일한 이유는 요약 보고서를 다시 계산할 때 자동 업데이트 (중복 키 업데이트)를 허용하기 때문입니다.

아마도 그것을 설명하는 더 좋은 방법은 요약 테이블 그룹 중 하나가 응답자가 제공 한 비즈니스 주소의 우편 번호 접두사에 의해 지리적으로 결과를 얻는 특정 예일 것입니다. 모든 응답자가 비즈니스 주소를 제공하는 것은 아니므로 거래와 주소 테이블 간의 관계는 매우 정확합니다. 이 데이터의 요약 테이블에서 해당 영역 내의 데이터 요약을 포함하는 각 우편 번호 접두사에 대해 행이 생성됩니다. 우편 번호 접두사가 알려지지 않은 데이터 요약을 보여주기 위해 추가 행이 생성됩니다.

나머지 데이터 테이블을 명시 적 "there_is_no_zip_code"0- 값을 갖도록 변경 하고이 값을 나타내는 ZipCodePrefix 테이블에 특별 레코드를 배치하는 것은 부적절하다.

도움이 되었습니까?

해결책

나는 (2)의 선을 따라 무언가가 실제로 가장 좋은 방법이라고 생각합니다. 또는 적어도 처음부터 시작한 경우입니다. SQL에서 NULL은 알 수없는 것을 의미합니다. 당신이 다른 의미를 원한다면, 당신은 실제로 그것에 특별한 값을 사용해야하며, 0은 확실히 괜찮은 선택입니다.

당신은 이것을 가로 질러해야합니다 전체 이 테이블뿐만 아니라 데이터베이스. 그러면 이상한 특별한 경우에도 감겨서는 안됩니다. 실제로, 당신은 현재 많은 것을 제거 할 수 있어야합니다 (예 : 현재 필터가없는 요약 행을 원한다면 정상 케이스와 달리 특별한 경우 "필터가 null"이 있습니다. "필터 =?".)

또한 FK 제약 조건을 유효하게 유지하기 위해 추천 테이블에서도 "존재하지 않는"항목을 만들어야합니다.

추신 : OA 기본 키가있는 테이블은 관계형 테이블이 아니며 실제로 피해야합니다.

편집 1

흠,이 경우 실제로 복제 키 업데이트가 필요합니까? 인서트를하고 있다면 ... 선택하면 아마도 그렇게 할 것입니다. 그러나 앱이 데이터를 공급하는 경우 직접 수행하십시오. 업데이트를 수행하십시오 (매핑 zip = null 에게 zip is null), 0이 삽입 된 경우 (MySQL이 이것을 반환합니다) 얼마나 많은 행이 변경되었는지 확인하십시오.

다른 팁

기본 널 열을 기본 0으로 변경하여 고유 키를 일관되게 일치시킬 수 있습니다. 이는 요약 테이블에 대한 쿼리 개발을 지나치게 복잡하게하는 부작용이 있습니다. 그것은 우리가 "case filter_id = 0 그런 다음 else filter_id end"를 많이 사용하도록 강요하고 다른 모든 테이블에는 filter_id에 대한 실제 널이 있기 때문에 어색한 결합을 만듭니다.

"case filter_id = 0을 반환 한 다음 else filter_id end"를 반환하는 뷰를 작성하고 테이블 대신이보기를 직접 사용하십시오. 요약 테이블에는 수십만 행이 포함되어 있으며 View Performance가 상당히 열악하다고 들었습니다.

MySQL 5.x에서의 성능보기는 뷰가 널로 대체하는 것 외에는 아무것도하지 않기 때문에 괜찮습니다. 보기에서 집계/정렬을 사용하지 않는 한,보기에 대한 대부분의 쿼리는 쿼리 옵티마이저에 의해 다시 작성되어 기본 테이블을 눌렀습니다.

물론 FK이므로 ID가 0 인 참조 테이블에 항목을 만들어야합니다.

MARIADB (이전의 MySQL)의 최신 버전을 사용하면 대리 열 경로 #5를 사용하면 중복 키 업데이트 문의 삽입으로 단순히 업 서적을 수행 할 수 있습니다. MySQL의 생성 된 저장된 열 또는 mariadb 영구적 인 가상 열을 추가하여 귀중한 필드에 고유성 제약 조건을 적용하면 일부 부풀어 오르기 위해 데이터베이스에서 넌센스 데이터를 간접적으로 유지합니다.

예를 들어

CREATE TABLE IF NOT EXISTS bar (
    id INT PRIMARY KEY AUTO_INCREMENT,
    datebin DATE NOT NULL,
    baz1_id INT DEFAULT NULL,
    vbaz1_id INT AS (COALESCE(baz1_id, -1)) STORED,
    baz2_id INT DEFAULT NULL,
    vbaz2_id INT AS (COALESCE(baz2_id, -1)) STORED,
    blam DOUBLE NOT NULL,
    UNIQUE(datebin, vbaz1_id, vbaz2_id)
);

INSERT INTO bar (datebin, baz1_id, baz2_id, blam)
    VALUES ('2016-06-01', null, null, 777)
ON DUPLICATE KEY UPDATE
    blam = VALUES(blam);

MARIADB의 경우 영구적으로 저장된 대체의 경우 인덱스에는 지속성이 필요합니다.

MySQL은 열을 생성했습니다 mariadb 가상 열

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top