쿼리를 날리다 보면, 필요에 따라 그룹별로 순위를 매겨야 할 때 있다.
이에 대해 오라클에서는 그러한 기능을 제공하는데,
아래가 바로 그 예이다.

[Oracle]
SELECT empno, ename, job, sal, 
           
ROW_NUMBER() OVER(PARTITION BY job ORDER BY sal) AS rnum
FROM scott.emp;


<<결과>>

     EMPNO ENAME                JOB                       SAL       RNUM
---------- -------------------- ------------------ ---------- ----------
      7902 FORD                  ANALYST                    3000          1
      7788 SCOTT                ANALYST                   3000          2
      7369 SMITH                 CLERK                         800          1
      7900 JAMES                CLERK                         950          2
      7876 ADAMS                CLERK                       1100          3
      7934 MILLER                CLERK                       1300          4
      7782 CLARK                 MANAGER                  2450          1
      7698 BLAKE                 MANAGER                  2850          2
      7566 JONES                 MANAGER                  2975          3
      7839 KING                    PRESIDENT                5000          1
      7654 MARTIN               SALESMAN                 1250          1
      7521 WARD                  SALESMAN                 1250          2
      7844 TURNER               SALESMAN                 1500          3
      7499 ALLEN                 SALESMAN                 1600          4

14 개의 행이 선택되었습니다.



상기 쿼리는,
emp 테이블의 JOB을 기준으로 하여 그룹을 정하고 (PARTITION BY job),  -- 1
sal을 기준으로 하여 순위를 매겨(ORDER BY sal),
각각의 행에 ROW_NUMBER를 부여하겠다는 의미이다.                         -- 2


여기서 'PARTITION BY job'은 job별 정렬을 발생시킨다.
즉, 최종 결과물의 넘버링은 ORDER BY job, sal의 순으로 결과가 나오는 것이다.




[MySQL]
그런데, 불행하게도..... MySQL에는 저 기능이 없다.
그렇기 때문에 우리의 친구 꼼수(?)를 이용하여 저것을 구현해 내야 하는데.....

SELECT empno, ename, job, sal, rnum
FROM (
   SELECT a.*, 
           (CASE @vjob WHEN a.job THEN @rownum:=@rownum+1 ELSE @rownum:=1 END) rnum,
           (@vjob:=a.job) vjob
   FROM emp a, (SELECT @vjob:='', @rownum:=0 FROM DUAL) b
   ORDER BY a.job, a.sal                 
) c;


<<결과>>
+-------+--------+-----------+------+------+
| empno | ename  | job       | sal  | rnum |
+-------+--------+-----------+------+------+
|  7902 | FORD   | ANALYST   | 3000 |    1 |
|  7788 | SCOTT  | ANALYST   | 3000 |    2 |
|  7369 | SMITH  | CLERK     |  800 |    1 |
|  7900 | JAMES  | CLERK     |  950 |    2 |
|  7876 | ADAMS  | CLERK     | 1100 |    3 |
|  7934 | MILLER | CLERK     | 1300 |    4 |
|  7782 | CLARK  | MANAGER   | 2450 |    1 |
|  7698 | BLAKE  | MANAGER   | 2850 |    2 |
|  7566 | JONES  | MANAGER   | 2975 |    3 |
|  7839 | KING   | PRESIDENT | 5000 |    1 |
|  7654 | MARTIN | SALESMAN  | 1250 |    1 |
|  7521 | WARD   | SALESMAN  | 1250 |    2 |
|  7844 | TURNER | SALESMAN  | 1500 |    3 |
|  7499 | ALLEN  | SALESMAN  | 1600 |    4 |
+-------+--------+-----------+------+------+
14 rows in set (0.00 sec)

어때... 결과가 같아 보이는가?

자, 그럼 쿼리를 뜯어보자.
여기서 궁금하게 생각되는 부분은 아래 3개의 쿼리라고 예상 된다.

1. (CASE @vjob WHEN a.job THEN @rownum:=@rownum+1 ELSE @rownum:=1 END) rnum,
--> 이전 job 필드와 동일한 그룹인가를 판별하고, 그룹에 따라 순번을 부여하기 위함이며,
      테이블에서 각각의 행을 읽을 때마다,
      변수 @vjob 값이 지금 새로 읽은 job 과 같다면 변수 @rownum을 1증가 시키고, 
      그렇지 않은 경우(@vjob이 현재 읽은 job값과 같지 않다면) @rownum을 1로 초기화 시킨다
.

2. (@vjob:=a.job) as vjob
--> 테이블에서 각각의 행을 읽을 때마다,
      그룹 판별을 위해 현재 읽고 있는 행의 job값을 변수 @vjob에 입력

3. (SELECT @vjob:='', @rownum:=0 FROM DUAL) b
--> 원래는 쿼리를 수행하기 이전에, 
      SET @vjob:=0, @rownum:=0;  을 수행하여 변수를 초기화 해야 한다.
      만약 해주지 않으면, NULL 값이 들어가게 된다.

      하지만 그럴 경우 쿼리가 2번 수행되어야 하기 때문에,

      하나의 쿼리로 만들기 위해서 이런 식의 서브 쿼리를 이용한 것이다.
      이 서브쿼리는 초기 테이블 확인시 1회만 수행되고,
      이후부터는 열람되지 않는다.
    
      !! 주의 !!
      서브쿼리 안에서의 결과값만 가지고 현재의 결과값을 얻고자 할 때,
      변수가 되는 항목의 값을 동일한 자료형으로 맞춰주지 않으면, 
      정상적인 결과값이 나오지 않는다.
      가령 위의 예를 이용하자면, @vjob의 초기값을 @vjob:=0 으로 수행 하고
      서브쿼리만을 수행하면 정상적인 결과값이 나오지 않게 된다. 
      한 번 해보자~

      

이 3가지를 이해한다면 아마 이해할 수 있을 것이라 생각되지만,
한 가지 짚고 넘어가야 할 것이 있다.

Q. 우리가 흔히 쓰는 SELECT 문장의 수행순서는 어떻게 될까?
무슨의미냐 하면..
위에서 사용한 것처럼 변수를 이용한 SELECT 내 연속적인 값의 할당은,
수행결과에 영향을 미치게 되지 않을까?
라는 질문이다.


흠.. 내가 말을 써놓고 난해하군..
예제를 보도록 하자.

<<예제>>
SET @val1=0, @val2=0;    #아까도 말했듯이 변수 초기화는 먼저 선행되어야 한다.
SELECT @val1:=@val1+1, @val2:=@val1+1, @val2:=0, @val1=@val2+1
FROM DUAL;


자.... 당신이 예상하는 결과는?.....



<<쿼리 수행 결과>>
 +----------------+----------------+----------+---------------+
| @val1:=@val1+1 | @val2:=@val1+1 | @val2:=0 | @val1=@val2+1 |
+----------------+----------------+----------+---------------+
|                      1 |                     2 |             0 |                    1 |
+----------------+----------------+----------+---------------+
1 row in set (0.00 sec)

상기와 같이 SELECT 내 수행 결과는,
왼쪽에서 오른쪽으로 순차적인 수행이 이루어짐을 알 수 있다.

즉, @val1:=@val1+1  @val2:=@val1+1  @val2:=0  →  @val1=@val2+1
로 수행 순서가 정해진다는 의미.

그러므로,
변수를 이용한 SELECT를 이용할 때는 반드시 수행순서를 염두해 두고 쿼리를 작성하도록 하자.




PS : 오라클에는 예제 테이블이 있지만 MySQL 에는 없으니
       혹시 테스트 해보고 싶은 사람은 아래 쿼리를 수행해서 테스트 해보도록...

CREATE TABLE emp (
   empno INT,
   ename VARCHAR(30),
   job VARCHAR(30),
   sal INT
)ENGINE=INNODB DEFAULT CHAR SET=UTF8;

INSERT INTO emp
VALUES
(7902,'FORD','ANALYST',3000),
(7788,'SCOTT','ANALYST',3000),
(7369,'SMITH','CLERK',800),
(7900,'JAMES','CLERK',950),
(7876,'ADAMS','CLERK',1100),
(7934,'MILLER','CLERK',1300),
(7782,'CLARK','MANAGER',2450),
(7698,'BLAKE','MANAGER',2850),
(7566,'JONES','MANAGER',2975),
(7839,'KING','PRESIDENT',5000),
(7654,'MARTIN','SALESMAN',1250),
(7521,'WARD','SALESMAN',1250),
(7844,'TURNER','SALESMAN',1500),
(7499,'ALLEN','SALESMAN',1600);
Posted by 달팽이맛나
,

작업 중인 모 사이트가 갑자기 다운되는 현상이 발생했다.

원인은 MySQL DB connect 숫가가 꽉 차서 연결이 안되서였다.(max_connections)

급하게 MySQL Daemon을 restart해서 다시 정상이 되기는 했지만 문제를 찾아야 했다.

 

DB를 확인해 보니 1천만 row 가까운 테이블이 존재했고, 별다른 인덱스가 걸려있지 않았다.

 

Slow Query Log와 해당 테이블을 사용하는 웹프로그램을 뒤져서 해당 테이블을 다른 테이블과 JOIN 하여 데이터를 추출하고 있다는 것을 알았다....


 

웹프로그램을 짜다보면...

'이 서비스가 얼마나 활성화되겠어? 조금 서비스하다가 말겠지..'

라는 안일한 생각과 게으름으로 쿼리를 대충 사용하는 경우가 종종 있다.

 

그런 경우 약간 만 동시접속자가 증가하면 서비스가 멈추는 황당한 일을 겪게 된다.

 

이번도 그런 경우였다. 그래서 정리해 본다.

 

예제 테이블 정의

* table name : T1
+------+-------------+
| id   | name        |
+------+-------------+
| 1    | honggil     |
| 2    | nari        |
| 3    | abcd        |
+------+-------------+


* table name : T2
+------+-------------+
| id   | comment     |
+------+-------------+
| 1    | hohoho      |
| 2    | yo!!        |
+------+-------------+


 

1. JOIN을 생략한 JOIN

mysql> SELECT * FROM T1, T2 WHERE T1.id = T2.id
+------+-------------+-------------+
| id   | name        | comment     |
+------+-------------+-------------+
| 1    | honggil     | hohoho      |
| 2    | nari        | yo!!        |
+------+-------------+-------------+
 

JOIN한 각 테이블에 값이 모두 있는 필드 만 추출한다.

 

 

2. INNER JOIN

mysql> SELECT * FROM T1 INNER JOIN T2 ON T1.id = T2.id
+------+-------------+-------------+
| id   | name        | comment     |
+------+-------------+-------------+
| 1    | honggil     | hohoho      |
| 2    | nari        | yo!!        |
+------+-------------+-------------+
 
1번의 JOIN을 생략하고 Where 절로 처리한 것과 같은 결과를 보인다.

 

3. LEFT JOIN

mysql> SELECT * FROM T1 LEFT JOIN T2 ON T1.id = T2.id
+------+-------------+-------------+
| id   | name        | comment     |
+------+-------------+-------------+
| 1    | honggil     | hohoho      |
| 2    | nari        | yo!!        |
| 3    | abcd        | NULL        |
+------+-------------+-------------+
 
JOIN의 왼쪽에 있는 테이블을 기준으로, JOIN의 오른쪽 테이블에 조건에 맞는 값이 없으면 NULL 로 표시한다.
결국 JOIN의 왼쪽 테이블의 값은 모두 표시된다.

 

4. RIGHT JOIN

mysql> SELECT * FROM T1 RIGHT JOIN T2 ON T1.id = T2.id
+------+-------------+-------------+
| id   | name        | comment     |
+------+-------------+-------------+
| 1    | honggil     | hohoho      |
| 2    | nari        | yo!!        |
+------+-------------+-------------+
 
LEFT JOIN의 반대.
즉, JOIN의 오른쪽에 있는 테이블을 기준으로, JOIN의 왼쪽 테이블에 조건에 맞는 값이 없으면 NULL 로 표시한다.
결국 JOIN의 오른쪽 테이블의 값은 모두 표시된다.

이 밖에 여러가지 JOIN이 있지만 생략한다.


JOIN을 하면 간편하긴 하지만 Slow Query의 원인이 되는 경우가 많다.
어떤 쿼리가 Slow Query인지 찾기 위해 로그를 쌓는 방법을 알아보자.

1. 데몬을 실행시킬 때 Slow Query Log를 적용하는 방법
MySQL 데몬 실행 시 --log-slow-queries=[저장할파일명] 라는 옵션을 준다. [저장할파일명]은 원하는 위치의 파일명을 입력하면 된다.

/etc/init.d/mysqld 파일의  'start' 에서 아래 부분을 찾아 옵션을 추가한 후 서버를 restart하면 된다.

$bindir/mysqld_safe --datadir=$datadir --pid-file=$pid_file --log-slow-queries=slow_query.log  >/dev/null 2>&1 &


2. 설정 파일에 Slow Query Log를 적용하는 방법
my.cnf 파일에 설정사항을 입력할 수가 있다.
일반적으로 my.cnf 파일은 /etc 아래에 존재한다.
아래의 내용을 적당한 위치에 기재하고 서버를 restart하면 된다.

long_query_time = 3   # 3초 이상 시간이 걸리는 Query
log-slow-queries=[저장할파일명]


[저장할파일명]은 텍스트로 저장되므로 아무 에디터에서나 열수 있고, 쌓이는 내용은 어렵지 않으므로 쉽게 알 수 있을 것이다.
Posted by 달팽이맛나
,


date_add와 date_sub의 종류는

 second - 초를 추가 합니다(interval 1 second)

minute - 분을 추가 합니다.(interval 1 minute)
hour - 시간을 추가 합니다.(interval 1 hour)
day - 일을 추가 합니다.(interval 1 day)
month - 달을 추가 합니다. .(interval 1 month)
year - 년을 추가 합니다. .(interval 1 year)
minute_second - 분과 초를 추가 합니다. (interval "1:1" minute_second)
hour_minute - 시간과 분을 추가 합니다. (interval "1:1" hour_minute)
day_hour - 일과 시간을 추가 합니다. (interval "1 1" day_hour)
year_month - 년과 월을 추가 합니다. (interval "1-1" year_month)
hour_second - 시간과 분, 초를 추가 합니다. (interval "1:1:1" hour_second)
day_minute - 일과 시간, 분을 추가 합니다.(interval "1 1:1" day_minute)
day_second - 일과 시간, 분, 초를 추가 합니다.(interval "1 1:1:1" day_second)

Posted by 달팽이맛나
,

[JavaScript] URL 알아내기

2009. 9. 8. 14:55

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.


wget 사용법

* 이 글은 유닉스 관련 운영체제의 경험이 별로 없는 사람을 위한 wget 의 간단한 지침서입니다.

1. wget ?

코맨드 라인에서 파일 다운로드를 쉽게 할 수 있는 유틸리티이다. 요즘의 대부분의 리눅스 배포판에는 기본적으로 설치되어 있다.

2. Windows 버전의 wget

* win32 에서 사용가능한 바이너리도 존재한다. 여기를 참조.

- 위의 바이너리를 다운로드하고 윈도우 환경변수에서 PATH에 잡혀 있는 폴더로 복사해 둔다.
- "어떻게 윈도우에서 PATH를 잡나요?" 라고 생각하시는 분이라면 여기를 참조할 것.
- "PATH가 뭐지요?" 라고 생각하는 분이라면 걍 C:Windows 폴더로 복사한다. -.-;;;

3. 기본적인 사용법
 
- PATH가 걸린 상태에서 '명령 프롬프트' 를 부르고 'wget' 을 입력한다.
* 보다 명령 프롬프트를 쉽게 사용하려면 여기를 참조.

- "wget --help" 라고 하면 사용할 수 있는 옵션이 출력된다.


4. 중요한 옵션 및 사용예
 
- r : 지정된 디렉토리의 하위 디렉토리의 내용을 몽땅 다운로드한다.

사용예 : wget -r ftp://ftp.ncbi.nlm.nih.gov/blast/db/ 
설명 : 이렇게 하면 ftp://ftp.ncbi.nlm.nih.gov/blast/db/ 의 디렉토리 구조를 유지한 채로 모든 파일을 불러온다.

- nd : 디렉토리를 만들지 않는다. 계층적으로 나열된 웹 사이트의 디렉토리의 내용을 한 디렉토리로 불러올 때 편리하다. -r 옵션과 같이 사용하면 매우 유용하다.

사용예 : wget -nd -r ftp://ftp.ncbi.nlm.nih.gov/blast/db/ 
설명 : 이렇게 하면 ftp://ftp.ncbi.nlm.nih.gov/blast/db/  내의 내용물을 현재 폴더에 몽땅 다운로드받는다.

-A, --accept=: 지정된 확장자의 파일만을 받아온다.

사용예 : wget -nd -r --accept=fna ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria/

설명 : 이렇게 하면 ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria 에서 확장자가 .fna 인 파일만을 받아서 현재 디렉토리에 저장한다. (물론 -nd 옵션을 빼면 폴더 구조가 그대로 유지된다)


-R, --reject=: 지정된 확장자의 파일만을 빼고 받아온다.

사용예 : wget -nd -r --accept=fna ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria/

설명 : 이렇게 하면 ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria 에서 확장자가 .fna 인 파일만 빼고 받아서 현재 디렉토리에 저장한다.

-l , --level= : -r 옵션, 즉 하위 디렉토리 받아오기를 사용하였을 때 다운로드받을 최대 단계를 지정할 때 사용한다.

사용예 : wget -nd -r --accept=fna --level=3 ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria/ 
설명 : ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria 에서 확장자가 .fna 인 파일만 빼고 받고 3단계까지 거슬러 올라서 다운로드를 수행한다.

-N : 현재 다운로드 받을 위치에 있는 파일이 현재 내 하드에 있는 파일보다 새로운 파일일때만 다운로드를 수행한다

-m : 미러 명령. 즉, 특정한 웹사이트의 내용을 그대로 폴더 구조채 긁어오되, 새로 업데이트한 내용만을 다운받고 싶을 때 사용한다.

ftp id, password 지정 : wget ftp://id:password@website



이런 식으로 id와 password를 지정하면 된다

이 정도면 많이 사용하는 옵션에 대해서는 설명했을 것이다. 기타 명령어에 대해서는 wget --help 를 입력하면 자세한 설명이 나와 있다.


5. 기타
 
- 쉘 스크립트나 배치 파일을 이용하여 자동화하면 매우 편리하다.

가령 유닉스 계열이라면

#!/bin/bash
wget -nd -r ftp://id:password@website/$1/


과 같은 식으로 간단한 쉘 스크립트를 작성하여 적당한 이름으로 저장한 후, Path가 걸린 위치에 넣고 실행 권한 (chmod +x <파일이름>) 을 주면 매우 편리하다.

윈도우라면

echo off
wget -nd -r  ftp://id:password@website/%1/


과 같은 식으로 배치 파일을 만들고 *.bat 으로 저장한다.

- 도스 시절부터 PC를 이용해 온 사람이라면 배치 파일에 대해서 잘 알고 있겠지만, 그렇지 않은 사람은 여기 를 참조해 보기를. 사실 유닉스 계열의 쉘 스크립트만큼 강력하지는 않고,윈도우 자체가 GUI 환경 위주의 운영체계이긴 하지만 그래도 어느 정도 수준의 일은 윈도우에서도 처리 가능하다. 사실 Win98 같은 구닥다리 말고 NT 계열의 운영체제 (NT, 2000, XP) 는 결코 널럴하게 만들어진 운영 체제는 아니다. 아마도  NT 계열 MS 운영체제가 유닉스 계열에 대해서 가지는 가장 큰 약점은 '유저의 평균수준' 일지도 모른다. -.-;;
Posted by 달팽이맛나
,