2014-11-09 4 views
0

데이터베이스 내 여러 테이블에서 데이터를 검색하는 가장 좋은 방법에 대해 궁금한데. 슬프게도 실제로 할 수있는 올바른 방법이 무엇인지 이해하는 데 도움이되는 것을 찾을 수 없었습니다.개체 당 복수 선택을 처리하는 가장 좋은 방법

콘텐츠 페이지가 ContentPages 인 테이블이 있다고 가정 해 보겠습니다. (페이지에 대해 무엇 나 또한 최선을 설명하는 태그를 저장하는 담당 테이블 ContentPagesTags을 가지고 테이블 ContentPages뿐만 아니라,

PageID 
PageTitle 
PageContent 

이제이 테이블에는 다음과 같은 필드로 구성 이 바로 웹 사이트처럼 - stackoverflow, 질문에 특정 태그를 적용 할 수있는 곳). ContentPagesTags 테이블은 다음 필드로 구성

PageID 
TagID 

ContentPagesTags 표는 페이지 및 첨부 된 태그 사이의 관계를 담당하고있다. TagID 필드는 마지막 테이블 PageTags에서 가져온 것으로, 여기에는 콘텐츠 페이지에 적용 할 수있는 모든 태그가 저장됩니다. 마지막 테이블 구조는 다음과 같습니다.

TagID 
TagTitle 

꽤 많이 있습니다. 이제, 언제든지 ContentPage 개체를 검색하여 데이터 테이블에서 필요한 정보를 추출 할 때마다 관련된 모든 태그의 배열을로드하려고합니다. 기본적으로 내가 지금까지 해왔 던 것을 내 목표 달성하기 위해 두 개의 쿼리를 실행 :

SELECT * FROM ContentPages 

을 그리고 다음 ContentPage 개체를 반환하기 전에 각 페이지마다 다음 쿼리를 실행 :

SELECT * FROM ContentPagesTags WHERE PageID = @PageID 

PageID은 현재 페이지의 ID이며 객체를 구축 중입니다.

모든 내용을 요약하기 위해 필요한 정보를 모두 검색하기 위해 각 콘텐츠 페이지 개체 당 두 개의 쿼리를 실행하고 있습니다. 이 특정 예제에서는 하나의 테이블에서 정보를 추출하기 위해 수행 한 작업 만 보여 주었지만 시간이 지나면 필자는 필요한 정보를 얻기 위해 각 개체마다 여러 쿼리를 실행하고 있습니다 (예 : 페이지 태그 이외의 또한 페이지 주석, 페이지 초안 및 필요한 것으로 생각되는 추가 정보를 선택하려는 경우). 결과적으로 여러 명령을 쿼리 할 수있게되어 내 웹 응용 프로그램이 예상보다 훨씬 느리게 실행됩니다.

나는 그러한 작업을 처리하는 더 빠르고, 더 효율적인 방법이 있다고 확신한다. 다른 SQL 선택에 관한 나의 지식을 향상시키고 각 객체마다 다중 선택을하지 않고도 사용자가 요청한 엄청난 양의 데이터를 처리하는 방법에 대해이 주제를 다룰 때 기쁜 마음입니다.

답변

1

내가 원래 질문의 댓글에서 묻는 질문에 대한 명확한 설명을 기다리는 동안, 나는 적어도 이런 말을 할 수 있습니다 순수한 "쿼리 성능"에서

스탠드 점을,이 정보의 측면에서 서로 다른 것입니다 PageID 관계 외부의 서로 다른 (예 : [Tags] 및 [Comments] 테이블과는 관련이 없지만 이러한 여분의 테이블 사이의 행 단위 기준은 아닙니다.

  • 당신이 다시 [ContentPages] 테이블의 모든 서브 테이블 사이의 PageID 외국 키 입력이 있는지 확인합니다 : 따라서, 외부 쿼리 수준에서 효율성을 얻을 수 그렇게하는 것이 더 아무것도 없다.

  • 각 서브 테이블의 PageID 필드에 색인이 있는지 확인하십시오 (클러스터되지 않은 것이 좋고 사용 패턴에 따라 FILLFACTOR가 90 - 100이어야합니다).

  • 색인 유지 관리를 정기적으로 수행하십시오. 필요한 경우 적어도 REORGANIZE를 약간 자주하고 REBUILD하십시오.

  • 테이블이 적절하게 모델링되어 있는지 확인합니다 : 즉 1의 값을 저장하기 위해 INT를 사용하지 않는 (적절한 데이터 유형을 사용 - 이제까지 10 최악의 50 이상 가지 않을 것 (10), 그냥 때문에 응용 프로그램 계층에서 int을 쉽게 코드화 할 수 있으며 모든 PK 또는 클러스터 된 색인에 대해 UNIQUEIDENTIFIER를 사용하지 않아도됩니다. 진지하게 : 데이터 모델링 (데이터 유형 및 구조)이 좋지 않으면 일부 또는 모든 쿼리의 전반적인 성능이 저하되어 인덱스 또는 다른 기능이나 트릭이 도움이되지 않을 수 있습니다..

  • 은 엔터프라이즈 에디션이있는 경우, 측면에서 (특히 정말 큰 것 같은 [ContentPagesTags] 것처럼 [Comments] 또는 큰 연관 테이블과 같은 테이블, 행 또는 페이지 압축 (인덱스의 기능입니다)의 사용을 고려한다 행 개수)을 사용하면 더 작은 고정 길이 데이터 유형을 사용하여 더 큰 유형으로 선언 된 값을 저장할 수 있습니다. 의미 : TagID에 대해 INT (4 바이트) 또는 BIGINT (8 바이트)가있는 경우 IDENTITY 값이 SMALLINT 데이터 유형에서 사용 된 2 바이트보다 커야하고 잠시 동안 초과해야합니다. 그것은 SMALLINT이었다 것처럼 INT 데이터 유형, 하지만 SQL 서버 4 바이트, 2 바이트의 공간 1,005의 값을 저장한다. 기본적으로 행 크기를 줄이면 각 8k 데이터 페이지 (SQL Server가 데이터를 읽고 저장하는 방식)에 더 많은 행을 넣을 수 있으므로 실제 IO가 줄어들고 메모리에 캐시 된 데이터 페이지를보다 잘 활용할 수 있습니다.

  • 동시성이 문제가되는 경우 Snapshot Isolation을 확인하십시오.

이제 응용 프로그램/프로세스 관점에서 연결/호출 수를 줄이려고합니다. 정보를 CSV 또는 XML 필드에 병합하여 각 PageID/PageContent 행과 1 대 1로 끝낼 수는 있지만 실제로는 RDBMS에서 가장 단순한 형식으로 데이터를 제공하는 것보다 효율적이지 않습니다. INT 값을 문자열로 변환 한 후 더 큰 CSV 또는 XML 문자열로 병합하는 데는 추가 시간을 들여야 만 앱 계층에서 패키지를 푸는 데 더 많은 시간을 할애 할 수 있습니다.

대신 여러 개의 결과 집합을 반환하여 호출 횟수를 줄이고 작업 시간/복잡성을 증가시키지 않을 수 있습니다. 예 :

CREATE PROCEDURE GetPageData 
(
    @PageID INT 
) 
AS 
SET NOCOUNT ON; 

SELECT fields 
FROM [Page] pg 
WHERE pg.PageID = @PageID; 

SELECT tag.TagID, 
     tag.TagTitle 
FROM [PageTags] tag 
INNER JOIN [ContentPagesTags] cpt 
     ON cpt.TagID = tag.TagID 
WHERE cpt.PageID = @PageID; 

SELECT cmt.CommentID, 
     cmt.Comment 
     cmd.CommentCreatedOn 
FROM [PageComments] cmt 
WHERE cmt.PageID = @PageID 
ORDER BY cmt.CommentCreatedOn ASC; 

SqlDataReader.NextResult()을 통해 결과 집합을 순환합니다.


는하지만, 단지 기록을 위해, 난 정말이 정보에 대한 세 가지 별도의 "수"저장 프로 시저를 호출하면 정말 그렇게 많은 각 페이지를 작성하는 작업의 총 시간을 증가시킬 것이라고 생각하지 않습니다. 현실보다 더 많은 인식/이론 인 문제를 해결하지 못하게하기 위해 두 가지 방법의 성능 테스트를 먼저 수행하는 것이 좋습니다.

편집 :
주 :

  • 여러 결과 집합 (안 SQL 서버 M.A.R.S. 기능 "다중 활성 결과 집합") 저장 프로 시저를 특정하지 않습니다. 당신은뿐만 아니라 SqlCommand를 통해 여러 매개 변수화 된 SELECT 문을 발행 할 수 있습니다 :

    string _Query = @" 
    SELECT fields 
    FROM [Page] pg 
    WHERE pg.PageID = @PageID; 
    
    SELECT tag.TagID, 
         tag.TagTitle 
    FROM [PageTags] tag 
    INNER JOIN [ContentPagesTags] cpt 
         ON cpt.TagID = tag.TagID 
    WHERE cpt.PageID = @PageID; 
    
    --assume SELECT statement as shown above for [PageComments]"; 
    
    SqlCommand _Command = new SqlCommand(_Query, _SomeSqlConnection); 
    _Command.CommandType = CommandType.Text; 
    
    SqlParameter _ParamPageID = new SqlParameter("@PageID", SqlDbType.Int); 
    _ParamPageID.Value = _PageID; 
    _Command.Parameters.Add(_ParamPageID); 
    
  • 당신이 SqlDataReader.Read()를 사용하는 경우는 다음과 같은 것이다. 옵션을 표시하기 위해 여러 가지 방법으로 값을 얻으려고 의도적으로 표시하고 있음에 유의하십시오. 또한 태그 및/또는 주석의 수는 CPU 관점에서 볼 때 실제로는 관련이 없습니다. AJAX를 사용하여 한 번에 하나의 항목 만 작성하고 전체 세트를 메모리로 가져 오지 않는 한, 더 많은 메모리와 같지만 그럴 수는 없습니다. 단 하나의 페이지에 충분한 태그와 주석이있는 것은 의심 스럽습니다. 심지어 눈에 띄는).

    // assume the code block above is right here 
    
    SqlDataReader _Reader; 
    _Reader = _Command.ExecuteReader(); 
    
    if (_Reader.HasRows) 
    { 
        // only 1 row returned from [ContentPages] table 
        _Reader.Read(); 
        PageObject.Title = _Reader["PageTitle"].ToString(); 
        PageObject.Content = _Reader["PageContent"].ToString(); 
        PageObject.ModifiedOn = (DateTime)_Reader["LastModifiedDate"]; 
    
        _Reader.NextResult(); // move to next result set 
        while (_Reader.Read()) // retrieve 0 - n rows 
        { 
         TagCollection.Add((int)_Reader["TagID"], _Reader["TagTitle"].ToString()); 
        } 
    
        _Reader.NextResult(); // move to next result set 
        while (_Reader.Read()) // retrieve 0 - n rows 
        { 
         CommentCollection.Add(new PageComment(
           _Reader.GetInt32(0), 
           _Reader.GetString(1), 
           _Reader.GetDateTime(2) 
            )); 
        } 
    } 
    else 
    { 
        throw new Exception("PageID " + _PageID.ToString() 
           + " does not exist. What were you thinking??!?"); 
    } 
    
  • 또한 DataSet에 여러 결과 집합을로드 할 수 있습니다 각 결과 세트는 자신의 DataTable 될 것입니다. 자세한 내용은 솔직히 DataSet.Load

    // assume the code block 2 blocks above is right here 
    
    SqlDataReader _Reader; 
    _Reader = _Command.ExecuteReader(); 
    DataSet _Results = new DataSet(); 
    
    if (_Reader.HasRows) 
    { 
        _Results.Load(_Reader, LoadOption.Upsert, "Content", "Tags", "Comments"); 
    } 
    else 
    { 
        throw new Exception("PageID " + _PageID.ToString() 
           + " does not exist. What were you thinking??!?"); 
    } 
    
+1

이전에 저장 프로 시저를 사용할 기회가 없었습니다. 당신의 대답은 타자를 치고 설명하는 것이지만, 실종 된 것이 하나 있습니다. 예를 들어 3 개의 태그와 3 개의 서로 다른 주석을 사용하여 한 페이지를 선택할 때 데이터 행이 어떻게 표시되는지에 대한 짧은 예를 보여주기 위해 원래의 대답을 변경할 수 있다면 매우 기쁩니다. 지금까지 SqlDataReader를 통해 행을 가져 왔을 때 다음 페이지로 이동하기위한 것이었지만 페이지 주석과 태그를 통해 가져 오기를 제안하는 것으로 나타났습니다. 네가 조금이라도 전시 할 수 있다면 너는 아주 친절 하리라. –

+1

이전 의견에 대한 제의 요청에도 불구하고 귀하의 의견에 감사드립니다. –

+1

@ TommyNaidich : 환영합니다. 예, 업데이트 할 수 있지만 여러 가지 결과 집합 (SQL Server의 M.A.R.S. 기능이 아닌 다중 ** 활성 ** 결과 집합)은 저장 프로 시저에만 해당되지 않습니다. SqlCommand를 통해 여러 개의 매개 변수화 된 SELECT 문을 발행 할 수도 있습니다 (예 : "SELECT 1; SELECT 2;"는 두 개의 결과 집합 임). DataSet을 사용하지 않지만 이와 같은 쿼리에서 DataSet을로드하면 결과 집합당 하나의 DataTable을 얻을 수 있습니다. –

1

태그를 구분 목록에 넣는 것이 좋습니다.당신은 다음과 같은 쿼리와 SQL 서버에서이 작업을 수행 할 수 있습니다

select cp.*, 
     stuff((select ', ' + TagTitle 
       from ContentPagesTags cpt join 
        PageTags pt 
        on cpt.TagId = pt.TagId 
       where cpt.PageId = cp.PageId 
       for xml path ('') 
      ), 1, 2, '') as Tags 
from ContentPages cp; 

문자열 연결의 구문은,하여야한다 내가 직관적 인 미만 말한다. 다른 데이터베이스에는 이에 대한 유용한 기능이 있습니다 (예 : listagg()group_concat()). 그러나 특히 적절한 색인 (ContentPagesTags(PageId, TagId) 포함)이있는 경우 실적이 상당히 적당합니다.

+0

에 대한 MSDN 페이지를 참조하시기 바랍니다이 방법은 약간의 전문가가 아닌 것 같다. 내가 올바르게 이해했다면, 당신이 제안한 쿼리는 XML 라인으로 만들어진 문자열을 제공합니다. 이것은 우리의 일을 처리하는 실제 방법을 알지 못하는 동안 우리가하는 것처럼 보입니다. 또한이 예제의 짧은 버전이 있습니까? 앞에서 설명한 것처럼 필자가 필요로하는 모든 정보를 선택하기 위해 예제에서 코드를 한 번만 사용하지 않고 여러 번 사용해야합니다. 이것은 정말 지저분해질 수 있습니다. –

+0

이것은 사용자가 직접 함수를 작성하지 않는 한 SQL Server에서 문자열 연결을 수행하는 방법입니다. 당신이 그것을 좋아하지 않아 Microsoft와 함께 할 수 있습니다. –

+0

나는 이것이 잘못된 길로 나온 것이라고 믿습니다. 나는 당신의 대답에 만족스럽지는 않지만,이 목표를 달성하기위한 더 쉬운 방법이 있는지 알고 싶었습니다.나는 마지막 주석에서 언급했듯이 문자열 연결을 만드는 방법을 특별히 요구하지 않았지만 내가 만든 각 객체마다 여러 행을 가져 오려고했습니다. 결국, 내가 이미 언급했듯이, 그것은 많은 라인을 요약하고 최종 SQL 쿼리는 꽤 뚱뚱하고 방대하게 돌아설 것입니다. 더 쉬운 (더 짧은) 방법이 있는지 간단히 알고 싶을뿐입니다. –

관련 문제