본문 바로가기
● Data Visualization

Power BI에서 SharePoint/Teams 이미지 분할·재조립으로 동적 표시하는 방법

by DataFolio.lab 2026. 5. 22.
반응형

 

Power BI로 보고서를 만들다 보면, 단순한 숫자와 텍스트를 넘어 이미지까지 데이터 모델 안에서 제어하고 싶을 때가 있습니다. 특히 Teams에 업로드된 파일이 실제로는 SharePoint 문서 라이브러리에 저장된다는 점을 이용하면, 해당 이미지를 Power Query로 불러와 데이터처럼 다루는 구성이 가능합니다. 

 

문제는 여기서부터 시작됩니다. 이미지 파일을 그대로 모델에 넣기보다는 Base64 문자열로 바꿔서 리포트에서 표현하는 방식이 자주 사용되는데, 이 문자열 길이가 길어지면 Power Query나 Power BI 모델에서 잘리거나 처리에 실패할 수 있습니다. Power BI에서 하나의 텍스트 값은 약 32,766자 수준의 제한이 있다는 사례가 알려져 있고, 큰 이미지는 이 한계를 쉽게 넘길 수 있습니다.

 

그래서 실무에서는 긴 이미지 문자열을 한 번에 넣는 대신, 잘리지 않을 크기로 여러 조각으로 나누어 저장한 뒤, 보고서 화면에서 DAX로 다시 이어 붙여 이미지를 표시하는 우회 방식이 사용됩니다. 이 글에서는 그 과정을 처음부터 끝까지 연결된 흐름으로 설명드리겠습니다. 

Power BI에서 SharePoint/Teams 이미지 분할·재조립으로 동적 표시하는 방법

 

반응형

Teams 이미지가 왜 SharePoint에서 시작되는가

Microsoft Teams에 올린 파일은 별도의 전용 저장소에만 존재하는 것이 아니라, 대부분 SharePoint 문서 라이브러리에 저장됩니다. 따라서 Power BI에서 Teams 이미지를 가져오고 싶다면 실제 접근 지점은 SharePoint가 되는 경우가 많습니다. Power Query의 SharePoint.Files 또는 SharePoint.Contents를 이용하면 해당 사이트의 파일 목록과 Content 바이너리 값을 읽을 수 있습니다. 

Content 컬럼은 이미지의 실제 바이너리 데이터입니다. 이 값을 그대로는 리포트 비주얼에서 쉽게 다룰 수 없기 때문에, 일반적으로 텍스트 기반 포맷인 Base64 문자열로 변환해서 사용합니다. Power Query에서는 Binary.ToText([Content], BinaryEncoding.Base64) 함수로 이 작업을 수행할 수 있습니다. 

하지만 이미지가 조금만 커져도 Base64 문자열은 매우 길어집니다. 이때 한 셀에 통째로 적재하려고 하면 텍스트 길이 제한에 걸려 일부가 잘리거나, 이후 DAX에서 재활용할 수 없는 데이터가 되어버립니다. 그래서 처음부터 “분할 저장”을 전제로 설계하는 것이 중요합니다. 


전체 설계 구조를 먼저 이해해야 한다

이 방식은 단순히 문자열을 자르는 작업이 아니라, 데이터 모델 구조까지 함께 설계해야 안정적으로 동작합니다. 가장 기본적인 구조는 Images 테이블과 ImageParts 테이블을 분리하는 것입니다. 

Images 테이블에는 이미지 하나당 한 행이 들어갑니다. 예를 들어 ImageID, 파일명, 생성일, 작성자, 카테고리 같은 메타데이터를 저장합니다. 반면 ImageParts 테이블에는 같은 이미지의 Base64 문자열을 여러 조각으로 나눈 값이 들어갑니다. 즉 ImageID, PartIndex, PartText 형태입니다. 

이렇게 두 테이블을 나누는 이유는 단순합니다. 보고서에서 사용자는 이미지 이름, 날짜, 부서, 제품군 같은 차원 정보를 기준으로 필터링하고 싶어 합니다. 이때 Images 테이블이 중심이 되고, 선택된 ImageID에 연결된 여러 ImageParts를 DAX가 다시 합쳐 최종 이미지 문자열을 만드는 구조가 가장 자연스럽습니다. 


Power Query에서 해야 할 핵심 작업

Power Query 단계에서는 크게 네 가지를 처리합니다.

  • SharePoint에서 이미지 파일 가져오기
  • Content 바이너리를 Base64 문자열로 변환하기
  • 긴 문자열을 일정 길이로 분할하기
  • 분할된 결과를 행 단위 테이블로 확장하기

여기서 가장 중요한 포인트는 분할 길이입니다. 알려진 텍스트 제한보다 여유 있게 작게 잡는 것이 좋기 때문에, 보통 30,000자 정도로 자르는 구성이 안전합니다. 이렇게 하면 한 조각이 32,766자를 넘지 않아 모델 로드 시 잘릴 가능성을 낮출 수 있습니다. 

아래는 SharePoint에서 이미지를 불러오고, Base64로 바꾼 뒤, 30,000자 단위로 쪼개서 행으로 확장하는 Power Query M 코드 예시입니다. 환경에 맞게 SharePoint 사이트 주소만 바꿔서 사용할 수 있습니다.

let
    Source = SharePoint.Files("https://<your-tenant>.sharepoint.com/sites/<site>", [ApiVersion = 15]),
    Filtered = Table.SelectRows(Source, each Text.EndsWith([Name], ".jpg") or Text.EndsWith([Name], ".png")),
    AddedImageID = Table.AddIndexColumn(Filtered, "ImageID", 1, 1, Int64.Type),
    AddedBase64 = Table.AddColumn(AddedImageID, "Base64Full", each Binary.ToText([Content], BinaryEncoding.Base64), type text),
    SplitFunction = (txt as text, chunkSize as number) =>
        let
            len = Text.Length(txt),
            count = Number.RoundUp(len / chunkSize),
            idx = {0..count-1},
            idxTable = Table.FromList(idx, Splitter.SplitByNothing(), {"PartIndex"}),
            partTable = Table.AddColumn(
                idxTable,
                "PartText",
                each Text.Range(txt, [PartIndex] * chunkSize, chunkSize),
                type text
            )
        in
            partTable,
    AddedParts = Table.AddColumn(AddedBase64, "PartsTable", each SplitFunction([Base64Full], 30000)),
    ExpandedParts = Table.ExpandTableColumn(AddedParts, "PartsTable", {"PartIndex", "PartText"}, {"PartIndex", "PartText"}),
    SelectedColumns = Table.SelectColumns(ExpandedParts, {"ImageID", "Name", "Folder Path", "PartIndex", "PartText"})
in
    SelectedColumns

이 코드는 하나의 이미지 파일을 여러 개의 PartText 행으로 나누어 적재합니다. 예를 들어 하나의 이미지가 Base64 기준 95,000자라면, 대략 4개의 파트로 쪼개져 PartIndex = 0, 1, 2, 3 형태로 저장됩니다. 이 순서 정보가 나중에 DAX 재조립에서 매우 중요합니다. 


Images 테이블과 ImageParts 테이블은 분리해서 쓰는 편이 좋다

실무에서는 위 쿼리를 그대로 단일 테이블로 쓰기보다, 메타데이터용 Images 테이블과 파트 저장용 ImageParts 테이블로 나누는 편이 관리가 좋습니다. 이유는 모델 관계와 슬라이서 구성이 훨씬 단순해지기 때문입니다. 

예를 들어 Images 테이블은 아래 같은 구조를 갖게 할 수 있습니다.

Images
- ImageID
- Name
- Folder Path
- Created Date
- Category

그리고 ImageParts 테이블은 아래처럼 구성합니다.

ImageParts
- ImageID
- PartIndex
- PartText

이후 모델에서 Images[ImageID]ImageParts[ImageID] 를 1:N 관계로 연결합니다. 이렇게 하면 보고서에서 사용자가 특정 이미지나 제품군을 고르면, 해당 컨텍스트 안에서만 관련 파트들이 모여 최종 이미지가 생성됩니다. 즉 이미지도 일반 데이터처럼 필터 컨텍스트의 영향을 받게 됩니다. 


DAX에서 다시 합쳐 이미지로 만드는 방식

Power Query에서 문자열을 분할했다면, 이제 보고서 시점에는 다시 하나의 문자열로 합쳐야 합니다. 여기서 사용하는 함수가 CONCATENATEX 입니다. 이 함수는 특정 테이블의 여러 행을 순서대로 이어 붙이는 데 적합합니다. 

핵심은 두 가지입니다.

  • 반드시 PartIndex 기준으로 정렬해서 합쳐야 합니다.
  • 앞에 data:image/jpeg;base64, 또는 data:image/png;base64, 같은 접두사를 붙여야 이미지 URL로 인식됩니다.

다음은 가장 기본적인 DAX 예시입니다.

Display Image =
IF(
    HASONEVALUE(Images[ImageID]),
    "data:image/jpeg;base64," &
    CONCATENATEX(
        FILTER(
            ImageParts,
            ImageParts[ImageID] = SELECTEDVALUE(Images[ImageID])
        ),
        ImageParts[PartText],
        "",
        ImageParts[PartIndex],
        ASC
    ),
    BLANK()
)

이 Measure는 현재 필터 컨텍스트에서 하나의 ImageID만 선택되었을 때만 동작합니다. 선택된 이미지의 모든 PartTextPartIndex 오름차순으로 합친 뒤, 맨 앞에 Base64 이미지 접두사를 붙여 최종 문자열을 만듭니다. 

그 다음 이 Measure 또는 관련 필드를 비주얼에서 사용할 때 데이터 카테고리를 Image URL로 지정하면, Power BI가 해당 문자열을 이미지처럼 렌더링합니다. 이때 슬라이서나 표 선택에 따라 SELECTEDVALUE(Images[ImageID])가 바뀌므로, 이미지도 함께 동적으로 바뀌게 됩니다. 


왜 화면에서 동적 필터링이 가능한가

많은 분들이 여기서 헷갈려 하시는 부분이 있습니다. “문자열을 이어 붙였을 뿐인데 왜 동적 필터링이 되나?” 하는 점입니다. 이유는 DAX Measure가 정적인 값이 아니라, 현재 보고서의 필터 컨텍스트를 기준으로 매번 다시 계산되기 때문입니다. 

예를 들어 사용자가 제품 A를 선택하면 Images 테이블에서 해당 제품과 연결된 ImageID만 남습니다. 그러면 Display Image Measure 안의 SELECTEDVALUE(Images[ImageID])도 제품 A의 이미지 ID로 바뀌고, 그 결과 ImageParts에서 해당 ID의 조각들만 모아 다시 조합합니다. 결국 보고서의 선택 상태가 바뀔 때마다 최종 Base64 문자열도 바뀌는 구조입니다. 

즉 이 방식의 본질은 “이미지를 데이터처럼 모델링해서 필터 컨텍스트 안에서 계산한다”는 데 있습니다. 그래서 단순한 정적 이미지 삽입과 달리, 특정 고객·상품·부서·일자 선택에 따라 이미지도 함께 움직이는 리포트를 만들 수 있습니다. 


실제 구현에서 자주 부딪히는 문제

첫 번째는 성능입니다. Base64 문자열은 길이가 길고, CONCATENATEX는 이를 매번 이어 붙여야 하므로 이미지 수가 많거나 페이지에 여러 개를 동시에 띄우면 렌더링이 느려질 수 있습니다. 이런 방식은 소량의 이미지에는 충분히 쓸 수 있지만, 대규모 이미지 저장소를 Power BI 모델 안에 넣는 구조로 확장하는 것은 부담이 큽니다. 

두 번째는 DAX 텍스트 한계입니다. 알려진 사례처럼 Power BI는 매우 긴 문자열 처리에서 오류를 낼 수 있으며, DAX 함수가 허용하는 최대 텍스트 길이도 무한정 크지 않습니다. 따라서 원본 이미지를 적절히 리사이즈하거나 압축해 사용하는 것이 좋습니다. 특히 고해상도 PNG는 Base64 길이가 급격히 길어질 수 있어 주의가 필요합니다. 

세 번째는 파일 포맷입니다. JPEG와 PNG는 접두사가 다르므로, 하나의 테이블에 여러 확장자가 섞여 있다면 MIME 타입도 함께 저장해 두는 것이 안전합니다. 예를 들면 image/jpeg, image/png 값을 메타데이터로 두고 DAX에서 동적으로 접두사를 붙이는 방식입니다. 

아래처럼 확장자에 따라 접두사를 바꾸는 DAX도 사용할 수 있습니다.

Display Image Dynamic =
VAR SelectedImageID = SELECTEDVALUE(Images[ImageID])
VAR MimeType = SELECTEDVALUE(Images[MimeType])
VAR Base64Text =
    CONCATENATEX(
        FILTER(
            ImageParts,
            ImageParts[ImageID] = SelectedImageID
        ),
        ImageParts[PartText],
        "",
        ImageParts[PartIndex],
        ASC
    )
RETURN
IF(
    NOT ISBLANK(SelectedImageID) && NOT ISBLANK(MimeType),
    "data:" & MimeType & ";base64," & Base64Text,
    BLANK()
)

이 방식은 이미지 파일 형식이 혼합된 환경에서 더 실용적입니다. 


압축까지 고려할 수는 있지만, 복잡도가 올라간다

일부 구현에서는 Base64로 바로 저장하지 않고, 먼저 바이너리 데이터를 압축한 다음 텍스트로 바꾸는 방식도 검토합니다. 이론적으로는 저장 길이를 줄이는 데 도움이 될 수 있습니다. Power Query에는 Binary.FromText, Binary.ToText 같은 함수와 압축 관련 함수가 존재합니다. 

하지만 이 접근은 실제 Power BI 모델 안에서 재복원 과정을 단순하게 처리하기 어렵다는 문제가 있습니다. Power Query 단계에서라면 압축과 해제가 가능하지만, DAX는 바이너리 복원 로직을 다루는 데 적합하지 않습니다. 그래서 대부분의 실무 구현은 “압축 없이 Base64로 변환 → 안전한 길이로 분할 → DAX로 재조립”이라는 단순한 구조를 택합니다. 복잡도를 낮추는 것이 유지보수에 유리하기 때문입니다. 


이 방식이 맞는 경우와 아닌 경우

이 방법은 분명히 유용하지만, 모든 상황의 정답은 아닙니다. 이미지가 많지 않고, 외부 URL 방식이 정책상 불가능하며, 보고서 내에서 필터에 따라 이미지를 바꿔야 할 때는 상당히 효과적입니다. 특히 제품 이미지, 설비 사진, 현장 점검 스냅샷처럼 “몇 장 안 되지만 컨텍스트에 따라 바뀌어야 하는 이미지”에는 잘 맞습니다.

반대로 이미지 건수가 많거나 해상도가 높고, 새로고침 성능과 모델 크기가 중요하다면 외부 스토리지 참조가 더 적합합니다. 실제로 커뮤니티와 실무 블로그에서도 대형 이미지나 다수 이미지를 Power BI 모델 내부에 직접 넣는 방식보다는, Azure Blob Storage나 SharePoint 파일 URL을 활용하는 방식을 더 안정적인 대안으로 소개하는 경우가 많습니다. 

즉 이 패턴은 “우회 기술”에 가깝습니다. 가능해서 쓰는 방법이지, 언제나 가장 좋은 아키텍처라고 보기는 어렵습니다. 그래서 구현 전에 이미지 개수, 해상도, 보안 정책, 새로고침 주기, 사용자 수를 함께 고려해야 합니다. 


Power BI에서 Teams(SharePoint)에 있는 이미지를 데이터처럼 다루고, 리포트의 슬라이서나 선택 상태에 따라 동적으로 바뀌게 만들고 싶다면, 이미지 바이너리를 Base64 문자열로 변환한 뒤 이를 잘리지 않을 크기로 분할 저장하고, 최종적으로 DAX에서 다시 이어 붙여 이미지 URL 형태로 표현하는 방식이 현실적인 해법이 될 수 있습니다. 이 구조의 핵심은 단순한 문자열 처리 기술이 아니라, 이미지를 ImagesImageParts라는 모델 구조 안에 넣고 필터 컨텍스트 기반으로 재조립하는 설계에 있습니다. 

다만 이 방식은 Power BI의 텍스트 길이 제한과 성능 한계를 우회하는 목적에 가깝기 때문에, 이미지 크기와 수량이 커질수록 유지보수 부담이 커집니다. 따라서 소규모·보안 중심·동적 전환이 중요한 리포트에는 적합하지만, 대규모 이미지 처리에는 외부 URL 기반 구조가 더 바람직합니다. 

정리하면 실무 적용 순서는 다음처럼 가져가는 것이 좋습니다. 첫째, 외부 URL 참조가 가능한지 검토합니다. 둘째, 불가능하다면 Base64 분할 저장 + DAX 재조립 방식을 적용합니다. 셋째, 구현 후에는 반드시 실제 해상도의 이미지로 성능과 렌더링을 검증해야 합니다. 이 순서로 접근하면 불필요한 시행착오를 줄일 수 있습니다. 


구현 순서 요약

  • SharePoint에서 Teams 이미지의 Content 바이너리를 읽습니다.
  • Power Query에서 Base64 문자열로 변환합니다.
  • 문자열이 길면 30,000자 단위로 분할합니다.
  • ImagesImageParts 테이블로 나누어 모델링합니다.
  • CONCATENATEX로 DAX Measure를 만들어 다시 합칩니다.
  • data:image/...;base64, 접두사를 붙여 이미지 URL로 만듭니다.
  • 비주얼에서 필터 컨텍스트에 따라 동적으로 이미지를 표시합니다.
반응형

놓치면 아쉬운 추천 글, 함께 읽어보세요!

  • 추천 글을 불러오는 중입니다...