PRAGMA AUTONOMOUS_TRANSACTION;
The AUTONOMOUS_TRANSACTION pragma instructs the PL/SQL compiler to mark a routine as autonomous (independent). An autonomous transaction is an independent transaction started by another transaction, the main transaction. Autonomous transactions let you suspend the main transaction, do SQL operations, commit or roll back those operations, then resume the main transaction.
The AUTONOMOUS_TRANSACTION pragma instructs the PL/SQL compiler to mark a routine as autonomous (independent). An autonomous transaction is an independent transaction started by another transaction, the main transaction. Autonomous transactions let you suspend the main transaction, do SQL operations, commit or roll back those operations, then resume the main transaction.
Unlike regular triggers, autonomous triggers
can contain transaction control statements such as COMMIT andROLLBACK. Also, unlike regular triggers, autonomous
triggers can execute DDL statements (such as CREATEand DROP) using native dynamic
SQL. Changes made by an autonomous transaction become visible to other
transactions when the autonomous transaction commits. The changes also become
visible to the main transaction when it resumes, but only if its isolation
level is set to READ COMMITTED (the default).
1
2
3
4
5
6
7
8
|
CREATE TRIGGER parts_trigger
BEFORE INSERT ON parts
FOR EACH ROW
DECLARE
PRAGMA
AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO parts_log
VALUES(:new.pnum, :new.pname);
COMMIT; -- allowed
only in autonomous triggers
END;
|
Pl/SQL
script for VARRAYS
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
|
CREATE OR REPLACE PROCEDURE TEST_TYPE_VARRAY
IS
TYPE
LV_ARRAY_TYPE IS VARRAY(10) OF
PLS_INTEGER;
LV_ARRAY
LV_ARRAY_TYPE := LV_ARRAY_TYPE();
LV_ARRAY_2
LV_ARRAY%TYPE;
/*------------------------------------------------------------------------*/
/*initialize
the lv_array using a
Constructor.----------------------------*/
/*Else 'reference
to uninitialized collection is thrown-------------------------------------*/
/*-----------------------------------------------------------------------------------------------*/
BEGIN
FOR I IN
1 .. 10 LOOP
LV_ARRAY.EXTEND;
/*-------------------------------------------------------------*/
/*EXTEND
is required to add elements to the varray.
-----------*/
/*Should
not exceed size of
VARRAY-----------------------------*/
/*----------------------------------------------------------------------------*/
LV_ARRAY(I)
:= I;
END
LOOP;
LV_ARRAY_2
:= LV_ARRAY_TYPE(11, 12, 13, 14, 15);
DBMS_OUTPUT.PUT_LINE('LV_ARRAY');
FOR I IN
LV_ARRAY.FIRST .. LV_ARRAY.LAST LOOP
DBMS_OUTPUT.PUT_LINE(LV_ARRAY(I));
END
LOOP;
DBMS_OUTPUT.PUT_LINE('------------------------------');
/*--------------------------------------------------------------------------------------------------*/
/*Prints
1,2,3,4,5,6,7,8,9,10.---------*/
/*--------------------------------------------------------------------------------------------------*/
DBMS_OUTPUT.PUT_LINE('LV_ARRAY_2');
FOR I IN
LV_ARRAY_2.FIRST .. LV_ARRAY_2.LAST LOOP
DBMS_OUTPUT.PUT_LINE(LV_ARRAY_2(I));
END
LOOP;
DBMS_OUTPUT.PUT_LINE('------------------------------');
/*----------------------------------------------------------------------------------*/
/*Prints
11,12,13,14,15---------*/
/*------------------------------------------------------------------------------------*/
LV_ARRAY_2.EXTEND;
/*-------------------------------------------------------------------------------------------*/
/*EXTEND
adds a null element .COUNT
is now
6, after EXTEND. ----------*/
/*
EXTEND(2) will add two null elements------------------------------------------*/
/*-------------------------------------------------------------------------*/
LV_ARRAY_2(LV_ARRAY_2.COUNT)
:= 16;
DBMS_OUTPUT.PUT_LINE('LV_ARRAY_2
after extend');
FOR I IN
LV_ARRAY_2.FIRST .. LV_ARRAY_2.LAST LOOP
DBMS_OUTPUT.PUT_LINE(LV_ARRAY_2(I));
END
LOOP;
DBMS_OUTPUT.PUT_LINE('------------------------------');
/*---------------------------------------------------------------------*/
/*Prints
11,12,13,14,15,16---------*/
/*-------------------------------------------------------------*/
DBMS_OUTPUT.PUT_LINE('The
maximum limit of a VARRAY can be displayed using LIMIT' ||
'---'
|| LV_ARRAY.LIMIT);
DBMS_OUTPUT.PUT_LINE('FIRST and LAST displays
the smallest and largest INDICES, not
the elements' ||
'---'
|| LV_ARRAY_2.FIRST || '---' ||
LV_ARRAY_2.LAST);
DBMS_OUTPUT.PUT_LINE('For VARRAYS,
COUNT is always equal to
LAST' ||
'--LAST
INDEX--' || LV_ARRAY_2.LAST || '--COUNT--' ||
LV_ARRAY_2.COUNT);
DBMS_OUTPUT.PUT_LINE('TRIM
can be used to remove the last
element from the
VARRAY');
DBMS_OUTPUT.PUT_LINE('Before
TRIM, the last element is' || '----' ||
LV_ARRAY(LV_ARRAY.LAST));
LV_ARRAY.TRIM;
DBMS_OUTPUT.PUT_LINE('After TRIM,
the last element is' || '----' ||
LV_ARRAY(LV_ARRAY.LAST));
DBMS_OUTPUT.PUT_LINE('Before
TRIM, the last element of lv_array_2 is' ||
'----'
|| LV_ARRAY_2(LV_ARRAY_2.LAST));
LV_ARRAY_2.TRIM(2);
DBMS_OUTPUT.PUT_LINE('After TRIM(2),
the last element of lv_array_2 is' ||
'----'
|| LV_ARRAY_2(LV_ARRAY_2.LAST));
LV_ARRAY_2.DELETE;
DBMS_OUTPUT.PUT_LINE('DELETE removes
all elements from
a VARRAY' ||
'---lv_array_2.COUNT-->'
|| LV_ARRAY_2.COUNT);
/*----------------------------------------------------------------------------------*/
/*VARRAYs
are dense------------------------------------------------------*/
/*so we
cannot use DELETE(2) unlike in
associative arrays
or nested tables */
/*---------------------------------------------------------------------------------*/
END;
|
REF
CURSOR example in PL/SQL
The following script
shows how to use REF CURSOR, how to open a strong REF CURSOR and a weak REF
CURSOR.
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
|
CREATE OR REPLACE PROCEDURE TEST_REF_CURSOR1
IS
-------------------procedure
to test the use of ref_cursor
LV_VAR
EMPLOYEES%ROWTYPE;
LV_EXCHANGE
EMP_DETAIL%ROWTYPE;
TYPE
TEST_REF IS REF CURSOR;--weak ref cursor
REF_EMP
TEST_REF;
TYPE
test_strong_ref IS REF CURSOR RETURN TEMP_TBL_TEST%ROWTYPE;
/*strong ref cursor using
%rowtype.*/
strong_ref
test_strong_ref;
lv_strong
strong_ref%ROWTYPE;
lv_var_type
ANOTHER_TABLE%ROWTYPE;
TYPE
test_strong_ref_type IS REF CURSOR RETURN lv_var_type%TYPE;--strong
ref cursor using %type.
strong_ref_type
test_strong_ref_type;
BEGIN
OPEN REF_EMP
FOR
SELECT *
FROM EMPLOYEES WHERE
ROWNUM <
10;---USE OF WEAK CURSOR
LOOP
FETCH REF_EMP
INTO LV_VAR;
EXIT
WHEN REF_EMP%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(LV_VAR.EMPLOYEE_NUMBER
|| '----' ||
LV_VAR.EMPLOYEE_NAME);
END LOOP;
CLOSE REF_EMP;---no
need close before opening the ref cursor again.
IF
(REF_EMP%ISOPEN) THEN
DBMS_OUTPUT.PUT_LINE('cursor
open');
ELSE
BEGIN
OPEN REF_EMP
FOR
SELECT *
FROM EMP_DETAIL WHERE
ROWNUM < 10;
LOOP
FETCH REF_EMP
INTO LV_EXCHANGE;
EXIT
WHEN REF_EMP%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(LV_EXCHANGE.EMP_HIRE_DATE
|| '----' ||
LV_EXCHANGE.EMP_DEPT);
END LOOP;
CLOSE REF_EMP;
END;
END IF;
OPEN strong_ref
FOR SELECT * FROM TEMP_TBL_TEST
WHERE rownum < 10;
LOOP
FETCH strong_ref
INTO lv_strong;
EXIT
WHEN strong_ref%NOTFOUND;
dbms_output.put_line(lv_strong.COL1||'--'||lv_strong.COL2);
END LOOP;
CLOSE strong_ref;
OPEN strong_ref
FOR SELECT * FROM TEMP_TBL_TEST_DUPLICATE
WHERE rownum < 10;/*can be used as both
the tables
have
the same structure. (TEMP_TBL_TEST_DUPLICATE CREATED FROM TEMP_TBL_TEST)*/
LOOP
FETCH strong_ref
INTO lv_strong;
EXIT
WHEN strong_ref%NOTFOUND;
dbms_output.put_line(lv_strong.COL1
||'---'||lv_strong.COL2 );
END LOOP;
CLOSE strong_ref;
OPEN strong_ref_type
FOR SELECT * FROM ANOTHER_TABLE
WHERE rownum < 4;
LOOP
FETCH strong_ref_type
INTO lv_var_type;
EXIT
WHEN strong_ref_type%NOTFOUND;
dbms_output.put_line(lv_var_type.COL1);
END LOOP;
CLOSE strong_ref_type;
END;
|
'&'
vs '&&' in ORACLE
“&” is used to create a
temporary substitution variable. You will be prompted to enter the value every
time the variable is referenced.
“&&” is used to create a
permanent substitution variable. You need to enter the value only once.
1. Using ‘&’
1
2
|
SELECT employee_name
FROM employee
WHERE emp_id
='&emp'
|
Every time, you execute
this select statement, you will have to pass the value of emp.
2. Using ‘&&’
Consider the following procedure.
Consider the following procedure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
SQL> set serveroutput
on;
SQL> CREATE OR REPLACE PROCEDURE test_substitution
AS
emp_name
VARCHAR2(10);
emp_designation
VARCHAR2(10);
BEGIN
SELECT employee_name
INTO emp_name FROM
employee
WHERE emp_id
='&&emp';
dbms_output.put_line(emp_name);
SELECT employee_desg
INTO emp_designation FROM
employee
WHERE emp_id='&emp';
dbms_output.put_line(emp_designation);
END;
Procedure created
|
This procedure fetches
the employee_name and employee_desg from the employee table for a particular
emp_id.
It will prompt for the emp_id only once as ‘&&emp’ is used. In the next select statement,although a substitution variable is used, you will not be prompted to enter a value for emp.
It will prompt for the emp_id only once as ‘&&emp’ is used. In the next select statement,although a substitution variable is used, you will not be prompted to enter a value for emp.
The next time you
execute the procedure,it will not prompt for the value to be passed.
If you need to pass a
different value each time you run the procedure, then you will have to undefine the variable emp.
1
|
SQL> undefine
emp;
|
AND then compile the
procedure again. This will prompt for a new value to be passed.
If you just undefine the
variable and execute the procedure, it will not prompt for a new value.
It is important to undefine and then recompile the procedure.
It is important to undefine and then recompile the procedure.
What
is a one-time-only procedure in pl/sql?
A one-time-only
procedure is an anonymous block of code encapsulated within a package body,
used for initializing variables and is executed only once when the package is
invoked for the first time in a user session.
It has a BEGIN but not
an END . The END of the package body serves as the end of the
one-time-only procedure within the package body.
An example of a
one-time-procedure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
CREATE OR REPLACE
PACKAGE pckg_name
AS
FUNCTION
function_name ... ;
PROCEDURE
procedure_name ... ;
END pckg_name
CREATE OR REPLACE
PACKAGE BODY pckg_name
AS
FUNCTION
function_name BEGIN ... END function_name;
PROCEDURE
procedure_name ... BEGIN ... END procedure_name;
BEGIN
-- whatever code is
here executes one time only,
-- the first time
the package is called
END pckg_name
|
The
'RR' Date Format
The RR date format element of the TO_DATE and TO_CHAR functions allows a database site to default the century to
different values depending on the two-digit year, so that years 50 to 99 default to
19xx and years 00 to 49 default to
20xx. Therefore, regardless
of the current century at the time the data is entered, the ‘RR’ format will
ensure that the year stored in the database is as follows:
·
If the current year is
in the second half of the century (50 – 99), and a two-digit year between `00′
and `49′ is entered, this will be stored as a ‘next century’ year. For example,
`02′ entered in 1996 will be stored as `2002′
·
If the current year is
in the second half of the century (50 – 99), and a two-digit year between `50′
and `99′ is entered, this will be stored as a ‘current century’ year. For
example, `97′ entered in 1996 will be stored as `1997′.
·
If the current year is
in the first half of the century (00 – 49), and a two-digit year between `00′
and `49′ is entered, this will be stored as a ‘current century’ year. For
example, `02′ entered in 2001 will be stored as `2002′.
·
If the current year is
in the first half of the century (00 – 49), and a two-digit year between `50′
and `99′ is entered, this will be stored as a ‘previous century’ year. For
example, `97′ entered in 2001 will be stored as `1997′.
The `RR’ date format is available for inserting and
updating DATE data in the database. It is not required for retrieval or query of data already stored in the
database as Oracle has always stored the YEAR component of a date in its
four-digit form.
Here is an example of
the RR usage:
INSERT INTO emp
(empno, deptno,hiredate) VALUES
(9999,
20, TO_DATE('01-jan-03', 'DD-MON-RR'));
INSERT INTO emp
(empno, deptno,hiredate) VALUES
(8888,
20, TO_DATE('01-jan-67', 'DD-MON-RR'));
SELECT empno,
deptno,
TO_CHAR(hiredate,
'DD-MON-YYYY') hiredate
FROM emp;
|
This produces the
following data:
EMPNO DEPTNO HIREDATE
———- ———- —————–
8888 20 01-JAN-1967
9999 20 01-JAN-2003
———- ———- —————–
8888 20 01-JAN-1967
9999 20 01-JAN-2003
Rollup
in GROUP BY (ORACLE)
Level: Intermediate.
ROLLUP is used to calculate multiple levels of subtotals for a specified group. It also calculates a grand total.
It is an extension of the GROUP BY CLAUSE.
ROLLUP is used to calculate multiple levels of subtotals for a specified group. It also calculates a grand total.
It is an extension of the GROUP BY CLAUSE.
It is very helpful for
subtotaling along a hierarchical dimension such as time or geography. For
instance, a query could specify a ROLLUP(y, m, day) or ROLLUP(country, state,
city). It can be used to find the MIN, MAX,AVG,SUM and COUNT of specified sub groups.
Consider the following
table and the sql statements below:
Table used:
Table used:
CLASS_NUM
|
STUDENT_ID
|
1
|
1
|
1
|
2
|
1
|
3
|
2
|
1
|
2
|
2
|
3
|
1
|
4
|
1
|
————————————–
1
2
|
SELECT class_num,SUM(class_num)
FROM temp_1234
GROUP BY class_num---sum
of individual class_num
|
This is a simple group
by clause with the aggregate function SUM.
1
2
|
SELECT class_num,SUM(class_num)
FROM temp_1234
GROUP BY rollup(class_num)--sum
of individual groups and the sum of sum(class_num)
|
An additional row is
displayed, which shows the SUM of SUM(class_num)
———————————————————————————————————
———————————————————————————————————
1
2
|
SELECT class_num,COUNT(*)
FROM temp_1234
GROUP BY class_num--how
many times different class_num occours.
|
|
1
2
|
SELECT class_num,COUNT(*)
FROM temp_1234
GROUP BY rollup(class_num)--how
many times different class_num occurs and sum of count(*)
|
———————————————————————————————————
1
2
|
SELECT class_num,student_id,COUNT(*)
FROM temp_1234
GROUP BY class_num,student_id---count(*)
gives count of occurrence each group by of (class_num,student_id)
|
|
1
2
|
SELECT class_num,student_id,COUNT(*)
FROM temp_1234
GROUP BY rollup(class_num,student_id)---count(*)
gives count of each group by of (class_num,student_id) and sum of group by of
each (class_num and student_id. Rollup on switch type. Since more than two
columns in group by, a break up of count(*) is also shown.
|
1
2
3
|
SELECT class_num,student_id,AVG(student_id),SUM(student_id),MIN(student_id),max(student_id)
FROM temp_1234
GROUP BY ROLLUP(class_num,student_id)---follows
the same concept as above and extended for max, min,sum and avg
|
|
1
2
|
SELECT student_id,class_num,COUNT(*)
FROM temp_1234
GROUP BY rollup(student_id,class_num)---count(*)
gives count of each group by of (student_id,class_num) and sum of group by of
each (class_num and student_id. Rollup on student_id.
|
———————————————————————————————————
1
2
|
SELECT student_id,class_num,AVG(student_id),SUM(student_id),MIN(student_id),max(student_id)
FROM temp_1234
GROUP BY ROLLUP(student_id,class_num)---follows the same concept as
above and extended for max, min,sum and avg. Rollup on student_id
|
——————more on
rollup——————-
——————find the amount
for each date and the total amount for all the dates
1
2
|
SELECT purchase_date,SUM(amount)
FROM test_rollup
GROUP BY ROLLUP(purchase_date)
|
——————-find the amount
spent by each cust_id on each day,
the total amount for each day and the total amount for all the dates.*/
the total amount for each day and the total amount for all the dates.*/
–start with
1
2
3
|
SELECT purchase_date,cust_id,SUM(amount)
FROM test_rollup
GROUP BY purchase_date,cust_id
|
—roll it up
1
2
3
|
SELECT purchase_date,cust_id,SUM(amount)
FROM test_rollup
GROUP BY ROLLUP(purchase_date,cust_id)
|
——————-more columns…
1
2
3
|
SELECT purchase_date,cust_id,order_id,SUM(amount)
FROM test_rollup
GROUP BY ROLLUP(purchase_date,cust_id,order_id)
|
Calling
a procedure with IN and OUT parameter in oracle
This post shows how one
can call a procedure with an IN and OUT parameter from the sql prompt.
Consider the following
procedure with one IN parameter and one OUT parameter.
1
2
3
4
5
6
7
8
|
CREATE OR REPLACE PROCEDURE test_in_out(num1
IN NUMBER,num2 OUT
NUMBER)
AS
num3 NUMBER;
BEGIN
SELECT SUM(2+3+4)
INTO num2 FROM dual;
num3:=num1;
dbms_output.put_line('value
of num3 is-->'||num3);
END;
|
First declare a bind variable as
1
|
SQL> variable T
number;
|
Next set serveroutput
on. Otherwise dbms_output will not be able to write messages at the prompt.
1
|
SQL> set serveroutput
on;
|
Then call the procedure
as :
1
2
3
4
5
6
7
8
9
10
|
SQL> exec test_in_out(3,
:T);
value of num3
is-->
3
PL/SQL procedure successfully
completed
T
---------
9
SQL>
|
To see the value of T you can use the PRINT command.
1
2
3
4
|
SQL> print T
T
---------
9
|
Pseudocolumns
in Oracle
There are five
pseudocolums that can be used in PL/SQL. They are CURRVAL, NEXTVAL, LEVEL, ROWID
and ROWNUM.
and ROWNUM.
1. CURRVAL and NEXTVAL
CURRVAL returns the current value in a specified sequence. NEXTVAL increments the sequence and returns the
next value. If you have a sequence GENERATE_NUM, then you can obtain the current value or the next value as
CURRVAL returns the current value in a specified sequence. NEXTVAL increments the sequence and returns the
next value. If you have a sequence GENERATE_NUM, then you can obtain the current value or the next value as
SELECT GENERATE_NUM.CURRVAL FROM DUAL;
SELECT GENERATE_NUM.NEXTVAL FROM DUAL;
SELECT GENERATE_NUM.NEXTVAL FROM DUAL;
Note: Before you can
reference CURRVAL in a session, you have to use NEXTVAL to generate a number.
2. ROWID
ROWID returns the binary address of a row in the database table. It can be stored in variables of type UROWID to store rowids in a readable format.
ROWID returns the binary address of a row in the database table. It can be stored in variables of type UROWID to store rowids in a readable format.
SQL>
SQL> DECLARE
2 u_row_id urowid;
3 BEGIN
4 SELECT ROWID INTO u_row_id FROM table_name
5 WHERE ROWNUM<2 span="">
6 dbms_output.put_line(u_row_id);
7 END;
8 /
AAARwiAAKAABcnKAAA 2>
SQL> DECLARE
2 u_row_id urowid;
3 BEGIN
4 SELECT ROWID INTO u_row_id FROM table_name
5 WHERE ROWNUM<2 span="">
6 dbms_output.put_line(u_row_id);
7 END;
8 /
AAARwiAAKAABcnKAAA
PL/SQL procedure successfully completed
3. ROWNUM
ROWNUM returns a number indicating the order in which a row was selected from a table. The first row has a ROWNUM of 1.
ROWNUM returns a number indicating the order in which a row was selected from a table. The first row has a ROWNUM of 1.
SQL>
SQL> SELECT ROWNUM FROM table_name
2 WHERE ROWNUM<10 span=""> 10>
SQL> SELECT ROWNUM FROM table_name
2 WHERE ROWNUM<10 span="">
ROWNUM
----------
1
2
3
4
5
6
7
8
9
----------
1
2
3
4
5
6
7
8
9
9 rows selected
SQL>
4. LEVEL
LEVEL is used with the SELECT CONNCET BY statement to organize rows in a tree structure. Root level is 1,
children is 2 and so on. The START WITH clause identifies the root of the tree. The direction is specified
with the PRIOR operator.
4. LEVEL
LEVEL is used with the SELECT CONNCET BY statement to organize rows in a tree structure. Root level is 1,
children is 2 and so on. The START WITH clause identifies the root of the tree. The direction is specified
with the PRIOR operator.
Consider the following employee table
EMPLOYEEID EMPLOYEENAME REPORTING_EMPLOYEE_ID
845628 AFREF RTRWD 971368
345678 MD. KHADEMUL ISLAM 789456
123456 SAMTRA DERGT 718293
789456 DILIP KR. BARMAN 123639
123639 PULIN BIHARI PAL 852369
852369 A.K.NARJINERY 198753
EMPLOYEEID EMPLOYEENAME REPORTING_EMPLOYEE_ID
845628 AFREF RTRWD 971368
345678 MD. KHADEMUL ISLAM 789456
123456 SAMTRA DERGT 718293
789456 DILIP KR. BARMAN 123639
123639 PULIN BIHARI PAL 852369
852369 A.K.NARJINERY 198753
select level, employeeid,employee_name,manger_employee_id
from employee Start with employeeid ='345678'
connect by prior manger_employee_id = employeeid
order by LEVEL
from employee Start with employeeid ='345678'
connect by prior manger_employee_id = employeeid
order by LEVEL
And the output is:
LEVEL EMPLOYEEID EMPLOYEENAME REPORTING_EMPLOYEE_ID
1 345678 MD. KHADEMUL ISLAM 789456
2 789456 DILIP KR. BARMAN 123639
3 123639 PULIN BIHARI PAL 852369
4 852369 A.K.NARJINERY 198753
LEVEL EMPLOYEEID EMPLOYEENAME REPORTING_EMPLOYEE_ID
1 345678 MD. KHADEMUL ISLAM 789456
2 789456 DILIP KR. BARMAN 123639
3 123639 PULIN BIHARI PAL 852369
4 852369 A.K.NARJINERY 198753
No comments:
Post a Comment