본문 바로가기
데이터 베이스/MySQL

MYSQL 커서&CURSOR 예제&문제 16

by 인생여희 2016. 12. 20.
반응형

커서


커서는 테이블의 여러행을 쿼리한 후에, 쿼리의 결과인 행 집합을 한 행씩 처리하기 위한 방식


순서 : 파일을 연다 -> 처음 데이터를 읽는다. -> 파일의 끝까지 반복한다. -> 파일을 닫는다. 


명령어


커서선언 DECLARE CURSOR


반복조건 선언 DECLARE CONTINUE HANDLER

(더이상 읽을 행이 없을 경우에, 실행할 내용 설정)



커서 열기 OPEN


커서에서 데이터 가져오기 FETCH

(LOOP ~ END LOOP 문으로 반복 구간 지정 )


데이터 처리 


커서닫기 




-- 예제 테이블 생성 및 데이타 생성 


CREATE TABLE Member (

   userid VARCHAR(20),

   `point` INT

) ENGINE = InnoDB ROW_FORMAT = DEFAULT;




insert into Member (UserID ) VALUES ( 'User01') ; 

insert into Member (UserID ) VALUES ( 'User02') ; 

insert into Member (UserID ) VALUES ( 'User03') ; 

insert into Member (UserID ) VALUES ( 'User04') ; 

commit;



select * from Member;





drop PROCEDURE if EXISTS curdemo


delimiter $$

CREATE PROCEDURE curdemo()

BEGIN

  DECLARE done boolean DEFAULT FALSE;

  DECLARE vRowCount INT DEFAULT 0 ;

  DECLARE vUserID varchar(20);

  

  -- 커서로 만들 데이타 값들

  DECLARE cur1 CURSOR FOR SELECT Userid FROM Member;

 

  -- 커서가 마지막에 도착할 때의 상태값

  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;


  -- 커서를 연다. 

  OPEN cur1;

 

  -- Loop 가 돌아간다. 

  read_loop: LOOP

 

  -- 커서로 만드어진 데이타를 돌린다. 

  FETCH cur1 INTO vUserID ;

 

    SET vRowCount = vRowCount +1 ; 

    

    -- 커서가 마지막 로우면 Loop를 빠져나간다. 

    IF done THEN

      LEAVE read_loop;

    END IF;

 

  END LOOP;


  SELECT vRowCount  ; 

  -- 커서를 닫는다. 

  CLOSE cur1;

 

END;

delimiter;



Call curdemo() ; 







 -- 예제 테이블 생성


CREATE TABLE PointHistory (

   UserID VARCHAR(20),

   PointDate DATE,

   PointValue INT

) ENGINE = InnoDB ROW_FORMAT = DEFAULT;


-- 예제 데이타 생성


insert into PointHistory (  UserID  ,PointDate  ,PointValue) 

VALUES (   'User02'   ,'2014-01-01'  , 10  )



-- 이미 있는 프로시져 삭제한다. ..


DROP PROCEDURE IF EXISTS curdemo ;

delimiter $$

CREATE PROCEDURE curdemo()

BEGIN

  DECLARE done INT DEFAULT FALSE;

  DECLARE vRowCount INT DEFAULT 0 ;

  DECLARE vUserID varchar(20);

  DECLARE vPointValue int ; 

    

  DECLARE cur1 CURSOR FOR SELECT Userid FROM Member;

 

  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;


  OPEN cur1; 


  read_loop: LOOP

 

  FETCH cur1 INTO vUserID ;

  

    -- Not Found Handler 값 변화 살펴보자.

  SELECT done ;  

 

    -- 포인트 테이블의 값을 읽어 온다. 

    SELECT PointValue into vPointValue FROM pointhistory

    WHERE UserID = vUserID ; 

    

    --  회원테이블의 포인트 값에 업데이트 한다. 

    UPDATE Member Set point = vPointValue WHERE UserID = vUserID ; 

    

    -- 커서가 몇번을 도는지 알아 본다. 

    SET vRowCount = vRowCount + 1  ; 

    

    IF done THEN

      LEAVE read_loop;

    END IF;

 

  END LOOP;


  SELECT vRowCount ; 

  CLOSE cur1;

 

END;

delimiter;



Call curdemo() ;



-- 실행 결과 없다.  → 우리가 원하는 바가 아니다. vRowCount 가 1이다. 1번 돌았다.




-- 3. 해결 방법 

-- 

-- - 커서의 DECLARE CONTINUE HANDLER FOR NOT FOUND 는 커서의 집합이 없을 때이기도 하지만 커서안에서 다른 쿼리문의 집합이 없을 때도 True 을 반환한다. 

-- - 그래서 Mysql 은 커서가 이상해 이런 얘기가 나오는 거다. 

-- - MSSQL은 커서의 집합만을 비교하여 마지막 커서행인지 판단해 주는데 mysql 의 경우 커서뿐만 아니라 커서안의 select 의 집합도 NOT FOUND로 판단하고 있다. 

-- - 그래서 커서안의 select  의 집합의  NOT FOUND와 Curosor 의 NOT FOUND을 구분하여 줄 필요가 있다. 



DROP PROCEDURE IF EXISTS curdemo ;

delimiter $$

CREATE PROCEDURE curdemo()

BEGIN

  DECLARE done INT DEFAULT FALSE;

  DECLARE vRowCount INT DEFAULT 0 ;

  DECLARE vUserID varchar(20);

  DECLARE vPointValue int ;   

  

  DECLARE cur1 CURSOR FOR SELECT Userid FROM Member;

 

  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done =TRUE ;


  OPEN cur1; 


  REPEAT

 

  FETCH cur1 INTO vUserID ;


  -- Not Found Handler 값 변화 살펴보자.

  SELECT done ;  


    -- 커서가 마지막이 아니라면    

    IF NOT done THEN      

    

      SELECT PointValue into vPointValue FROM pointhistory

      WHERE UserID = vUserID ; 

    

      UPDATE Member Set point = vPointValue WHERE UserID = vUserID ; 

      -- SELECT concat(vUserID, '', vPointValue)  ; 

    

      SET vPointValue = 0 ; 


      -- 위의 select 가 조회 데이타가 없어서 not found 되어

      -- fetch 문을 빠져나가는 걸 방지한다.  

      SET done = False ;        


    END IF;  

  

  UNTIL DONE END REPEAT;

  CLOSE cur1;

END;

delimiter;


-- 위의 예제는 커서가 마지막행을 만나기 전에 SELECT 문에서 조회값이 없는 경우 Not Found 도 발생하는 걸 인위적으로  SET done = False 으로 해결 하고 있다. 


Call curdemo() 



출처http://bizadmin.tistory.com/entry/MySQL-Fetch-Cursor-%EB%AC%B8-%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
커서
 
 
 
커서는 테이블의 여러행을 쿼리한 후에, 쿼리의 결과인 행 집합을 한 행씩 처리하기 위한 방식
 
 
 
순서 : 파일을 연다 -> 처음 데이터를 읽는다. -> 파일의 끝까지 반복한다. -> 파일을 닫는다. 
 
 
 
명령어
 
 
 
커서선언 DECLARE CURSOR
 
 
 
반복조건 선언 DECLARE CONTINUE HANDLER
 
(더이상 읽을 행이 없을 경우에, 실행할 내용 설정)
 
 
 
 
 
커서 열기 OPEN
 
 
 
커서에서 데이터 가져오기 FETCH
 
(LOOP ~ END LOOP 문으로 반복 구간 지정 )
 
 
 
데이터 처리 
 
 
 
커서닫기 
 
 
 
 
 
 
 
-- 예제 테이블 생성 및 데이타 생성 
 
 
 
CREATE TABLE Member (
 
   userid VARCHAR(20),
 
   `point` INT
 
ENGINE = InnoDB ROW_FORMAT = DEFAULT;
 
 
 
 
 
 
 
insert into Member (UserID ) VALUES ( 'User01') ; 
 
insert into Member (UserID ) VALUES ( 'User02') ; 
 
insert into Member (UserID ) VALUES ( 'User03') ; 
 
insert into Member (UserID ) VALUES ( 'User04') ; 
 
commit;
 
 
 
 
 
select * from Member;
 
 
 
 
 
 
 
 
 
drop PROCEDURE if EXISTS curdemo
 
 
 
delimiter $$
 
CREATE PROCEDURE curdemo()
 
BEGIN
 
  DECLARE done boolean DEFAULT FALSE;
 
  DECLARE vRowCount INT DEFAULT 0 ;
 
  DECLARE vUserID varchar(20);
 
  
 
  -- 커서로 만들 데이타 값들
 
  DECLARE cur1 CURSOR FOR SELECT Userid FROM Member;
 
 
 
  -- 커서가 마지막에 도착할 때의 상태값
 
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
 
 
 
  -- 커서를 연다. 
 
  OPEN cur1;
 
 
 
  -- Loop 가 돌아간다. 
 
  read_loop: LOOP
 
 
 
  -- 커서로 만드어진 데이타를 돌린다. 
 
  FETCH cur1 INTO vUserID ;
 
 
 
    SET vRowCount = vRowCount +1 ; 
 
    
 
    -- 커서가 마지막 로우면 Loop를 빠져나간다. 
 
    IF done THEN
 
      LEAVE read_loop;
 
    END IF;
 
 
 
  END LOOP;
 
 
 
  SELECT vRowCount  ; 
 
  -- 커서를 닫는다. 
 
  CLOSE cur1;
 
 
 
END;
 
delimiter;
 
 
 
 
 
Call curdemo() ; 
 
 
 
 
 
 
 
 
 
 
 
 
 
 -- 예제 테이블 생성
 
 
 
CREATE TABLE PointHistory (
 
   UserID VARCHAR(20),
 
   PointDate DATE,
 
   PointValue INT
 
ENGINE = InnoDB ROW_FORMAT = DEFAULT;
 
 
 
-- 예제 데이타 생성
 
 
 
insert into PointHistory (  UserID  ,PointDate  ,PointValue) 
 
VALUES (   'User02'   ,'2014-01-01'  , 10  )
 
 
 
 
 
-- 이미 있는 프로시져 삭제한다. ..
 
 
 
DROP PROCEDURE IF EXISTS curdemo ;
 
delimiter $$
 
CREATE PROCEDURE curdemo()
 
BEGIN
 
  DECLARE done INT DEFAULT FALSE;
 
  DECLARE vRowCount INT DEFAULT 0 ;
 
  DECLARE vUserID varchar(20);
 
  DECLARE vPointValue int ; 
 
    
 
  DECLARE cur1 CURSOR FOR SELECT Userid FROM Member;
 
 
 
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
 
 
 
  OPEN cur1; 
 
 
 
  read_loop: LOOP
 
 
 
  FETCH cur1 INTO vUserID ;
 
  
 
    -- Not Found Handler 값 변화 살펴보자.
 
  SELECT done ;  
 
 
 
    -- 포인트 테이블의 값을 읽어 온다. 
 
    SELECT PointValue into vPointValue FROM pointhistory
 
    WHERE UserID = vUserID ; 
 
    
 
    --  회원테이블의 포인트 값에 업데이트 한다. 
 
    UPDATE Member Set point = vPointValue WHERE UserID = vUserID ; 
 
    
 
    -- 커서가 몇번을 도는지 알아 본다. 
 
    SET vRowCount = vRowCount + 1  ; 
 
    
 
    IF done THEN
 
      LEAVE read_loop;
 
    END IF;
 
 
 
  END LOOP;
 
 
 
  SELECT vRowCount ; 
 
  CLOSE cur1;
 
 
 
END;
 
delimiter;
 
 
 
 
 
Call curdemo() ;
 
 
 
 
 
-- 실행 결과 없다.  → 우리가 원하는 바가 아니다. vRowCount 가 1이다. 1번 돌았다.
 
 
 
 
 
 
 
-- 3. 해결 방법 
 
-- 
 
-- - 커서의 DECLARE CONTINUE HANDLER FOR NOT FOUND 는 커서의 집합이 없을 때이기도 하지만 커서안에서 다른 쿼리문의 집합이 없을 때도 True 을 반환한다. 
 
-- - 그래서 Mysql 은 커서가 이상해 이런 얘기가 나오는 거다. 
 
-- - MSSQL은 커서의 집합만을 비교하여 마지막 커서행인지 판단해 주는데 mysql 의 경우 커서뿐만 아니라 커서안의 select 의 집합도 NOT FOUND로 판단하고 있다. 
 
-- - 그래서 커서안의 select  의 집합의  NOT FOUND와 Curosor 의 NOT FOUND을 구분하여 줄 필요가 있다. 
 
 
 
 
 
DROP PROCEDURE IF EXISTS curdemo ;
 
delimiter $$
 
CREATE PROCEDURE curdemo()
 
BEGIN
 
  DECLARE done INT DEFAULT FALSE;
 
  DECLARE vRowCount INT DEFAULT 0 ;
 
  DECLARE vUserID varchar(20);
 
  DECLARE vPointValue int ;   
 
  
 
  DECLARE cur1 CURSOR FOR SELECT Userid FROM Member;
 
 
 
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done =TRUE ;
 
 
 
  OPEN cur1; 
 
 
 
  REPEAT
 
 
 
  FETCH cur1 INTO vUserID ;
 
 
 
  -- Not Found Handler 값 변화 살펴보자.
 
  SELECT done ;  
 
 
 
    -- 커서가 마지막이 아니라면    
 
    IF NOT done THEN      
 
    
 
      SELECT PointValue into vPointValue FROM pointhistory
 
      WHERE UserID = vUserID ; 
 
    
 
      UPDATE Member Set point = vPointValue WHERE UserID = vUserID ; 
 
      -- SELECT concat(vUserID, '', vPointValue)  ; 
 
    
 
      SET vPointValue = 0 ; 
 
 
 
      -- 위의 select 가 조회 데이타가 없어서 not found 되어
 
      -- fetch 문을 빠져나가는 걸 방지한다.  
 
      SET done = False ;        
 
 
 
    END IF;  
 
  
 
  UNTIL DONE END REPEAT;
 
  CLOSE cur1;
 
END;
 
delimiter;
 
 
 
-- 위의 예제는 커서가 마지막행을 만나기 전에 SELECT 문에서 조회값이 없는 경우 Not Found 도 발생하는 걸 인위적으로  SET done = False 으로 해결 하고 있다. 
 
 
 
Call curdemo() 
 
 
 
 
 
출처: http://bizadmin.tistory.com/entry/MySQL-Fetch-Cursor-%EB%AC%B8-%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95
 
 
 
 
 
 
 
 
 
 
cs



반응형

댓글