[퀴즈] SQL 문(SELECT) 에서 TABLE 목록 추출 0 8 25,787

by park1q 정규식 [2010.10.06 16:34:43]


퀴즈 낼 실력이 될지 모르겠지만
재미있을것 같아서 한번 내봅니다.

활용 범위를 막 생각했는데..두가지 밖에 생각이 안나네요.

1. CRUD MATRIX 작성시에 (이것만 그려주는 솔루션도 예전에 본적 있습니다.)
2. SQL 작성 툴 개발시 TABLE DESCRIPTION 도우미 모듈.

문제는 간단합니다. SQL문(SELECT 문)에서 TABLE LIST 를 종으로 출력하는 문제입니다.

편의상 테이블을 만드시고 하시는것이 좋을듯합니다.

WITH T as (
select 'SELECT * FROM TAB1, TAB2 WHERE TAB1.COL1 = TAB2.COL2 AND COL1 = (SELECT COL2 FROM TAB3 WHERE TAB3.COL3 IN(SELECT COL4 FROM TAB4))' from dual union all
select 'SELECT (SELECT DEPT_NAME FROM DEPT WHERE DEPT_ID=E.DEPT_ID),EMP_ID FROM EMP' from dual
)
select ~~~ from t ;

SUBSTR(ORG_TXT,1,30) TABLE_NAME
SELECT (SELECT DEPT_NAME FROM  DEPT
SELECT (SELECT DEPT_NAME FROM  EMP
SELECT * FROM TAB1, TAB2 WHERE  TAB1
SELECT * FROM TAB1, TAB2 WHERE  TAB2
SELECT * FROM TAB1, TAB2 WHERE  TAB3
SELECT * FROM TAB1, TAB2 WHERE  TAB4



이렇게 만드는것입니다.

일단 모든 SQL 이 아닌 SELECT 문장만 있다는 전제입니다. 뭐..다른 SQL 은 select 보다 쉬울거라 생각이 듭니다.
참고로 SELECT 'SELECT * FROM TAB' FROM DUAL 같은 SQL 은 DUAL 만 출력되어야 합니다.

제가 한것도 있지만 좀더 나은 작품이 나올것으로 기대 됩니다. *^^*



모범답안은 마농님이 써 놓으셨네요. 제 답보다 훨씬 깔끔하고 확하네요..
감사드립니다.
여기 퀴즈보고 슬쩍 배운 실력으로 문제를 내봤는데..
파서를 만든다는게..쉬운일이 아니란걸 알았네요.
참고로 약간의 예외상황이 있을수 있습니다..*^^*

많은 분들 감사 드립니다.

출제자의 답]

SELECT ORG_TXT, regexp_substr(replace(nvl(regexp_substr(V1, ',?[^,]+,?', 1, l.lvl),
v1),
','),
'\S+') table_name
  FROM (SELECT ORG_TXT,
   REGEXP_REPLACE(
   REGEXP_REPLACE(
   REGEXP_REPLACE(
   REGEXP_REPLACE(
   REGEXP_REPLACE(
   REGEXP_REPLACE(
   ORG_TXT,'''[^'']+''',' '),
   ' FROM ','$') ,
   ' SAMPLE[ (]| WHERE[ (]| CONNECT | GROUP | HAVING[ (]| START |\(|\)',' ₩ '),
   '$([^₩]+)₩|.',',\1'),
   ',{1,}',','),
   ', +,',',') V1
    FROM T) T ,
    (select level lvl from dual connect by level <= 10) l
 WHERE L.LVL <= LENGTH(V1) - LENGTH(REPLACE(V1, ',')) - 1
 ORDER BY ORG_TXT, LVL

참고 :

SELECT

[ hint ]
[ { { DISTINCT | UNIQUE } | ALL } ] select_list FROM { table_reference | join_clause | ( join_clause ) }
[ , { table_reference | join_clause | (join_clause) } ] ...
  [ where_clause ]
  [ hierarchical_query_clause ]
  [ group_by_clause ]
  [ HAVING condition ]
  [ model_clause ]


보완했습니다. 모든 SQL 적용이 가능하도록 혹시 예외를 확인하셨다면..댓글 부탁 드립니다.
select org_txt org_txt ,
   lv ,
   regexp_substr(regexp_substr(v, '[^,]+', 1, lv), '[^ ]+') table_name
  from (select org_txt, regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(
   regexp_replace(upper(org_txt),'F|W|C', ' '),
   '(\\r\\n|\\r|\\n)', ' ') , -- 다중행을 1행으로
   '/[^/]*/',' '),     -- 주석 제거
   '''[^'']*''',' '), -- String 제거
   '\(',' ('),     -- 괄호 띄어 쓰기
   '\)',') '),     -- 괄호 띄어 쓰기
   ' FROM ',' F '),    -- FROM 을 F 로
   'INSERT.*INTO',' F '), -- INSERT INTO 를 F 로
   'SELECT ',' W '),   -- SELECT 를  W (SUBQUERY)
   'UPDATE ',' F '),   -- UPDATE 를 FROM 으로
   ' TABLE',' C '),    -- TABLE 함수는 TABLE이 아님
   ' SET | UNION | WHERE |GROUP BY | HAVING | CONNECT BY | START WITH | MODEL | SAMPLE( )*\(|USING( )*\(| ON|\)|$', ' W '),
   'F([^F|W]+)W|.','C\1'),
   '( FULL| LEFT| RIGHT| CROSS| NATURAL| INNER)?( OUTER)? JOIN ', 'C'),
   'C{1,}','C'),
   '\([^C]+C',''),
   'C',',') v
  from t),   
  (select level lv from dual connect by level <= 9)
    where lv <= length(v) - length(replace(v, ',')) - 1
 order by org_txt, lv 


JAVA CODE
   concatedTables = orgSqlText.toUpperCase()
   .replaceAll("F|W|C", " ")
   .replaceAll("(\\r\\n|\\r|\\n)", " ")
   .replaceAll("/[^/]*/"," ")
   .replaceAll("'[^']*'"," ")
   .replaceAll("\\("," (")
   .replaceAll("\\)",") ")
   .replaceAll(" FROM "," F ")
   .replaceAll("INSERT.*INTO"," F ")
   .replaceAll("SELECT"," W ") //
   .replaceAll("UPDATE "," F ")
   .replaceAll(" TABLE"," C ")
   .replaceAll(" SET | UNION | WHERE |GROUP BY | HAVING | CONNECT BY | START WITH | MODEL | SAMPLE( )*\\(|USING( )*\\(| ON|\\)|$", " W ")
   .replaceAll("F([^F|W]+)W|.","C$1")
   .replaceAll("( FULL| LEFT| RIGHT| CROSS| NATURAL| INNER)?( OUTER)? JOIN ", "C")
   .replaceAll("C{1,}","C")
   .replaceAll("\\([^C]+C","")
   .replaceAll("C",",")
   ;

by v상이v [2010.10.08 09:33:37]
아...전 감도 안오네요...
어려운데요 이거....-ㅅ-;;;

by park1q [2010.10.08 13:13:19]
어려울줄 알았는데..
v상이v님 실력이믄 충분히 가능할겁니다.
사실 조금 노가다 처럼 보이긴 합니다만ㅠㅠ
파서 만들실력은 안되고 해서리..간단하게 만들어 봤는데..100% 파싱할거라는 자신은 없습니다만..조금씩 보완하면..될꺼 같더군요.
제대로 된 파서라면 SQL 의 유효성까지 확인이 가능해야 하는데..이런건 불가능 하겠지요..

제가 푼 절차는 다음과 같습니다.

1. FROM 으로 문장을 분리한다.
FROM [TABLE LIST] "(" | "WHERE" | "CONNECT" | "UNION"
2. ,로 연결된 [TABLE LIST] TABLE을 분리한다. 단 ALIAS 가 있을때는 첫번째 단어만 취한다..

11G 는 REGEXP_COUNT 로 쉽게 해결될수도 있을것 같고..
저는 10G 기준으로 작성했습니다.

저같이 안하고 한다면..속도가 엄청 빠를것 같은데..ㅠㅠ

by 마농 [2010.10.08 13:47:04]
WITH t AS
(
SELECT 'SELECT * FROM TAB1, TAB2 WHERE TAB1.COL1 = TAB2.COL2 AND COL1 = (SELECT COL2 FROM TAB3 WHERE TAB3.COL3 IN(SELECT COL4 FROM TAB4))' org_txt FROM dual
UNION ALL SELECT 'SELECT (SELECT DEPT_NAME FROM DEPT WHERE DEPT_ID=E.DEPT_ID),EMP_ID FROM EMP' FROM dual
UNION ALL SELECT 'SELECT ''SELECT * FROM DUAL'' FROM DUAL' FROM dual
UNION ALL SELECT 'SELECT * FROM (SELECT * FROM DUAL a, DUAL b) a, (SELECT * FROM DUAL) b' FROM dual
UNION ALL SELECT 'SELECT * FROM DUAL SAMPLE(10)' FROM dual
UNION ALL SELECT 'SELECT * FROM EMP SAMPLE (10)' FROM dual
)
SELECT SUBSTR(org_txt, 1, 30) org_txt
, lv
, REGEXP_SUBSTR(REGEXP_SUBSTR(v, '[^,]+', 1, lv), '[^ ]+') v
FROM
(
SELECT org_txt,
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(org_txt
, '''[^'']*''')
, ' FROM ', ' F ')
, ' WHERE | GROUP BY | HAVING | CONNECT BY | START WITH | MODEL | SAMPLE( )*\(|\)|$', ' W ')
, 'F([^FW]+)W|.', ',\1')
, ',{1,}', ',') v
FROM t
)
, (SELECT LEVEL lv FROM dual CONNECT BY level <= 9)
WHERE lv <= LENGTH(v) - LENGTH(REPLACE(v, ',')) - 1
ORDER BY org_txt, lv
;

by v상이v [2010.10.09 00:26:12]
이미 퇴근해서...월요일에나 다시 해바야 겠어요...
집에선 해볼수가 없어서...

by 마농 [2010.10.12 10:46:46]
ANSI JOIN 구문에 대해서 빠트렸네요.

WITH t AS
(
SELECT 'SELECT * FROM TAB1, TAB2 WHERE TAB1.COL1 = TAB2.COL2 AND COL1 = (SELECT COL2 FROM TAB3 WHERE TAB3.COL3 IN(SELECT COL4 FROM TAB4))' org_txt FROM dual
UNION ALL SELECT 'SELECT (SELECT DEPT_NAME FROM DEPT WHERE DEPT_ID=E.DEPT_ID),EMP_ID FROM EMP' FROM dual
UNION ALL SELECT 'SELECT ''SELECT * FROM DUAL'' FROM DUAL' FROM dual
UNION ALL SELECT 'SELECT * FROM (SELECT * FROM DUAL a, DUAL b) a, (SELECT * FROM DUAL) b' FROM dual
UNION ALL SELECT 'SELECT * FROM DUAL SAMPLE(10)' FROM dual
UNION ALL SELECT 'SELECT * FROM EMP SAMPLE (10)' FROM dual
UNION ALL SELECT 'SELECT * FROM EMP e JOIN DEPT d ON e.deptno = d.deptno' FROM dual
UNION ALL SELECT 'SELECT * FROM EMP e LEFT OUTER JOIN DEPT d ON e.deptno = d.deptno' FROM dual
UNION ALL SELECT 'SELECT * FROM EMP e CROSS JOIN DEPT d' FROM dual
)
SELECT SUBSTR(org_txt, 1, 30) org_txt
, lv
, REGEXP_SUBSTR(REGEXP_SUBSTR(v, '[^,]+', 1, lv), '[^ ]+') v
, v
FROM
(
SELECT org_txt,
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE(org_txt
, '''[^'']*''')
, ' FROM ', ' F ')
, ' WHERE | GROUP BY | HAVING | CONNECT BY | START WITH | MODEL | SAMPLE( )*\(|USING( )*\(| ON|\)|$', ' W ')
, 'F([^FW]+)W|.', ',\1')
, ' (FULL|LEFT|RIGHT|CROSS|NATURAL|INNER)( OUTER)? JOIN ' , ',')
, ',{1,}', ',') v
FROM t
)
, (SELECT LEVEL lv FROM dual CONNECT BY level <= 9)
WHERE lv <= LENGTH(v) - LENGTH(REPLACE(v, ',')) - 1
ORDER BY org_txt, lv
;

by park1q [2010.10.12 11:27:46]
아~~마농님..대단 하시네요..
ANSI JOIN 까지 되도록 만드셨네요.
이걸로 왠만한 접근제어 / 감사 프로그램도 가능할듯 싶네요.
시간날때 java 로 한번 옮겨 보겠습니다.

by 마농 [2011.01.12 10:23:48]
최근 올라온 질문에 해당하는 쿼리에서는 정확한 결과가 안나오네요.
http://www.gurubee.net/article/51606

'SELECT T.COL1, T.COL2, S.COL1, S.COL2 FROM TAB1 T, (SELECT A.COL1, B.COL2 FROM (SELECT COL2 FROM SUB2) A, SUB1 B) S where 1 = 1'

by park1q [2011.01.13 14:06:40]
도저히..루핑없이 정규식만으로는 해결을 못한다는 결론만 내렸습니다.
더군다나 ansi join 의 경우도 테이블이 나올수도 있고, 서브쿼리로 나올수도 있으며, select 에서 사용되는 스칼라 서브쿼리도 N depth 이상 또는 그 안에서 서브쿼리도 나올수 있고..
나중에 시간되면..다시 시도해 보겠습니다.
댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입