모든 레코드를 선택하고 결합이 존재하면 테이블 A와 결합하고, 존재하지 않으면 테이블 B ON dbo.StringTranslations.Id =

내 시나리오는 다음과 같습니다.

나는 내 프로젝트를 위해 지역화 작업을하고 있으며 일반적으로 C # 코드 에서이 작업을 수행하려고하지만 SQL을 조금 강화하려고하기 때문에 SQL 에서이 작업을 조금 더하고 싶습니다.

환경 : SQL Server 2014 Standard, C # (. NET 4.5.1)

참고 : 프로그래밍 언어 자체는 관련이 없어야합니다. 완전성을 위해 포함시켜야합니다.

그래서 나는 내가 원하는 것을 달성했지만 원하는 정도까지 달성하지 못했습니다. JOIN기본 SQL을 제외한 모든 SQL을 수행 한 이후로 적어도 1 년 이 걸렸습니다 JOIN. 이는 매우 복잡 합니다.

다음은 데이터베이스의 관련 테이블에 대한 다이어그램입니다. (더 많은 것이 있지만이 부분에는 필요하지 않습니다.)

데이터베이스 다이어그램

이미지에 설명 된 모든 관계는 데이터베이스에서 완료됩니다. PK그리고 FK제한 조건이 모두 설정 및 작동됩니다. 설명 된 열이 없습니다 null. 모든 테이블에는 스키마가 dbo있습니다.

이제, 내가 원하는 것을 거의 수행 하는 쿼리가 있습니다 . 즉, 모든 ID SupportCategories모든 ID가 주어지면 다음 Languages중 하나를 반환합니다.

해당 문자열에 대한 해당 언어 (IE 용 오른쪽 적절한 번역이있는 경우 StringKeyId-> StringKeys.Id존재하고있는 LanguageStringTranslations StringKeyId, LanguageId그리고 StringTranslationId조합은 다음로드, 존재 StringTranslations.Text그것에 대해 StringTranslationId.

는 IF LanguageStringTranslations StringKeyId, LanguageIdStringTranslationId조합 않았다 NOT 존재, 다음은로드 StringKeys.Name값입니다. 는 Languages.Id주어진됩니다 integer.

엉망인 내 쿼리는 다음과 같습니다.

SELECT CASE WHEN T.x IS NOT NULL THEN T.x ELSE (SELECT
    CASE WHEN dbo.StringTranslations.Text IS NULL THEN dbo.StringKeys.Name ELSE dbo.StringTranslations.Text END AS Result
FROM dbo.SupportCategories
    INNER JOIN dbo.StringKeys
        ON dbo.SupportCategories.StringKeyId = dbo.StringKeys.Id
    INNER JOIN dbo.LanguageStringTranslations
        ON dbo.StringKeys.Id = dbo.LanguageStringTranslations.StringKeyId
    INNER JOIN dbo.StringTranslations
        ON dbo.StringTranslations.Id = dbo.LanguageStringTranslations.StringTranslationId
WHERE dbo.LanguageStringTranslations.LanguageId = 38 AND dbo.SupportCategories.Id = 0) END AS Result FROM (SELECT (SELECT
    CASE WHEN dbo.StringTranslations.Text IS NULL THEN dbo.StringKeys.Name ELSE dbo.StringTranslations.Text END AS Result
FROM dbo.SupportCategories
    INNER JOIN dbo.StringKeys
        ON dbo.SupportCategories.StringKeyId = dbo.StringKeys.Id
    INNER JOIN dbo.LanguageStringTranslations
        ON dbo.StringKeys.Id = dbo.LanguageStringTranslations.StringKeyId
    INNER JOIN dbo.StringTranslations
        ON dbo.StringTranslations.Id = dbo.LanguageStringTranslations.StringTranslationId
WHERE dbo.LanguageStringTranslations.LanguageId = 5 AND dbo.SupportCategories.Id = 0) AS x) AS T

문제는 나에게 제공 할 수 없다는 것이다 ALLSupportCategories및 각각 StringTranslations.Text존재하는 경우, 또는 그들의 StringKeys.Name존재하지 않은 경우. 그들 중 하나를 제공하는 데 완벽하지만 전혀 아닙니다. 기본적으로 언어에 특정 키에 대한 번역이없는 경우 기본적으로 번역 된 언어를 사용 StringKeys.Name하는 것이 StringKeys.DefaultLanguageId좋습니다. (적어도 그렇게하지는 않지만 대신에 대한 번역을로드 StringKeys.DefaultLanguageId하면 나머지 쿼리에 올바른 방향을 가리키면 직접 할 수 있습니다.)

나는 이것에 많은 시간을 보냈으며, C #으로 작성 해야하는지 (일반적으로하는 것처럼) 지금까지 수행 될 것임을 알고 있습니다. SQL 에서이 작업을 수행하고 싶습니다. 좋아하는 출력을 얻는 데 문제가 있습니다.

유일한 경고는 적용된 실제 쿼리 수를 제한하고 싶습니다. 모든 열은 색인화되어 있으며 지금은 열이 마음에 들며 실제 스트레스 테스트가 없으면 더 이상 색인을 작성할 수 없습니다.

편집 : 또 다른 메모, 데이터베이스를 가능한 한 표준화 된 상태로 유지하려고하므로 피할 수 있다면 복제하지 않으려 고합니다.

데이터 예

출처

dbo.Support 카테고리 (전체) :

Id  StringKeyId
0   0
1   1
2   2

dbo. 언어 (185 개 레코드, 예를 들어 2 개만 표시) :

Id  Abbreviation    Family  Name    Native
38  en  Indo-European   English English
48  fr  Indo-European   French  français, langue française

dbo.LanguagesString 번역 (전체) :

StringKeyId LanguageId  StringTranslationId
0   38  0
1   38  1
2   38  2
3   38  3
4   38  4
5   38  5
6   38  6
7   38  7
1   48  8 -- added as example

dbo.StringKeys (전체) :

Id  Name    DefaultLanguageId
0   Billing 38
1   API 38
2   Sales   38
3   Open    38
4   Waiting for Customer    38
5   Waiting for Support 38
6   Work in Progress    38
7   Completed   38

dbo.String 번역 (전체) :

Id  Text
0   Billing
1   API
2   Sales
3   Open
4   Waiting for Customer
5   Waiting for Support
6   Work in Progress
7   Completed
8   Les APIs -- added as example

전류 출력

아래의 정확한 쿼리가 주어지면 다음과 같이 출력됩니다.

Result
Billing

원하는 출력

이상적으로, 나는 특정을 생략 할 수 있도록하고 싶습니다 SupportCategories.Id그래서 (언어 (38)에 관계없이 것처럼, 그들 모두를 얻을 English사용, 48 French, 또는 어떤 순간에 다른 언어) :

Id  Result
0   Billing
1   API
2   Sales

추가 예

French(Ie add 1 48 8to LanguageStringTranslations)에 대한 현지화를 추가 하면 출력이 다음으로 변경됩니다 (참고 :이 예제는 분명히 현지화 된 문자열을 StringTranslations)에 추가합니다 (프랑스어 예제로 업데이트).

Result
Les APIs

추가 원하는 출력

위의 예에서 다음 출력이 필요합니다 (프랑스어 예로 업데이트 됨).

Id  Result
0   Billing
1   Les APIs
2   Sales

(예, 기술적으로 일관성 관점에서 잘못된 점을 알고 있지만 상황에서 바람직 할 것입니다.)

편집하다:

작은 업데이트, 나는 dbo.Languages테이블 의 구조를 변경하고 테이블에서 Id (int)열을 삭제하고 Abbreviation(이 이름은로 바뀌고 Id모든 상대 외래 키 및 관계가 업데이트 됨)로 바꿉니다. 기술적 인 관점에서 볼 때 이것은 표가 처음에는 고유 한 ISO 639-1 코드로 제한되어 있기 때문에 제 생각에 더 적합한 설정입니다.

Tl; dr

그래서 다음 질문은, 어떻게 반환이 쿼리를 수정할 수 있는 모든 의를 SupportCategories다음 중 하나를 반환 StringTranslations.Text그것을 위해 StringKeys.Id, Languages.Id조합, 또는StringKeys.Name 는 않은 경우 NOT 존재 하는가?

내 생각은 현재 쿼리를 다른 하위 쿼리로 다른 임시 유형으로 캐스팅 하고이 쿼리를 또 다른 SELECT문으로 래핑 하고 원하는 두 필드 ( SupportCategories.IdResult)를 선택할 수 있다는 것 입니다.

아무것도 찾지 못하면 일반적으로 사용하는 표준 방법을 사용하여 SupportCategoriesC # 프로젝트에 모든 것을로드 한 다음 수동으로 위의 쿼리를 각각 실행합니다 SupportCategories.Id.

모든 제안 / 의견 / 비평에 감사드립니다.

또한 터무니없이 길어서 죄송합니다. 모호성을 원하지 않습니다. 나는 종종 StackOverflow에 있고 물질이 부족한 질문을 보았습니다. 여기서 실수를하고 싶지 않았습니다.



답변

내가 생각해 낸 첫 번째 접근법은 다음과 같습니다.

DECLARE @ChosenLanguage INT = 48;

SELECT sc.Id, Result = MAX(COALESCE(
   CASE WHEN lst.LanguageId = @ChosenLanguage      THEN st.Text END,
   CASE WHEN lst.LanguageId = sk.DefaultLanguageId THEN st.Text END)
)
FROM dbo.SupportCategories AS sc
INNER JOIN dbo.StringKeys AS sk
  ON sc.StringKeyId = sk.Id
LEFT OUTER JOIN dbo.LanguageStringTranslations AS lst
  ON sk.Id = lst.StringKeyId
  AND lst.LanguageId IN (sk.DefaultLanguageId, @ChosenLanguage)
LEFT OUTER JOIN dbo.StringTranslations AS st
  ON st.Id = lst.StringTranslationId
  --WHERE sc.Id = 1
  GROUP BY sc.Id
  ORDER BY sc.Id;

기본적으로 선택한 언어 일치하는 잠재적 문자열을 가져 와서 모든 기본 문자열을 얻은 다음 집계 Id하여 선택한 언어에 대해 우선 순위를 정한 다음 기본값을 폴백으로 사용하십시오.

UNION/ 와 비슷한 작업을 수행 할 수는 EXCEPT있지만 거의 동일한 객체에 대해 여러 번 스캔 할 것으로 생각합니다.


답변

INAaron의 답변에서 및 그룹 을 피하는 대체 솔루션 :

DECLARE
    @SelectedLanguageId integer = 48;

SELECT
    SC.Id,
    SC.StringKeyId,
    Result =
        CASE
            -- No localization available
            WHEN LST.StringTranslationId IS NULL
            THEN SK.Name
            ELSE
            (
                -- Localized string
                SELECT ST.[Text]
                FROM dbo.StringTranslations AS ST
                WHERE ST.Id = LST.StringTranslationId
            )
        END
FROM dbo.SupportCategories AS SC
JOIN dbo.StringKeys AS SK
    ON SK.Id = SC.StringKeyId
LEFT JOIN dbo.LanguageStringTranslations AS LST
    WITH (FORCESEEK) -- Only for low row count in sample data
    ON LST.StringKeyId = SK.Id
    AND LST.LanguageId = @SelectedLanguageId;

언급 한 바와 같이, FORCESEEK힌트는 LanguageStringTranslations제공된 샘플 데이터로 테이블의 카디널리티가 낮기 때문에 가장 효율적인 계획을 얻는 데만 필요합니다 . 행이 많을수록 옵티마이 저는 자연스럽게 인덱스 탐색을 선택합니다.

실행 계획 자체에는 흥미로운 기능이 있습니다.

마지막 외부 조인의 통과 특성은 StringTranslations테이블에 대한 행이 이전에 LanguageStringTranslations테이블 에서 발견 된 경우에만 테이블 찾아보기가 수행됨 을 의미 합니다. 그렇지 않으면이 조인의 내부가 현재 행에 대해 완전히 건너 뜁니다.

테이블 DDL

CREATE TABLE dbo.Languages
(
    Id integer NOT NULL,
    Abbreviation char(2) NOT NULL,
    Family nvarchar(96) NOT NULL,
    Name nvarchar(96) NOT NULL,
    [Native] nvarchar(96) NOT NULL,

    CONSTRAINT PK_dbo_Languages
        PRIMARY KEY CLUSTERED (Id)
);

CREATE TABLE dbo.StringTranslations
(
    Id bigint NOT NULL,
    [Text] nvarchar(128) NOT NULL,

    CONSTRAINT PK_dbo_StringTranslations
    PRIMARY KEY CLUSTERED (Id)
);

CREATE TABLE dbo.StringKeys
(
    Id bigint NOT NULL,
    Name varchar(64) NOT NULL,
    DefaultLanguageId integer NOT NULL,

    CONSTRAINT PK_dbo_StringKeys
    PRIMARY KEY CLUSTERED (Id),

    CONSTRAINT FK_dbo_StringKeys_DefaultLanguageId
    FOREIGN KEY (DefaultLanguageId)
    REFERENCES dbo.Languages (Id)
);

CREATE TABLE dbo.SupportCategories
(
    Id integer NOT NULL,
    StringKeyId bigint NOT NULL,

    CONSTRAINT PK_dbo_SupportCategories
        PRIMARY KEY CLUSTERED (Id),

    CONSTRAINT FK_dbo_SupportCategories
    FOREIGN KEY (StringKeyId)
    REFERENCES dbo.StringKeys (Id)
);

CREATE TABLE dbo.LanguageStringTranslations
(
    StringKeyId bigint NOT NULL,
    LanguageId integer NOT NULL,
    StringTranslationId bigint NOT NULL,

    CONSTRAINT PK_dbo_LanguageStringTranslations
    PRIMARY KEY CLUSTERED
        (StringKeyId, LanguageId, StringTranslationId),

    CONSTRAINT FK_dbo_LanguageStringTranslations_StringKeyId
    FOREIGN KEY (StringKeyId)
    REFERENCES dbo.StringKeys (Id),

    CONSTRAINT FK_dbo_LanguageStringTranslations_LanguageId
    FOREIGN KEY (LanguageId)
    REFERENCES dbo.Languages (Id),

    CONSTRAINT FK_dbo_LanguageStringTranslations_StringTranslationId
    FOREIGN KEY (StringTranslationId)
    REFERENCES dbo.StringTranslations (Id)
);

샘플 데이터

INSERT dbo.Languages
    (Id, Abbreviation, Family, Name, [Native])
VALUES
    (38, 'en', N'Indo-European', N'English', N'English'),
    (48, 'fr', N'Indo-European', N'French', N'français, langue française');

INSERT dbo.StringTranslations
    (Id, [Text])
VALUES
    (0, N'Billing'),
    (1, N'API'),
    (2, N'Sales'),
    (3, N'Open'),
    (4, N'Waiting for Customer'),
    (5, N'Waiting for Support'),
    (6, N'Work in Progress'),
    (7, N'Completed'),
    (8, N'Les APIs'); -- added as example

INSERT dbo.StringKeys
    (Id, Name, DefaultLanguageId)
VALUES
    (0, 'Billing', 38),
    (1, 'API', 38),
    (2, 'Sales', 38),
    (3, 'Open', 38),
    (4, 'Waiting for Customer', 38),
    (5, 'Waiting for Support', 38),
    (6, 'Work in Progress', 38),
    (7, 'Completed', 38);

INSERT dbo.SupportCategories
    (Id, StringKeyId)
VALUES
    (0, 0),
    (1, 1),
    (2, 2);

INSERT dbo.LanguageStringTranslations
    (StringKeyId, LanguageId, StringTranslationId)
VALUES
    (0, 38, 0),
    (1, 38, 1),
    (2, 38, 2),
    (3, 38, 3),
    (4, 38, 4),
    (5, 38, 5),
    (6, 38, 6),
    (7, 38, 7),
    (1, 48, 8); -- added as example