컴퓨터 공부/📗 CS

[데이터베이스] 5장 - SQL 로 데이터 조회하기! SELECT 를 활용해서 데이터를 읽어오는 기본적인 문법과 관련 키워드들

letzgorats 2024. 1. 4. 11:18

SELECT 로 데이터 조회하기

SELECT statement 는 아래와 같다.

- ID 가 9인 임직원의 이름과 직군을 알고 싶다.

 

: 현재 Employee 테이블은 아래와 같다.

EMPLOYEE 테이블
SELECT name, position FROM employee WHERE id = 9;

 

SELECT name, position
FROM employee 
WHERE id = 9;

 

여기서 SELECT 뒤에 오는 COLUMN(속성,열) 들을 projection attributes 라고 한다. 즉, 내가 관심있는 속성만 투영해서 가지고 온다는 의미이다. 즉, 우리가 원하는 컬럼인 name 과 position 에 해당하는 값들만 뽑아서 가져온다.

그리고, WHERE 이하를 selection condition 이라고 하는데, 이는 행을 결정해 준다고 생각하면 된다. (여기서는 id=9인 행)

 

다시말해, 이 projection attributes와 selection condition 이 교차되는 지점의 값들만 뽑아서 출력해준다.

 

★ 정리하면, SELECT statement 는 아래와 같다.

  • SELECT attribute(s)
  • FROM table(s)
  • [WHERE condition(s);

 

다른 SELECT statement 는 아래와 같다.

- project 2002를 리딩(leading) 하고 있는 임직원의 ID 와 이름과 직군을 알고 싶다.

 

: 여기서 임직원의 ID 와 이름과 직군을 알고 싶다고 했으니까, employee 테이블이 필요한 건 확실해 보인다.

: 근데, project 2002 를 리딩하는 것을 찾기 위해서는 또 project 테이블이 필요해보인다.

: 어떻게 하면 좋을까?

 

먼저, 현재 EMPLOYEE 테이블과 PROJECT 테이블은 아래와 같다.

 

EMPLOYEE 테이블
PROJECT 테이블

 

여기서 PROJECT 테이블의 leader_id 가 결국 EMPLOYEE 테이블에서 id 와 같은 속성을 뜻한다.

즉, PROJECT 테이블의 leader_id 는 EMPLOYEE 테이블의 PK 인 id 를 참조하고 있다고 이해하면 된다. 즉, leader_id는 외래키인 셈이다.

이 생각을 바탕으로 SQL 문을 작성하면 아래와 같다.

결과

SELECT EMPLOYEE.id, EMPLOYEE.name, position
FROM EMPLOYEE,PROJECT
WHERE EMPLOYEE.id = PROJECT.leader_id and PROJECT.id = 2002;

 

EMPLOYEE 테이블과 PROJECT 테이블이 둘 다 필요하니까, FROM 절 뒤에 두 테이블을 모두 써줬다.

이 때, 뽑아내야 하는 값은 "직원의 id, 직원의 name, 직원의 position" 이므로, 컬럼이름이 중복되는 속성은 앞에 어떤 테이블에서의 속성값인지 테이블 이름을 명시해줘야 한다.

 

만약, "직원의 name" 인데, EMPLOYEE.name 이 아니라 PROJECT.name 이라고 하면 PROJECT 테이블에 있는 name 값이 뽑혀지게 된다.  

PROJECT.name 일 때의 결과

 

다시 돌아와서 조건으로는 "PROJECT의 id" 가 2002인 것과 더불어, 현재 두 테이블이 어떤 속성으로 연관되어 있는지를 알려줘야 한다.

앞서 말했듯이 PROJECT 테이블의 leader_id 가 EMPLOYEE 테이블의 id 를 참조하는 외래키이기 때문에, 

EMPLOYEE.id = PROJECT.leader_id

 

이 구문을 WHERE 절에 반드시 포함시켜줘야 한다. 이 구문을 두 테이블을 조인시키는 역할을 한다고 해서 join condition 이라고 한다.

 


AS 키워드 사용하기

: AS 는 테이블이나 attribute 에 별칭(alias)를 붙일 때 사용한다.

: AS 는 생략 가능하다.

 

아까 SELECT 에서 작성했던 쿼리를 살펴보자.

SELECT EMPLOYEE.id, EMPLOYEE.name, position
FROM EMPLOYEE,PROJECT
WHERE EMPLOYEE.id = PROJECT.leader_id and PROJECT.id = 2002;

결과

 

이 쿼리에서 각 컬럼마다 접근을 할 때, 일일히 테이블 이름을 명시하는 것은 번거로울 수 있다.

이 때, AS 로 별칭을 사용하면서 간편화할 수 있는데, 사용법은 아래와 같다.

SELECT E.id, E.name, position
FROM EMPLOYEE AS E,PROJECT AS P
WHERE E.id = P.leader_id and P.id = 2002;

 

EMPLOYEE 테이블 이름을 E 로 대체하고, PROJECT 테이블 이름을 P 로 대체했다. 별칭은 마음대로 지어줘도 되지만, 가독성 좋게 하는 것이 좋다. 이 AS 키워드는 생략 가능하다. 즉 AS 없이 바로 별칭이 테이블 옆에 와도 된다는 의미다.

SELECT E.id, E.name, position
FROM EMPLOYEE E,PROJECT P
WHERE E.id = P.leader_id and P.id = 2002;

 

AS 를 쓴 것이나 생략한 것이나 별칭을 사용한 결과는 기존 쿼리와 동일하다.

결과는 동일하다.

그럼, 궁금한 점이 생길 수 있다. 별칭을 정해줬는데, 기존 테이블 이름으로 칼럼을 접근하면 어떻게 될까?

아래 쿼리처럼 말이다.

SELECT EMPLOYEE.id, EMPLOYEE.name, position
FROM EMPLOYEE E,PROJECT P
WHERE EMPLOYEE.id = PROJECT.leader_id and P.id = 2002;

결과

쿼리를 작성해서 실행해보면, ERROR 가 뜬다! 지금은 field list 즉 SELECT 절에서의 EMPLOYEE를 인식하지 못한 것이다. 그럼 SELECT 절에서는 별칭으로 접근하고 WHERE 절에서만 써보자.

SELECT E.id, E.name, position
FROM EMPLOYEE E,PROJECT P
WHERE EMPLOYEE.id = PROJECT.leader_id and P.id = 2002;

결과

 

결과는 똑같이 에러가 뜬다. 즉, 쿼리에서 별칭을 쓸거면 접근할 때도 다 통일해줘야 하고 안 쓸거면 안 써야 한다!

 

 

이 AS 키워드는 SELECT 절에서도 적용 가능하다. FROM 과 SELECT 에서만 사용할 수 있는 키워드라고 이해하면 된다.

SELECT 에서 AS를 쓰는 이유는 결과 테이블값의 칼럼이름을 가독성 좋게 바꾸려는데 의의가 있다. 아까 예시에서의 결과를 다시 가져오면, 아래와 같다.

결과

 

뭔가 가독성 좋게 "직원의 ID, 직원의 이름, 직군" 이라고 각각 칼럼값을 주고 싶다면, 아래와 같이 쿼리를 짤 수 있다.

SELECT E.id AS '직원의 id', E.name AS '직원의 이름' , position AS '직군'
FROM EMPLOYEE E,PROJECT P
WHERE E.id = P.leader_id and P.id = 2002;

결과

 

의도했던 대로 잘 결과가 나온것을 확인할 수 있다. 

이 때 FROM 절에서는 AS 가 생략이 가능한 것처럼, SELECT 절에서도 AS 가 생략이 가능하다는 점을 명심하자.

SELECT E.id '직원의 id', E.name '직원의 이름', position '직군'
FROM EMPLOYEE E,PROJECT P
WHERE E.id = P.leader_id and P.id = 2002;

 


 DISTINCT

 

statement 는 아래와 같다.

- 디자이너들이 참여하고 있는 프로젝트들의 ID 와 이름을 알고 싶다.

 

: 임직원 중에서도 디자이너인 직원들을 알고 싶으니까 position 칼럼값을 포함하는 EMPLOYEE 테이블이 필요한 건 확실해 보인다.

: 그리고, 그 직원이 참여하고 있는 프로젝트의 ID와 이름을 알고 싶으므로, PROJECT 테이블도 역시 필요해 보인다. 

: 근데, 이 EMPLOYEE 테이블과 PROJECT 테이블을 연결시켜주는 테이블이 바로 WORKS_ON 테이블이다. 왜냐하면, WORKS_ON 테이블에는 어떤 임직원이 어떤 프로젝트에 참여하고 있는지 나와있기 때문이다.

: 즉, WORKS_ON 테이블의 empl_id 는 EMPLOYEE 테이블의 id 를 참조하고 있고, proj_id 는 PROJECT 테이블의 id 를 참조하고 있다.

 

현재 테이블들을 살펴보면서 이해를 해보자.

EMPLOYEE 테이블 , WORKS_ON 테이블
PROJECT 테이블

 

세 개의 테이블을 사용해야 한다는 것을 숙지하고 쿼리를 작성해보면 아래와 같다.

SELECT P.id, P.name
FROM EMPLOYEE E, WORKS_ON W, PROJECT P
WHERE W.empl_id = E.id and W.proj_id = P.id
    AND E.position = 'DSGN';

 

먼저, 세 개의 테이블을 이용해야 하니까 FROM 절에는 EMPLOYEE, WORKS_ON, PROJECT 세 개의 테이블이 모두 와야 한다. 이 때, 별칭 AS 를 생략하고 각 테이블을 E,W,P 로 명시해줬다.

 

조건으로는 "EMPLOYEE의 position"이 'DSGN'인 것과 더불어, 현재 세 테이블이 어떤 속성으로 연관되어 있는지를 알려줘야 한다.

즉, SELECT 에서의 예제처럼 공통된 컬럼을 연결시켜줘야 하는데, 앞서 말했듯이 WORKS_ON 테이블의 empl_id는 EMPLOYEE 테이블의 id 를 참조하고, WORKS_ON 테이블의 proj_id 는 PROJECT 테이블의 id를 참조한다.

 

이를 표현하면 아래와 같은 구문이 된다. 

 WORKS_ON.empl_id = EMPLOYEE.id and WORKS_ON.proj_id = PROJECT.id

 

이 구문을 WHERE 절에 반드시 포함시켜줘야 각 테이블마다 참조를 하면서 원하는 값을 뽑아낼 수 있게 된다.

우리가 결론적으로 SELECT 하고자 하는 값은 프로젝트의 id 와 name 이기 때문에 마지막 SELECT 절에 P.id, P.name 을 해주면 아래와 같은 결과를 얻을 수 있다.

결과

 

근데, 지금 결과에는 중복된 tuple 이 들어가게 됐다. 이유가 뭘까?

EMPLOYEE 테이블을 보면, 현재 디자이너는 2번(Kim)과 7번(Chloe) 디자이너다.

Kim이 참여하고 있는 프로젝트 id 는 2003 이고, Chloe 가 참여하고 있는 프로젝트 id 는 2002이랑 2003이다.

즉, 결과에 나온 프로젝트 id와 name은 각각 Kim과 Chole 가 참여하고 있는 프로젝트들인데, 둘 다 2003번 프로젝트를 참여하고 있기 때문에 쿼리를 돌린 결과 중복해서 나온 것이다.

 

확인차로 한 번 봐보자. SELECT 문에 W.empl_id 만 추가하면 아래와 같은 결과가 나온다.

SELECT W.empl_id, P.id, P.name
FROM EMPLOYEE E, WORKS_ON W, PROJECT P
WHERE W.empl_id = E.id and W.proj_id = P.id
    AND E.position = 'DSGN';

결과

 

원래는 이런 테이블에서 id와 name 만 뽑아내다보니 프로젝트 id 가 2003인 부분이 중복되어 보인 것이다.

 

우리는 지금 각각의 디자이너들이 참여한 모든 프로젝트의 ID와 name을 열거하기보다는 순수하게 디자이너들이 참여한 프로젝트의 ID와 name 이 무엇인지 알고 싶은 것이다.

 

이 때, DISTINCT 키워드를 사용해서 중복제거를 하면 된다. 리터럴하게 뚜렷하게 구별짓는다는 의미로 받아들이면 된다.

SELECT DISTINCT P.id, P.name
FROM EMPLOYEE E, WORKS_ON W, PROJECT P
WHERE W.empl_id = E.id and W.proj_id = P.id
    AND E.position = 'DSGN';

결과

 

DISTINCT 를 씀으로써 중복된 P.id 를 제거하고 남기고 싶은 것만 남았다.

또, 위 오른쪽 그림처럼 DISTINCT는 중복제거를 원하는 컬럼을 대상으로 (괄호)로 지정해줘도 된다. 이 때는, DISTINCT는 1개의 칼럼만을 대상으로 한다. 괄호는 optional 한 부분이다.

 

그렇다면, 아래처럼 작성하면 어떻게 될까?

SELECT P.id, DISTINCT(P.name)
FROM EMPLOYEE E, WORKS_ON W, PROJECT P
WHERE W.empl_id = E.id and W.proj_id = P.id
    AND E.position = 'DSGN';

 

오류

이렇게 하면, 오류가 난다. 대체 왜일까?

 

사실, DISTINCT는 특정 컬럼에만 적용할 수 없으며, DISTINCT 뒤에 오는 모든 컬럼에 적용된다. 뒤에 따라오는 id 와 name 의 조합이 중복적인 경우엔 중복되는 조합 중 하나의 조합만 출력되도록 하는 것이다.

 

즉, DISTINCT 뒤에 따라붙는 모든 컬럼에 적용이 되는데, 이 때 단일 컬럼 하나하나에 적용되는 것이 아니라 뒤에 따라붙는 컬럼들의 ‘조합’에 적용된다는 말이다.

 

처음의 table 에서 id와 name이 중복되는 조합은 id가 2003일 때, name 이 ‘홈페이지 UI개선’인 경우이다.

따라서, distinct 를 적용하면 중복되는 조합 id=2003,name=‘홈페이지 UI 개선’ 중 중복되는 행을 날리고 하나만 남기는 것이다.

 

다시한번 기억하자. DISTINCT 를 마치 파라미터를 필요로 하는 함수처럼 인지하면 안된다. 뒤에 여러 개의 컬럼이 붙는다면, 개별 컬럼들에 적용되는 것이 아니라 중복되는조합 하나만 출력하는 것임을!


Q. 그럼 왜, SELECT DISTINCT(P.id), P.name 를 하면 SELECT DISTINCT P.id, P.name 와 똑같이 정상 작동되는 것일까?

 

둘 다 결국 SELECT 문에 있는 모든 열에 대해 DISTINCT 가 적용되는 것이다.

사실 지금처럼 컬럼이 2개 이상일 때는 DISTINCT(P.id) 처럼 쓰는 것을 권장하지 않는다. 컬럼이 1개일 때는 DISTINCT에 괄호를 붙이든 생략하든 직관적으로 이해가 가지만, 지금은 다음과 같은 이유로 권장되지 않는다.

 

1) 잘못된 해석의 여지

: DISTINCT(P.id)는 마치 P.id 열에만 DISTINCT가 적용되는 것처럼 보일 수 있기에, 권장되지 않는다. 실제로는 SELECT 문에 나열된 모든 열에 대해 중복 제거가 적용되기에 혼동을 일으킬 수 있다.

 

2) 괄호의 불필요성

: SQL 에서는 'DISTINCT' 는 함수가 아니다. 괄호가 필요하진 않다.(COUNT는 집계함수라서 필요하지만)

: 'DISTINCT'는 결과 집합에서 중복된 행을 제거하는 키워드일 뿐이다. 괄호를 사용하는 것은 문법적으로 허용되지만, 이는 일반적인 관행이나 명확한 코드 작성 방식에 어긋난다.  

 

즉, 명확성과 가독성을 위해 2개 이상의 컬럼이 SELECT 에 오고, DISTINCT 를 써야하는 상황이라면 괄호 없이 DISTINCT 를 사용하는 것이 좋다.

 


 LIKE

statement 는 아래와 같다.

- 이름이 N으로 시작하거나 N으로 끝나는 임직원들의 이름을 알고 싶다.

 

: 임직원들의 이름을 뽑아내야 하니까 EMPLOYEE 테이블에 접근을 해야한다.

 

EMPLOYEE 테이블부터 살펴보자.

EMPLOYEE 테이블

 

조건문에는 name이 N으로 시작하거나 N으로 끝나는 쿼리를 작성하면 될 듯 한데, 어떻게 짜면 좋을지 쿼리를 살펴보자.

SELECT name
FROM EMPLOYEE
WHERE name LIKE 'N%' or name LIKE '%N';

 

결과

 

WHERE 문에 LIKE 키워드를 사용하면 된다. LIKE 연산자를 사용하면 특수 와일드 카드 문자를 사용하여 문자열 데이터에 대해 "패턴"일치를 수행할 수 있다.

주요 와일드카드 캐릭터는 2개인데, '%'와 '_' 이다.

N으로 시작하는 것을 의미하는 것은 'N%' 라고 표현할 수 있고, N으로 끝나는 것을 의미하는 것은 '%N' 이라고 표현하면 된다.

 

이 때, '%'는 특정 문자 또는 일련의 문자에 대한 와일드카드 문자로, 'A'로 시작하는 스트링은 'A%'로 표현할 수 있고, 'a'로 끝나는 스트링은 '%a'로 표현할 수 있다.

물론, %가 있다고 해서 어떤 문자를 반드시 포함해야 하는 것은 아니다. '%er%'라고 해도, 'er'로 끝나는 값이 올 수 있다는 뜻이다.

 

MySQL 에서는 LIKE 연산자만 있으며, 기본적으로 대소문자를 구별짓지 않는다.

PostgreSQL 에서는 LIKE 는 대,소문자를 구분짓지만, ILIKE 라는 키워드는 대,소문자를 구별짓지 않는다. ( LIKE is case-sensitive, ILIKE is case-insensitive) 

 

'_'는 단일문자만 바꿀 수 있다. 그렇기 때문에, 여러 밑줄을 사용할 수가 있다.

예를 들어, "Version#B7", "Version#A4"와 같은 형태를 띄는 값이 있다면, WHERE value LIKE 'Version#__' 와 같이 쿼리를 짤 수 있다.


 

다른 statement 를 봐보자.

- 이름에 EN 이 들어가는 임직원들의 이름을 알고 싶다.

EMPLOYEE 테이블

SELECT name
FROM EMPLOYEE
WHERE name LIKE '%EN%';

결과

 

%EN% 이라고 지정해줘서 name 에 'EN' 이라는 값이 들어가면, 그 이름을 뽑아주는 쿼리이다. 여기서 JAYDEN 을 보면 EN 뒤에 아무런 글자가 없는데도, 뽑힌 것 보면, %가 있다고 해서 반드시 문자가 포함되야 하는 것은 아니다.


 



다른 statement 를 봐보자.

- 이름이 J로 시작하는, 총 여섯 글자의 이름을 가지는 임직원의 이름을 알고 싶다.

 

'_' 를 이용해서 쿼리를 작성해보면, '_'는 '%' 와는 다르게 반드시 문자가 와야 하기 때문에, J로 시작하고 '_'를 5개 붙여주면 J로 시작하는 총 여섯 글자의 이름을 뽑아낼 수 있다.

SELECT name
FROM EMPLOYEE
WHERE name LIKE 'J_____';

결과

 

'%'를 사용하면 아래와 같이 작성할 수 있을 것이다.

SELECT name
FROM EMPLOYEE
WHERE LENGTH(name) = 6 and name LIKE 'J%';

결과

 

결과는 동일하게 JAYDEN 이 나온다.


 

 

다른 statement 를 봐보자.

- 그럼, % 로 시작하거나 _ 로 끝나는 프로젝트 이름을 찾고 싶다면 어떻게 해야할까?

 

escape 문자와 함께 LIKE 를 사용하려면, '\'(백슬래시) 를 이용하면 된다. 다른 프로그래밍언어에서도 ""(큰따옴표) 나 ''(작은따옴표), '\' 등을 스트링에 포함시키고 싶을 때도 '\'을 이용하곤 하는데, SQL 에서도 비슷하다.

SELECT name 
FROM PROJECT
WHERE name LIKE '\%%' or name LIKE '%\_';

 

간단하다. % 로 시작하니까 % 를 기준으로 앞에 '\%'를 붙여준 것이고, _ 로 끝나니까 '%' 뒤에 '\_' 를 붙여준것이다.

 

★ 정리하면, LIKE 는 아래와 같다.

항목 설명
LIKE 문자열 pattern matching 에 사용
reserved character % : 0개 이상의 임의의 개수를 가지는 문자들을 의미
_ : 하나의 문자를 의미
escape character \ : 예약 문자를 escape 시켜서 문자 본연의 문자로 사용하고 싶을 때 사용

 *(asterisk) 사용하기

statement 를 봐보자.

- ID 가 9인 임직원의 모든 attributes 를 알고 싶다.

결과

 

간단하다. 선택된 모든 칼럼(속성)값을 뽑아내주는 키워드가 *(asterisk) 이다.

 


 

그럼, SELECT 예제에서 살펴봤던 다음 statement를 살펴보자.

- project 2002를 리딩(leading) 하고 있는 임직원의 ID 와 이름과 직군을 알고 싶다.

 

쿼리는 아래와 같았다.

SELECT E.id, E.name, position
FROM EMPLOYEE E,PROJECT P
WHERE E.id = P.leader_id and P.id = 2002;

 

여기서, E.id 와 E.name 과 position 말고 *(asterisk)를 쓰면 어떻게 될까?

결과

엄청 긴 테이블이 출력됐다.

즉, EMPLOYEE 테이블과 PROJECT 테이블 두 개가 이어져서 출력됐다.

이 테이블을 기준으로 아까 우리가 원했던 직원id, 직원name, 직원position 을 뽑아냈던 것이다.


 WHERE 절이 없는 SELECT

: 별 다른 것은 없다. WHERE 절이 없는 SELECT 는 보고 있는 테이블에 있는 모든 tuples 를 반환한다.

: 따로 조건을 걸어주지 않았기 때문이다.


 

💡SELECT로 조회할 때 조건들을 포함해서 조회를 한다면, 이 조건들과 관련된 attributes 에 index가 걸려있어야 한다. 그렇지 않다면 데이터가 많아질수록 조회 속도가 느려진다.

SELECT *
FROM EMPLOYEE
WHERE position = 'dev_back';

 

위와 같은 쿼리가 있다고 가정해보자.

position 열에 인덱스가 없다면, 데이터베이스는 'dev_back' 직군의 직원을 찾기 위해 'EMPLOYEE' 테이블의 모든 행을 확인해야 한다.

하지만, position 열에 인덱스가 있다면, 데이터베이스는 빠르게 'dev_back' 부서의 직원을 찾을 수 있다.

 

따라서, 자주 조회 조건으로 사용되는 열에는 인덱스를 설정하는 것이 좋다. (데이터베이스의 성능이 향상되고, 대용량 데이터를 다룰 때, 조회 속도가 크게 개선된다.)

 

💡위 내용은 MySQL 기준이다. 다른 RDBMS 의 SQL 문법은 조금씩 다를 수 있다.

 

💡위 내용은 SELECT 와 관련하여 대표적으로 중요한 '기본기'들을 담고 있다. 그 외에도 여러가지 기본적인 조회기능들과 세부 사항들이 있음을 염두하자!


참고자료

- 유튜브 쉬운코드

반응형