본문 바로가기

Tech. Post/CodeHub, 잡다한 코드 이야기

[C#] LINQ - Where 사용법

개요

LINQ 문법 중 가장 많이 사용하는 Where 절에 대해 다양한 예제로 정리하였습니다. LINQ 에서 Where 절은 질의 결과를 제한하는 절입니다. 오직 Where 절 조건에 맞는 요소만이 결과 시퀀스에 추가됩니다. 아래 예제는 쿼리식 표현을 메서드식 표현으로 변경해 본 예시입니다. Where() 메서드 안에 있는 표현은 람다식으로 LINQ에서 많이 사용됩니다.

 

LINQ 쿼리 구조

public struct player
{
    public string 이름;
    public int 나이;
    public int 등번호;
    public string 포지션;
    public string 소속팀;
}
List<player> team = new()
{
    new player {이름 = "김현수", 나이 = 34, 등번호 = 22, 포지션 = "외야수", 소속팀 = "LG트윈스"},
    new player {이름 = "오지환", 나이 = 32, 등번호 = 10, 포지션 = "유격수", 소속팀 = "LG트윈스"},
    new player {이름 = "김민성", 나이 = 34, 등번호 = 16, 포지션 = "내야수", 소속팀 = "LG트윈스"},
    new player {이름 = "서건창", 나이 = 33, 등번호 = 31, 포지션 = "내야수", 소속팀 = "LG트윈스"},
    new player {이름 = "켈리", 나이 = 30, 등번호 = 1, 포지션 = "투수", 소속팀 = "LG트윈스"},
    new player {이름 = "이정후", 나이 = 24, 등번호 = 51, 포지션 = "외야수", 소속팀 = "키움히어로즈"},
    new player {이름 = "푸이그", 나이 = 29, 등번호 = 66, 포지션 = "내야수", 소속팀 = "키움히어로즈"},
    new player {이름 = "이용규", 나이 = 36, 등번호 = 19, 포지션 = "외야수", 소속팀 = "키움히어로즈"},
    new player {이름 = "요키시", 나이 = 33, 등번호 = 10, 포지션 = "투수", 소속팀 = "키움히어로즈"},
    new player {이름 = "이지영", 나이 = 27, 등번호 = 37, 포지션 = "포수", 소속팀 = "키움히어로즈"},
};

 

쿼리식 표현

var selectQuery = 
	from p in team
	where p.나이 >= 30
    	orderby p.나이 ascending
    	select p;

 

쿼리식 표현, 나이가 30 이상인 선수를 나이 순으로 오름차순 정렬 후 조회 결과

더보기
켈리,30,1,투수,LG트윈스
오지환,32,10,유격수,LG트윈스
서건창,33,31,내야수,LG트윈스
요키시,33,10,투수,키움히어로즈
김현수,34,22,외야수,LG트윈스
김민성,34,16,내야수,LG트윈스
이용규,36,19,외야수,키움히어로즈

 

메서드식 표현

 // Select 생략 가능
 var selectMethod = team.Where(p => p.나이 >= 30).OrderBy(p => p.나이).Select(p => p);

 

메서드식 표현, 나이가 30 이상인 선수를 나이 순으로 오름차순 정렬 후 조회 결과

더보기
켈리,30,1,투수,LG트윈스
오지환,32,10,유격수,LG트윈스
서건창,33,31,내야수,LG트윈스
요키시,33,10,투수,키움히어로즈
김현수,34,22,외야수,LG트윈스
김민성,34,16,내야수,LG트윈스
이용규,36,19,외야수,키움히어로즈

 

다수속성으로 필터링

selectQuery = 
	from player in team
	where player.소속팀 == "키움히어로즈" && player.나이 < 30
    	select player;
    
selectMethod = team.Where(player => player.소속팀 == "키움히어로즈" && player.나이 < 30);

소속팀이 '키움히어로즈' 이고 나이가 30 미만인 선수 조회 결과

더보기

이정후,24,51,외야수,키움히어로즈

푸이그,29,66,내야수,키움히어로즈

이지영,27,37,포수,키움히어로즈

 

인덱스 값으로 필터링

selectMethod = team.Where((player, index) => player.포지션.Length < index);

인덱스 값 보다 짧은 길이를 가진 포지션의 선수 조회 결과

더보기
켈리,30,1,투수,LG트윈스
이정후,24,51,외야수,키움히어로즈
푸이그,29,66,내야수,키움히어로즈
이용규,36,19,외야수,키움히어로즈
요키시,33,10,투수,키움히어로즈
이지영,27,37,포수,키움히어로즈

 

GROUP 절

group 절은 분류 기준에 따라 데이터를 분류하고 그룹화하여 그룹 개체를 반환합니다. 우리가 모양이나 색깔 또는 맛 등의 기준에 따라 나누듯 LINQ 쿼리식에도 group절을 이용하여 데이터를 분류할 수 있습니다.

group A by B into C

여기서 A는 범위 변수를 말하며, B는 분류 기준,  C는 분류 변수를 의미합니다. 추가적으로 쿼리 작업을 수행하려면 into 키워드가 필요하지만 그렇지 않은 경우에는 into와 그룹 변수를 쓰지 않아도 됩니다.

var selectQuery = from player in team
                  orderby player.나이
                  group player by player.나이 < 30;

foreach (var player in selectQuery)
{
    Console.WriteLine(player.Key ? "나이 30 미만 :" : "나이 30 이상:");
    foreach (var p in player)
    {
        Console.WriteLine("\t{0}: {1}세", p.이름, p.나이);
    }
}

선수의 나이가 30을 기준으로 미만과 이상으로 분류 결과

더보기
나이 30 미만 :
        이정후: 24세
        이지영: 27세
        푸이그: 29세
나이 30 이상:
        켈리: 30세
        오지환: 32세
        서건창: 33세
        요키시: 33세
        김현수: 34세
        김민성: 34세
        이용규: 36세

위 예제는 리스트인 team의 요소를 group 절의 분류 기준을 통해 분류하여 그룹화시키는 코드입니다. 분류 기준은 요소의 '나이'의 값이 30 미만인지 이상인지에 따라 분류하여 두 개의 그룹으로 나누어 집니다. 여기서 논리식이 참인지 거짓인지에 따라 그룹을 표현했습니다. 

 

JOIN 절

join 절은 직접 관계가 없는 두개의 데이터 원본을 연결시킬때 사용합니다. 각 원본의 요소가 서로 같은지 비교하여 일치한다면 데이터를 서로 연결시키고 일치하지 않는다면 연결시키지 않습니다. 우선 join 절의 형식에는 내부 조인과 외부 조인으로 나누어집니다. 내부 조인과 외부 조인의 차이점은 차차 살펴보도록 하고, join 절의 사용 형식을 알아보겠습니다.

from a in A
join b in B on a-field equals b-field

위의 형식에서 a-field는 a의 필드, b-field는 b의 필드를 나타냅니다. a는 기준이 되는 데이터이며, B는 비교할 대상 데이터 입니다. 그리고 여기서는 두 키가 같은지 비교를 하는 연산 빼고는 사용할 수 없으며, == 연산자 대신에 equals 키워드를 사용합니다. 이 키워드는 join 절에서만 사용이 가능하니 반드시 기억해 두길 바랍니다. 내부 조인과 외부 조인 예제에 사용되는 데이터는 아래와 같습니다.

List<Player> team = new()
{
    new Player {이름 = "김현수", 나이 = 34, 등번호 = 22, 포지션 = "외야수", 소속팀 = "LG트윈스"},
    new Player {이름 = "오지환", 나이 = 32, 등번호 = 10, 포지션 = "유격수", 소속팀 = "LG트윈스"},
    new Player {이름 = "김민성", 나이 = 34, 등번호 = 16, 포지션 = "내야수", 소속팀 = "LG트윈스"},
    new Player {이름 = "서건창", 나이 = 33, 등번호 = 31, 포지션 = "내야수", 소속팀 = "LG트윈스"},
    new Player {이름 = "켈리", 나이 = 30, 등번호 = 1, 포지션 = "투수", 소속팀 = "LG트윈스"},
    new Player {이름 = "이정후", 나이 = 24, 등번호 = 51, 포지션 = "외야수", 소속팀 = "키움히어로즈"},
    new Player {이름 = "푸이그", 나이 = 29, 등번호 = 66, 포지션 = "내야수", 소속팀 = "키움히어로즈"},
    new Player {이름 = "이용규", 나이 = 36, 등번호 = 19, 포지션 = "외야수", 소속팀 = "키움히어로즈"},
    new Player {이름 = "요키시", 나이 = 33, 등번호 = 10, 포지션 = "투수", 소속팀 = "키움히어로즈"},
    new Player {이름 = "이지영", 나이 = 27, 등번호 = 37, 포지션 = "포수", 소속팀 = "키움히어로즈"},
};

List<Rank> rank = new()
{
    new Rank { 이름 = "김현수", 타율 = 0.314, 타점 = 108, 순위 = 2},
    new Rank { 이름 = "오지환", 타율 = 0.283, 타점 = 78, 순위 = 4},
    new Rank { 이름 = "이정후", 타율 = 0.343, 타점 = 112, 순위 = 1},
    new Rank { 이름 = "푸이그", 타율 = 0.298, 타점 = 92, 순위 = 3},
};

먼저, 내부 조인에 대한 예제 코드를 알아봅시다.

var innerJoin = from player in team
                join ran in rank on player.이름 equals ran.이름
                select new { player.이름, ran.타율, ran.타점, player.포지션, player.소속팀, ran.순위 };

foreach (var join in innerJoin)
{
    Console.WriteLine($"{join.이름}, {join.타율}, {join.타점}, {join.포지션}, {join.소속팀}, {join.순위}");
}

내부 조인 결과

더보기
김현수, 0.314, 108, 외야수, LG트윈스, 2
오지환, 0.283, 78, 유격수, LG트윈스, 4
이정후, 0.343, 112, 외야수, 키움히어로즈, 1
푸이그, 0.298, 92, 내야수, 키움히어로즈, 3

위의 예제 코드를 보면, 범위 변수인 player의 '이름' 필드와 연결 대상인 ran의 'rank' 필드의 값이 서로 같은지 비교합니다. 만약 같으면, 무명 형식을 통해 새로운 형식을 만들어 내어 이름 필드에는 player.이름의 값과 타율 필드에는 ran.타율의 값 등이 들어갑니다. 그리고 여기서 중요한 것은 서로 비교 후 같지않는 경우 join 절의 결과에 포함하지 않습니다.

 

다음은, 외부 조인에 대한 예제코드 입니다.

var outerJoin = from player in team
                join ran in rank on player.이름 equals ran.이름 into sg
                from ran in sg.DefaultIfEmpty(new Rank() { 타점 = 0, 타율 = 0.0, 순위 = 0 })
                select new { player.이름, ran.타율, ran.타점, player.포지션, player.소속팀, ran.순위 };
                
foreach (var join in outerJoin)
{
    Console.WriteLine($"{join.이름}, {join.타율}, {join.타점}, {join.포지션}, {join.소속팀}, {join.순위}");
}

외부 조인 결과

더보기
김현수, 0.314, 108, 외야수, LG트윈스, 2
오지환, 0.283, 78, 유격수, LG트윈스, 4
김민성, 0, 0, 내야수, LG트윈스, 0
서건창, 0, 0, 내야수, LG트윈스, 0
켈리, 0, 0, 투수, LG트윈스, 0
이정후, 0.343, 112, 외야수, 키움히어로즈, 1
푸이그, 0.298, 92, 내야수, 키움히어로즈, 3
이용규, 0, 0, 외야수, 키움히어로즈, 0
요키시, 0, 0, 투수, 키움히어로즈, 0
이지영, 0, 0, 포수, 키움히어로즈, 0

위의 예제 코드를 보면, into 키워드가 사용되었고, sg란 임시 그룹을 가지고 DefaultIfEmpty 메서드를 통해 비어있는 join 절의 결과에 채워넣습니다. 외부 조인은 내부조인과 다르게 일치하지 않는 데이터는 필드의 값이 비어있기 때문에 DefaultIfEmpty 메서드를 통해 기본값을 지정해 줍니다.


마치며, 이번 포스트에는 LINQ 쿼리식 중 Where 방식에 대해 알아보았습니다. LIQN 쿼리식을 실제로 사용해 보면 많이 편하다는 것을 느낄 수 있었습니다. 혹시 이해가 되지 않은 내용은 코맨트를 달아주시면 답변해 드리도록 하겠습니다. 좀 더, LINQ 쿼리식에 대해 자세한 사항을 알아보고 싶다면 아래의 MSDN 사이트를 참고하세요.

 

http://msdn.microsoft.com/ko-kr/library/bb397933.aspx

'Tech. Post > CodeHub, 잡다한 코드 이야기' 카테고리의 다른 글

성능 프로파일러 사용 후기  (2) 2022.11.29
[C#] Lambda, Func, Action  (2) 2022.11.28