RDBMS가 조인 된 테이블을 중첩 형식으로 반환하지 않는 이유는 무엇입니까?


14

예를 들어, 사용자와 모든 전화 번호 및 이메일 주소를 가져오고 싶다고 가정하십시오. 전화 번호와 이메일은 별도의 테이블에 저장되며 한 명의 사용자는 많은 전화 / 이메일에 접속합니다. 나는 이것을 아주 쉽게 할 수있다.

SELECT * FROM users user 
    LEFT JOIN emails email ON email.user_id=user.id
    LEFT JOIN phones phone ON phone.user_id=user.id

이 문제는 사용자 이름, DOB, 선호하는 색상 및 사용자 테이블에 저장된 다른 모든 정보를 각 레코드 ( 이메일 전화 레코드) 에 대해 반복해서 반환하므로 대역폭을 소모하고 속도가 느려질 수 있습니다 결과를 낮추십시오.

각 사용자에 대해 단일 행을 반환하면 레코드 목록 에 전자 ​​메일 목록 과 전화 목록 이 있으면 더 좋지 않습니까? 데이터 작업도 훨씬 쉬워집니다.

LINQ 또는 다른 프레임 워크를 사용하여 이와 같은 결과를 얻을 수 있다는 것을 알고 있지만 관계형 데이터베이스의 기본 디자인에는 약한 것 같습니다.

우리는 NoSQL을 사용하여이 문제를 해결할 수 있지만 중간 정도의 근거는 없어야합니까?

뭔가 빠졌습니까? 왜 존재하지 않습니까?

* 예, 이런 식으로 설계되었습니다. 알겠습니다 작업하기 쉬운 대안이없는 이유가 궁금합니다. SQL은 작업을 계속 수행 할 수 있지만 직교 곱 대신 중첩 형식으로 데이터를 반환하는 약간의 사후 처리를 수행하기 위해 키워드를 추가 할 수 있습니다.

나는 이것이 선택한 스크립팅 언어로 수행 될 수 있다는 것을 알고 있지만 SQL 서버는 중복 데이터 (아래 예)를 보내거나와 같은 여러 쿼리를 발행해야합니다 SELECT email FROM emails WHERE user_id IN (/* result of first query */).


MySQL이 이와 비슷한 것을 반환하는 대신 :

[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "johnsmith45@gmail.com",
    },
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "john@smithsunite.com",
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "email": "originaljane@deerclan.com",
    }
]

그런 다음 클라이언트 측에서 고유 한 식별자를 그룹화해야하므로 클라이언트 측에서 원하는 방식으로 결과 세트를 다시 포맷하기 위해 클라이언트 측을 가져와야합니다.

[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "emails": ["johnsmith45@gmail.com", "john@smithsunite.com"]
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "emails": ["originaljane@deerclan.com"],
    }
]

또는 사용자 3 명, 이메일 1 명, 전화 번호 1 명으로 3 가지 쿼리를 실행할 수 있지만 이메일 및 전화 번호 결과 세트에는 user_id가 포함되어 있어야 사용자와 다시 일치시킬 수 있습니다. 이전에 가져 왔습니다. 다시, 중복 데이터 및 불필요한 후 처리.


6
SQL을 Microsoft Excel에서와 같이 스프레드 시트로 생각한 다음 내부 셀이 포함 된 셀 값을 작성하는 방법을 찾으십시오. 더 이상 스프레드 시트로 작동하지 않습니다. 당신이 찾고있는 것은 트리 구조이지만 더 이상 스프레드 시트의 이점을 얻지 못합니다 (즉, 트리의 열을 합계 할 수 없음). 트리 구조는 사람이 읽을 수있는 보고서를 만들지 않습니다.
Reactgular

54
SQL은 데이터를 반환하는 데 나쁘지 않으며 원하는 것을 쿼리하는 데 나쁩니다. 일반적으로 널리 사용되는 도구가 일반적인 사용 사례에서 버그가 있거나 파손되었다고 생각하면 문제가 있습니다.
Sean McSomething

12
@SeanMcSomething 너무 아파서, 나는 그것을 더 잘 말할 수 없었습니다.
WernerCD

5
이것은 좋은 질문입니다. "이것이 그런 방식입니다"라는 답변에는 요점이 없습니다. 포함 된 행 컬렉션이있는 행을 반환 할 수없는 이유무엇 입니까?
Chris Pitman

8
@SeanMcSomething : 널리 사용되는 도구가 C ++ 또는 PHP가 아니라면 아마도 맞을 것입니다. ;)
Mason Wheeler

답변:


11

관계형 데이터베이스의 장에서 모든 행과 열을 자세히 살펴보십시오. 이것이 관계형 데이터베이스가 작업하기에 최적화 된 구조입니다. 커서 는 한 번에 개별 행에서 작동합니다. 일부 작업은 임시 테이블을 생성합니다 (다시 행과 열이어야 함).

행만 작업하고 행만 반환함으로써 시스템은 메모리 및 네트워크 트래픽을보다 잘 처리 할 수 ​​있습니다.

언급했듯이 특정 최적화 (인덱스, 조인, 공용체 등)를 수행 할 수 있습니다.

중첩 된 트리 구조를 원한다면 모든 데이터를 한 번에 가져와야 합니다 . 데이터베이스 측의 커서에 대한 최적화는 사라졌습니다. 마찬가지로, 네트워크를 통한 트래픽은 하나의 큰 버스트가되어 한 줄씩 느린 세류보다 훨씬 오래 걸릴 수 있습니다 (이것은 때때로 오늘날 웹 세계에서 손실되는 것입니다).

모든 언어에는 그 안에 배열이 있습니다. 이것들은 다루고 다루기 쉬운 것들입니다. 매우 원시적 인 구조를 사용함으로써 데이터베이스와 프로그램 간의 드라이버 (어떤 언어에 상관없이)가 일반적인 방식으로 작동 할 수 있습니다. 나무를 추가하기 시작하면 언어 구조가 더 복잡해지고 횡단하기가 더 어려워집니다.

프로그래밍 언어가 반환 된 행을 다른 구조로 변환하는 것은 어렵지 않습니다. 트리 또는 해시 세트로 만들거나 반복 할 수있는 행 목록으로 남겨 두십시오.

여기에도 역사가 있습니다. 구조화 된 데이터 전송은 예전에는 추악한 일이었습니다. EDI 형식을보고 원하는 정보를 얻으십시오. 또한 나무는 재귀를 의미합니다. 일부 언어는 지원하지 않습니다 (이전의 가장 중요한 두 언어는 재귀를 지원 하지 않았습니다. 재귀는 F90 과 COBOL 시대가 될 때까지 포트란에 입력 되지 않았습니다).

오늘날의 언어는 재귀 및 고급 데이터 형식을 지원하지만 실제로 변경해야 할 이유는 없습니다. 그들은 일하고 잘 작동합니다. 사람 하는 일을 변경하면되는 NoSQL 데이터베이스입니다. 문서 기반 트리에서 문서에 트리를 저장할 수 있습니다. LDAP (실제로 오래된 것)는 트리 기반 시스템이기도합니다 (아마도 당신이 추구하는 것이 아닐 수도 있습니다). 아마도 nosql 데이터베이스의 다음 항목은 쿼리를 json 객체로 반환하는 것일 것입니다.

그러나 '오래된'관계형 데이터베이스는 행을 사용하여 잘 작동하고 모든 것이 문제 나 번역없이 대화 할 수 있기 때문에 행으로 작업하고 있습니다.

  1. 프로토콜 설계에서 추가 할 항목이 없을 때가 아니라 제거 할 항목이 없을 때 완벽에 도달했습니다.

RFC 1925 부터 -12 가지 네트워킹 진실


"중첩 된 트리 구조를 원한다면 모든 데이터를 한 번에 가져와야합니다. 데이터베이스 측의 커서에 대한 최적화는 사라졌습니다." -그건 사실이 아닙니다. 몇 개의 커서를 유지해야합니다. 하나는 기본 테이블 용이고 다른 하나는 결합 된 각 테이블마다 하나씩입니다. 인터페이스에 따라 하나의 행과 모든 조인 된 테이블을 하나의 청크 (부분적으로 스트리밍)로 반환하거나 반복을 시작할 때까지 하위 트리를 스트리밍하거나 쿼리하지 않을 수도 있습니다. 그러나 그렇습니다.
mpen

3
모든 현대 언어에는 일종의 트리 클래스가 있어야합니다. 그리고 그것을 처리하는 것은 운전자에게 달려 있지 않습니까? 나는 SQL 사람들이 여전히 공통 형식을 디자인해야한다고 생각합니다 (그에 대해 많이 몰라). 그래도 가져 오는 것은 조인으로 1 개의 쿼리를 보내고 각 행 (N 번째 행 만 변경되는 사용자 정보) 또는 1 개의 쿼리 (사용자)가 중복 된 데이터를 다시 필터링하여 필터링해야한다는 것입니다 , 결과를 반복 한 다음 각 레코드에 대해 두 개의 쿼리 (이메일, 전화)를 더 보내 필요한 정보를 가져옵니다. 어느 쪽이든 방법은 낭비되는 것 같습니다.
mpen

51

조인으로 정의 된 데카르트 제품이 포함 된 단일 레코드 세트가 요청한대로 정확하게 반환됩니다. 정확하게 원하는 시나리오가 많이 있습니다. 따라서 SQL이 나쁜 결과를 낳고 (따라서 변경하면 더 좋을 것이라고 암시) 실제로 많은 쿼리를 망칠 수 있습니다.

겪고있는 것을 " 객체 / 관계 임피던스 불일치 "라고합니다. 이는 객체 지향 데이터 모델과 관계형 데이터 모델이 근본적으로 몇 가지면에서 다르다는 점에서 발생하는 기술적 어려움입니다. LINQ 및 기타 프레임 워크 (ORM, Object / Relational Mapper라고도 함)는 마술처럼 "이 문제를 해결하지 않습니다". 그들은 단지 다른 쿼리를 발행합니다. SQL에서도 가능합니다. 내가하는 방법은 다음과 같습니다.

SELECT * FROM users user where [criteria here]

사용자 목록을 반복하고 ID 목록을 작성하십시오.

SELECT * from EMAILS where user_id in (list of IDs here)
SELECT * from PHONES where user_id in (list of IDs here)

그런 다음 가입하는 클라이언트 측을 수행합니다. 이것이 LINQ와 다른 프레임 워크가하는 방식입니다. 실제 마술은 없습니다. 추상화 계층 일뿐입니다.


14
"정확하게 요청한 내용"에 +1 우리는 기술을 효과적으로 사용하는 방법을 배워야한다는 결론보다는 기술에 문제가 있다는 결론에 너무 자주 넘어갑니다.
Matt

1
Hibernate는 eager 페치 모드가 해당 콜렉션에 사용될 때 단일 엔티티에서 루트 엔티티 및 특정 콜렉션을 검색합니다 . 이 경우 메모리의 루트 엔터티 속성을 줄입니다. 다른 ORM도 똑같이 할 수 있습니다.
Mike Partridge

3
실제로 이것은 관계형 모델을 비난하지 않습니다. 중첩 된 관계에 매우 잘 대처합니다. 감사합니다. 이것은 초기 SQL 초기 버전의 구현 버그입니다. 나는 더 최신 버전이 그것을 추가했다고 생각합니다.
John Nilsson

8
이것이 물체 관계형 임피던스의 예라고 확신합니까? 관계형 모델이 OP의 개념적 데이터 모델과 완벽하게 일치하는 것 같습니다. 각 사용자는 0 개, 하나 이상의 전자 메일 주소 목록과 연결되어 있습니다. 이 모델은 OO 패러다임에서도 완벽하게 사용할 수 있습니다 (집계 : 사용자 개체에 전자 메일 모음이 있음). 데이터베이스를 쿼리하는 데 사용되는 기술에 제한이 있으며, 이는 구현 세부 사항입니다. 계층 적 데이터를 반환하는 쿼리 기술이 있습니다. 예를 들어 .Net의 계층 적 DataSet
MarkJ

@ MarkJ 당신은 답변으로 작성해야합니다.
Mr.Mindor

12

내장 함수를 사용하여 레코드를 함께 연결할 수 있습니다. MySQL에서는 GROUP_CONCAT()함수를 사용할 수 있고 Oracle에서는 LISTAGG()함수를 사용할 수 있습니다 .

다음은 MySQL에서 쿼리가 어떻게 보이는지에 대한 샘플입니다.

SELECT user.*, 
    (SELECT GROUP_CONCAT(DISTINCT emailAddy) FROM emails email WHERE email.user_id = user.id
    ) AS EmailAddresses,
    (SELECT GROUP_CONCAT(DISTINCT phoneNumber) FROM phones phone WHERE phone.user_id = user.id
    ) AS PhoneNumbers
FROM users user 

이것은 다음과 같은 것을 반환합니다

username    department       EmailAddresses                        PhoneNumbers
Tim_Burton  Human Resources  hr@m.com, tb@me.com, nunya@what.com   231-123-1234, 231-123-1235

이것은 OP가 시도하는 것에 가장 가까운 솔루션 (SQL)입니다. 그는 여전히 EmailAddresses 및 PhoneNumbers 결과를 목록으로 나누기 위해 클라이언트 측 처리를 수행해야합니다.
Mr.Mindor

2
전화 번호에 '전화', '집'또는 '직장'과 같은 '유형'이 있으면 어떻게 되나요? 또한 쉼표는 기술적으로 전자 메일 주소에 인용됩니다 (따옴표로 묶인 경우). 어떻게하면 나눌 수 있습니까?
mpen

10

이 문제는 사용자 이름, DOB, 선호하는 색상 및 저장된 모든 다른 정보를 반환한다는 것입니다

문제는 당신이 충분히 선택적이지 않다는 것입니다. 당신 이 말했을 때 당신은 모든 것을 요구했습니다

Select * from...

... 그리고 당신은 그것을 얻었습니다 (DOB 및 좋아하는 색상 포함).

당신은 아마 조금 더 선택해야 할 것입니다 ... 선택적이고 다음과 같이 말했습니다.

select users.name, emails.email_address, phones.home_phone, phones.bus_phone
from...

user여러 email레코드에 참여할 수 있기 때문에 중복으로 보이는 레코드를 볼 수도 있지만이 두 가지를 구분하는 필드는 Select명령문에 없으므로 다음과 같이 말하고 싶을 수도 있습니다.

select distinct users.name, emails.email_address, phones.home_phone, phones.bus_phone
from...

... 각 레코드마다 반복해서 ...

또한, 나는 당신이하고있는 것을 알았습니다 LEFT JOIN. 그러면 조인 왼쪽의 모든 레코드 (즉 users)가 오른쪽의 모든 레코드 또는 다른 말로 조인됩니다 .

왼쪽 외부 조인은 내부 조인의 모든 값과 오른쪽 테이블과 일치하지 않는 왼쪽 테이블의 모든 값을 반환합니다.

( http://en.wikipedia.org/wiki/Join_(SQL)#Left_outer_join )

또 다른 질문은 실제로 왼쪽 조인이 필요 합니까 , 아니면 INNER JOIN충분했을까요? 그들은 매우 다른 유형의 조인입니다.

각 사용자에 대해 하나의 행을 반환하면 더 좋지 않을 것입니다. 그 레코드 내에 전자 메일 목록이 있습니다.

실제로 결과 집합 내의 단일 열에 즉석에서 생성 된 목록이 포함되도록하려면 수행 할 수 있지만 사용중인 데이터베이스에 따라 다릅니다. 오라클은 listagg기능이 있습니다.


궁극적 으로 다음과 비슷한 쿼리를 다시 작성하면 문제 해결 수 있다고 생각합니다 .

select distinct users.name, users.id, emails.email_address, phones.phone_number
from users
  inner join emails on users.user_id = emails.user_id
  inner join phones on users.user_id = phones.user_id

1
*를 사용하는 것은 권장되지 않지만 그의 문제의 핵심은 아닙니다. 사용자 열을 0 개로 선택하더라도 전화와 전자 메일은 모두 사용자와 일대 다 관계이므로 중복 효과가 발생할 수 있습니다. 고유 한 전화 번호가 phone1/name@hotmail.com, phone1/name@google.com에 두 번 나타나는 것을 막을 수는 없습니다.
mike30

6
-1 : "문제 해결 될 수 있습니다"라는 메시지가에서 left join로 어떤 영향을 줄지 알 수 없습니다 inner join. 이 경우 사용자가 불평하는 "반복"을 줄이지 않습니다. 전화 나 이메일이없는 사용자는 생략 할 수 있습니다. 거의 개선되지 않았습니다. 또한 "왼쪽에있는 모든 레코드를 오른쪽에있는 모든 레코드로"해석 할 때 ON기준을 건너 뜁니다.이 기준은 카티 전 곱에 내재 된 모든 '잘못된'관계를 제거하지만 모든 반복 필드를 유지합니다.
Javier

@Javier : 그렇기 때문에 실제로 왼쪽 조인이 필요하거나 INNER JOIN이 충분 하다고 말한 이유는 무엇 입니까? * OP의 문제 설명은 내부 조인의 결과를 예상 한 것처럼 들립니다 . 물론, 샘플 데이터 나 그들이 실제로 원하는 것에 대한 설명이 없다면 말하기가 어렵습니다. 나는 사람들 (내가 함께 일하는 사람들)이 이것을하는 것을 실제로 보았 기 때문에 제안했다 : 잘못된 조인을 선택한 다음 그들이 얻은 결과를 이해하지 못하면 불평한다. 데 을, 나는 여기에 일어날 줄 알았는데.
FrustratedWithFormsDesigner

3
질문의 요점이 없습니다. 이 가상의 예에서, 내가 원하는 모든 사용자 데이터 (이름, 생년월일 등) 나는 모든 그 / 그녀의 전화 번호를합니다. 내부 참여는 이메일이 없거나 전화가없는 사용자를 제외합니다. 어떻게 도움이됩니까?
mpen

4

쿼리는 항상 사각형의 들쭉날쭉 한 테이블 형식의 데이터 집합을 생성합니다. 세트 내에 중첩 된 하위 세트가 없습니다. 세트의 세계에서 모든 것은 순수한 중첩되지 않은 사각형입니다.

조인은 2 세트를 나란히 놓는 것으로 생각할 수 있습니다. "온"조건은 각 세트의 레코드가 일치되는 방식입니다. 사용자에게 3 개의 전화 번호가있는 경우 사용자 정보에 3 번 중복 된 내용이 표시됩니다. 직사각형의 들쭉날쭉하지 않은 세트는 쿼리에 의해 생성되어야합니다. 단순히 일대 다 관계로 세트를 결합하는 특성입니다.

원하는 것을 얻으려면 Mason Wheeler와 같은 별도의 쿼리를 사용해야합니다.

select * from Phones where user_id=344;

이 쿼리의 결과는 여전히 직선으로 들쭉날쭉하지 않은 집합입니다. 세트 세계의 모든 것과 마찬가지로.


2

병목 현상이 존재하는 위치를 결정해야합니다. 데이터베이스와 응용 프로그램 간의 대역폭은 일반적으로 매우 빠릅니다. 대부분의 데이터베이스가 한 번의 호출 내에서 3 개의 개별 데이터 세트를 리턴 할 수없고 조인이없는 이유는 없습니다. 그런 다음 원하는 경우 앱에서 모두 함께 참여하십시오.

그렇지 않으면 데이터베이스가이 데이터 세트를 함께 모은 다음 조인의 결과 인 각 행에서 반복되는 모든 값을 제거하고 행 자체에 동일한 이름 또는 전화 번호를 가진 두 사람과 같은 중복 데이터가있는 것은 아닙니다. 대역폭을 절약하기 위해 오버 헤드가 많은 것 같습니다. 더 나은 필터링 및 불필요한 열 제거로 적은 데이터를 반환하는 데 집중하는 것이 좋습니다. Select *는 의존하는 프로덕션 웰에서는 사용되지 않기 때문입니다.


"대부분의 데이터베이스가 한 번의 호출에서 3 개의 개별 데이터 세트를 리턴 할 수없고 조인이없는 이유는 없습니다."-한 번의 호출로 3 개의 개별 데이터 세트를 리턴하도록하려면 어떻게해야합니까? 3 가지 다른 쿼리를 보내야한다고 생각했는데 각 쿼리 사이에 대기 시간이 발생합니까?
mpen

하나의 트랜잭션에서 저장 프로 시저를 호출 한 다음 원하는만큼의 데이터 집합을 반환 할 수 있습니다. "SelectUserWithEmailsPhones"sproc가 필요할 수 있습니다.
Graham

1
@ Mark : 동일한 배치의 일부로 하나 이상의 명령을 (SQL 서버에서) 적어도 보낼 수 있습니다. cmdText = "select * from b; * from a; select * from c"를 선택한 다음 sqlcommand의 명령 텍스트로 사용하십시오.
jmoreno

2

사용자 쿼리와 전화 번호 쿼리에 대해 고유 한 결과를 원할 경우 데이터를 조인하지 마십시오. 그렇지 않으면 다른 사용자가 "설정"을 지적했거나 데이터에 모든 행에 대한 추가 필드가 포함됩니다.

조인이 아닌 2 개의 고유 쿼리를 실행하십시오.

저장 프로 시저 또는 인라인 매개 변수화 된 sql craft 2 쿼리에서 두 결과를 모두 반환합니다. 대부분의 데이터베이스 및 언어는 여러 결과 집합을 지원합니다.

예를 들어 SQL Server 및 C #은을 사용하여 기능을 수행합니다 IDataReader.NextResult().


1

뭔가 빠졌습니다. 데이터를 비정규 화하려면 직접해야합니다.

;with toList as (
    select  *, Stuff(( select ',' + (phone.phoneType + ':' + phone.PhoneNumber) 
                    from phones phone
                    where phone.user_id = user.user_id
                    for xml path('')
                  ), 1,1,'') as phoneNumbers
from users user
)
select *
from toList

1

관계형 클로저 개념은 기본적으로 모든 쿼리 결과가 기본 테이블 인 것처럼 다른 쿼리에서 사용할 수있는 관계라는 것을 의미합니다. 이는 쿼리를 구성 할 수있게하므로 강력한 개념입니다.

SQL을 통해 중첩 된 데이터 구조를 출력하는 쿼리를 작성할 수 있으면이 원칙을 위반하게됩니다. 중첩 된 데이터 구조는 관계가 아니므로 더 쿼리하거나 다른 관계를 결합하려면 새 쿼리 언어 또는 SQL에 대한 복잡한 확장이 필요합니다.

기본적으로 관계형 DBMS 위에 계층 적 DBMS를 구축합니다. 모호한 이익을 위해서는 훨씬 더 복잡해지며 일관된 관계형 시스템의 이점을 잃게됩니다.

SQL에서 계층 적으로 구조화 된 데이터를 출력하는 것이 편리한 이유를 이해하지만 DBMS 전체에서 추가 된 복잡성의 비용은이를 지원할 가치가 없습니다.


-4

Pls는 행 (사용자)의 구분 된 값의 단일 셀로 추출 될 수있는 열 (연락처)의 여러 행 (전화 번호)을 그룹화하는 STUFF 기능의 사용법을 나타냅니다.

오늘날 우리는 이것을 광범위하게 사용하지만 높은 CPU 및 성능 문제에 직면합니다. XML 데이터 형식은 다른 옵션이지만 쿼리 수준이 아닌 디자인 변경입니다.


5
이것이 어떻게 문제를 해결하는지 확장하십시오. "Pls의 사용법"을 말하지 말고 이것이 질문을 어떻게 달성 할 수 있는지에 대한 예를 제공하십시오. 또한 명확하게 만드는 제 3 자 소스를 인용하는 것이 도움이 될 수 있습니다.
bitsoflogic

1
STUFF스플 라이스와 비슷한 것 같습니다 . 그것이 내 질문에 어떻게 적용되는지 확실하지 않습니다.
mpen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.