Dynamic Relational operator in a where clause is Invalid (PL/SQL Oracle) - stored-procedures

I am using Oracle 10g and I have the following stored procedure:
CREATE OR REPLACE PACKAGE BODY RF_PKG_STFCA_PositivePay
AS
PROCEDURE RF_SP_STFCA_PositivePay(IN_DATE IN NUMBER, IN_DATE_OPERATOR IN NVARCHAR2, OUT_DATA OUT CUR_DATA)
IS
/* this procedure returns a Ref Cursor with all the requested parameters
calling the stored procedure from an asp page (and anywhere else)
does not require posting a predefined number of records */
PaymentBatchNumber NVARCHAR2(4);
CurrencyCode NVARCHAR2(3);
TransactionCode NVARCHAR2(3);
Transit_BranchNumber NVARCHAR2(5);
BankAccountNumber NVARCHAR2(7);
ChequeNumber NVARCHAR2(8);
ChequeAmount NVARCHAR2(10);
ClientReference NVARCHAR2(19);
IssueDate NVARCHAR2(8);
PayeeName1 NVARCHAR2(60);
AddressLine1 NVARCHAR2(60);
AddressLine2 NVARCHAR2(60);
AddressLine4 NVARCHAR2(60);
AddressLine5 NVARCHAR2(60);
DateCreated NVARCHAR2(25);
DateVoided NVARCHAR2(25);
BEGIN
OPEN OUT_DATA FOR
SELECT LPAD(NVL(CD.PAYMENT_BATCH_NO, '0'), 4, '0') AS PaymentBatchNumber,
SUBSTR(NVL(CD.CURRENCY_ID, ' '), 1, 1) AS CurrencyCode,
NVL(CD.STATUS, ' ') AS TransactionCode,
LPAD(NVL(BA.BRANCH_ID, '0'), 5, '0') AS Transit_BranchNumber,
LPAD(NVL(BA.ACCOUNT_NO, '0'), 7, '0') AS BankAccountNumber,
LPAD(NVL(CD.CHECK_NO, '0') , 8, '0') AS ChequeNumber,
LPAD(TO_CHAR(NVL(CD.AMOUNT, 0)), 10, '0') AS ChequeAmount,
LPAD(NVL(CD.CONTROL_NO, '0'), 19, '0') AS ClientReference,
TO_CHAR(NVL(CD.CHECK_DATE, LPAD(' ', 8, ' ')), 'YYYYMMDD') AS IssueDate,
RPAD(NVL(CD.NAME, ' '), 60, ' ') AS PayeeName1,
RPAD(NVL(CD.ADDR_1, ' '), 60, ' ') AS AddressLine1,
RPAD(NVL(CD.ADDR_2, ' '), 60, ' ') AS AddressLine2,
RPAD(NVL(CD.CITY, '') || CASE WHEN CD.CITY IS NULL OR CD.STATE IS NULL THEN ' ' ELSE ', ' END || NVL(CD.STATE, ''), 60, ' ') AS AddressLine4,
RPAD(NVL(CD.ZIPCODE, ' '), 60, ' ') AS AddressLine5,
TO_CHAR(CD.CREATE_DATE, 'YYYYMMDDHH24MISS') AS DateCreated,
CASE WHEN CD.VOID_DATE IS NULL THEN ' ' ELSE TO_CHAR(CD.VOID_DATE, 'YYYYMMDDHH24MISS') END AS DateVoided
INTO PaymentBatchNumber, CurrencyCode, TransactionCode, Transit_BranchNumber, BankAccountNumber, ChequeNumber,
ChequeAmount, ClientReference, IssueDate, PayeeName1, AddressLine1, AddressLine2, AddressLine4, AddressLine5,
DateCreated, DateVoided
FROM BANK_ACCOUNT BA
INNER JOIN CASH_DISBURSEMENT CD ON BA.ID = CD.BANK_ACCOUNT_ID
WHERE BA.ACCOUNT_NO IS NOT NULL AND CD.CHECK_NO > 0 AND CD.STATUS != 'X' AND CD.AMOUNT != 0 AND ((TO_NUMBER(TO_CHAR(CD.CREATE_DATE, 'YYYYMMDDHH24MISS')) || IN_DATE_OPERATOR || IN_DATE) OR
(CASE WHEN CD.VOID_DATE IS NULL THEN 0 ELSE TO_NUMBER(TO_CHAR(CD.VOID_DATE, 'YYYYMMDDHH24MISS')) END || IN_DATE_OPERATOR || IN_DATE))
ORDER BY BA.BRANCH_ID, BA.ACCOUNT_NO;
END RF_SP_STFCA_PositivePay;
END RF_PKG_STFCA_PositivePay;
And I get the following error when entering this into SQL plus:
invalid relational operator
What I'm trying to do: I have this stored procedure that returns a secordset to my asp.net application using the REF CURSOR. I give it 2 input parameters. 1 is a date (IN_DATE) and 1 is an operator (IN_DATE_OPERATOR). The program works if "|| IN_DATE_OPERATOR ||" is replaces with either = or >= just the way I want it to work. The problem is based on what happens in the .Net application I want the operater it uses in the where clause to be either ">=" or "=" and I wont know which until runtime.
I know I'm doing this wrong but I don't know how to get oracle to reconize that IN_DATE_OPERATOR is a relational operator. I am open to other methods to have a dynamic operator (i tried CASE WHEN IN_DATE_OPERATOR = '=' THEN '=' ELSE '>=' END to no avail too) but I don't want to create a whole seperate stored procedure I will have to maintin in addition to this or a completely dynamic where clause. My ideal solution would make the least amount of changes to this query as possible. Any suggestions?
Edit: ok I've edited my query do be the following:
CREATE OR REPLACE PACKAGE BODY RF_PKG_STFCA_PositivePay
AS
PROCEDURE RF_SP_STFCA_PositivePay(IN_DATE IN NUMBER, IN_DATE_OPERATOR IN VARCHAR2, OUT_DATA OUT CUR_DATA)
IS
/* this procedure returns a Ref Cursor with all the requested parameters
calling the stored procedure from an asp page (and anywhere else)
does not require posting a predefined number of records */
SQL_Statement VARCHAR2(8000);
BEGIN
SQL_Statement := 'SELECT LPAD(NVL(CD.PAYMENT_BATCH_NO, ''0''), 4, ''0'') AS PaymentBatchNumber, ' ||
' SUBSTR(NVL(CD.CURRENCY_ID, '' ''), 1, 1) AS CurrencyCode, ' ||
' NVL(CD.STATUS, '' '') AS TransactionCode, ' ||
' LPAD(NVL(BA.BRANCH_ID, ''0''), 5, ''0'') AS Transit_BranchNumber, ' ||
' LPAD(NVL(BA.ACCOUNT_NO, ''0''), 7, ''0'') AS BankAccountNumber, ' ||
' LPAD(NVL(CD.CHECK_NO, ''0'') , 8, ''0'') AS ChequeNumber, ' ||
' LPAD(TO_CHAR(NVL(CD.AMOUNT, 0)), 10, ''0'') AS ChequeAmount, ' ||
' LPAD(NVL(CD.CONTROL_NO, ''0''), 19, ''0'') AS ClientReference, ' ||
' TO_CHAR(NVL(CD.CHECK_DATE, LPAD('' '', 8, '' '')), ''YYYYMMDD'') AS IssueDate, ' ||
' RPAD(NVL(CD.NAME, '' ''), 60, '' '') AS PayeeName1, ' ||
' RPAD(NVL(CD.ADDR_1, '' ''), 60, '' '') AS AddressLine1, ' ||
' RPAD(NVL(CD.ADDR_2, '' ''), 60, '' '') AS AddressLine2, ' ||
' RPAD(NVL(CD.CITY, '''') || CASE WHEN CD.CITY IS NULL OR CD.STATE IS NULL THEN '' '' ELSE '', '' END || NVL(CD.STATE, ''''), 60, '' '') AS AddressLine4, ' ||
' RPAD(NVL(CD.ZIPCODE, '' ''), 60, '' '') AS AddressLine5, ' ||
' TO_CHAR(CD.CREATE_DATE, ''YYYYMMDDHH24MISS'') AS DateCreated, ' ||
' CASE WHEN CD.VOID_DATE IS NULL THEN '' '' ELSE TO_CHAR(CD.VOID_DATE, ''YYYYMMDDHH24MISS'') END AS DateVoided ' ||
' FROM BANK_ACCOUNT BA ' ||
' INNER JOIN CASH_DISBURSEMENT CD ON BA.ID = CD.BANK_ACCOUNT_ID ' ||
' WHERE BA.ACCOUNT_NO IS NOT NULL AND CD.CHECK_NO > 0 AND CD.STATUS != ''X'' AND CD.AMOUNT != 0 ' ||
' AND ((TO_NUMBER(TO_CHAR(CD.CREATE_DATE, ''YYYYMMDDHH24MISS'')) ' || IN_DATE_OPERATOR || ' :1) ' ||
' OR (CASE WHEN CD.VOID_DATE IS NULL THEN 0 ELSE TO_NUMBER(TO_CHAR(CD.VOID_DATE, ''YYYYMMDDHH24MISS'')) END ' || IN_DATE_OPERATOR || ' :2)) ' ||
' ORDER BY BA.BRANCH_ID, BA.ACCOUNT_NO ';
OPEN OUT_DATA FOR SQL_Statement USING IN_DATE, IN_DATE;
END RF_SP_STFCA_PositivePay;
END RF_PKG_STFCA_PositivePay;/
but I get the following error:
LINE/COL ERROR
32/3 PL/SQL: Statement ignored
32/21 PLS-00382: expression is of wrong type

You would need to dynamically assemble the SQL statement in a string and then use that string to open the cursor. You'll need something along the lines of the get_cur procedure below where you assemble the SQL statement in a local VARCHAR2 variable including the placeholders for the bind variables and then open the cursor using the SQL statement you assembled and the bind variable you passed in.
SQL> create or replace procedure get_cur( p_date in date, p_operator in varchar2, p_cur out sys_refcursor )
2 as
3 l_sql_stmt varchar2(1000);
4 begin
5 l_sql_stmt := 'select * from emp where hiredate ' || p_operator || ' :1';
6 open p_cur for l_sql_stmt using p_date;
7 end;
8 /
Procedure created.
SQL> var rc refcursor;
SQL> exec get_cur( date '2001-01-01', '>=', :rc );
PL/SQL procedure successfully completed.
SQL> print rc;
no rows selected
SQL> exec get_cur( date '2001-01-01', '<=', :rc );
PL/SQL procedure successfully completed.
SQL> print rc;
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO
----------
7369 SMITH CLERK 7902 17-DEC-80 801
20
7499 ALLEN SALESMAN 7698 20-FEB-81 1601 300
30
7521 WARD SALESMAN 7698 22-FEB-81 1251 500
30
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO
----------
7566 JONES MANAGER 7839 02-APR-81 2976
20
7654 MARTIN SALESMAN 7698 28-SEP-81 1251 1400
30
7698 BLAKE MANAGER 7839 01-MAY-81 2851
30
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO
----------
7782 CLARK MANAGER 7839 09-JUN-81 2451
10
7788 SCOTT ANALYST 7566 19-APR-87 3001
20
7839 KING PRESIDENT 17-NOV-81 5001
10
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO
----------
7844 TURNER SALESMAN 7698 08-SEP-81 1501 0
30
7876 ADAMS CLERK 7788 23-MAY-87 1101
20
7900 JAMES CLERK 7698 03-DEC-81 951
30
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO
----------
7902 FORD ANALYST 7566 03-DEC-81 3001
20
7934 MILLER CLERK 7782 23-JAN-82 1301
10
14 rows selected.
My guess is that you want something like this (obviously, since I don't have your tables or types, I can't test that this actually compiles so you'll likely need to correct typos)
CREATE OR REPLACE PACKAGE BODY RF_PKG_STFCA_PositivePay
AS
PROCEDURE RF_SP_STFCA_PositivePay(IN_DATE IN NUMBER, IN_DATE_OPERATOR IN NVARCHAR2, OUT_DATA OUT CUR_DATA)
IS
/* this procedure returns a Ref Cursor with all the requested parameters
calling the stored procedure from an asp page (and anywhere else)
does not require posting a predefined number of records */
l_sql_stmt VARCHAR2(4000);
BEGIN
l_sql_stmt := q'[SELECT LPAD(NVL(CD.PAYMENT_BATCH_NO, '0'), 4, '0') AS PaymentBatchNumber, ]' ||
q'[SUBSTR(NVL(CD.CURRENCY_ID, ' '), 1, 1) AS CurrencyCode, ]' ||
q'[NVL(CD.STATUS, ' ') AS TransactionCode, ]' ||
q'[LPAD(NVL(BA.BRANCH_ID, '0'), 5, '0') AS Transit_BranchNumber, ]' ||
q'[LPAD(NVL(BA.ACCOUNT_NO, '0'), 7, '0') AS BankAccountNumber, ]' ||
q'[LPAD(NVL(CD.CHECK_NO, '0') , 8, '0') AS ChequeNumber, ]' ||
q'[LPAD(TO_CHAR(NVL(CD.AMOUNT, 0)), 10, '0') AS ChequeAmount, ]' ||
q'[LPAD(NVL(CD.CONTROL_NO, '0'), 19, '0') AS ClientReference, ]' ||
q'[TO_CHAR(NVL(CD.CHECK_DATE, LPAD(' ', 8, ' ')), 'YYYYMMDD') AS IssueDate, ]' ||
q'[RPAD(NVL(CD.NAME, ' '), 60, ' ') AS PayeeName1, ]' ||
q'[RPAD(NVL(CD.ADDR_1, ' '), 60, ' ') AS AddressLine1, ]' ||
q'[RPAD(NVL(CD.ADDR_2, ' '), 60, ' ') AS AddressLine2, ]' ||
q'[RPAD(NVL(CD.CITY, '') || CASE WHEN CD.CITY IS NULL OR CD.STATE IS NULL THEN ' ' ELSE ', ' END || NVL(CD.STATE, ''), 60, ' ') AS AddressLine4, ]' ||
q'[RPAD(NVL(CD.ZIPCODE, ' '), 60, ' ') AS AddressLine5, ]' ||
q'[TO_CHAR(CD.CREATE_DATE, 'YYYYMMDDHH24MISS') AS DateCreated, ]' ||
q'[CASE WHEN CD.VOID_DATE IS NULL THEN ' ' ELSE TO_CHAR(CD.VOID_DATE, 'YYYYMMDDHH24MISS') END AS DateVoided ]' ||
q'[FROM BANK_ACCOUNT BA ]' ||
q'[INNER JOIN CASH_DISBURSEMENT CD ON BA.ID = CD.BANK_ACCOUNT_ID ]' ||
q'[WHERE BA.ACCOUNT_NO IS NOT NULL AND CD.CHECK_NO > 0 ]' ||
q'[AND CD.STATUS != 'X' ]' ||
q'[AND CD.AMOUNT != 0 ]' ||
q'[AND ((TO_NUMBER(TO_CHAR(CD.CREATE_DATE, 'YYYYMMDDHH24MISS'))]' || IN_DATE_OPERATOR || ':1') OR ' ||
q'[(CASE WHEN CD.VOID_DATE IS NULL THEN 0 ELSE TO_NUMBER(TO_CHAR(CD.VOID_DATE, 'YYYYMMDDHH24MISS')) END]' || IN_DATE_OPERATOR || ':2')) ' ||
q'[ORDER BY BA.BRANCH_ID, BA.ACCOUNT_NO ]';
OPEN out_data
FOR l_sql_stmt
USING in_date, in_date;
END RF_SP_STFCA_PositivePay;
END RF_PKG_STFCA_PositivePay;

Related

Zend 2 escaping single-quote

I am using Zend Framework 2 to generate the following escaped single-quote SQL query,
SELECT
`document`.*
FROM
`document`
WHERE
(
`document`.`document_taxon` LIKE '%Men\'s Health %' --escaped quote
AND `document`.`document_source_id` = ' 5 '
AND `document`.`document_published` = ' 1 '
AND `document`.`document_deleted` = ' 0 '
)
ORDER BY
`document_id` DESC
LIMIT 25 OFFSET 0
But I am getting this instead,
SELECT
`document`.*
FROM
`document`
WHERE
(
`document`.`document_taxon` LIKE '%Men's Health%'
AND `document`.`document_source_id` = ' 5 '
AND `document`.`document_published` = ' 1 '
AND `document`.`document_deleted` = ' 0 '
)
ORDER BY
`document_id` DESC
LIMIT 25 OFFSET 0
And here is my code
class DocumentTable extends TableGateway
{
....
$select=$this->getSql()->select();
$select->columns(array('*'));
$select->where
->NEST
->like('document_taxon', '%' . $label . '%')
->and
->equalTo('document_source_id', $sourceId)
->and
->equalTo('document_published', true)
->and
->equalTo('document_deleted', 0)
->UNNEST;
$select->order('document_id DESC');
$select->limit($limit);
$select->offset($offset);
...
}
I tried,
$this->getAdapter()->getPlatform()->quoteValue($string)
\Zend\Db\Sql\Expression("%". $label . "%")
str_replace("'", "\'", $label)
But I didn’t have much luck. I welcome any suggestion to solve this issue.
I worked it out. I was passing a normalized “label” value instead of the raw value. The above code snippet works fine.

Intermittent errors when performing export to file in Oracle 11g

System info
Oracle database 11g/11.2 Enterprise edition
Summary
When the procedure is called (via Hibernate's CallableStatements), it gets all the partitions to be exported based on the days to keep (an input parameter). After those partitions are evaluated, the function which accepts partitions lists as input parameters (the first one in the wall of code) uses the DBMS_DATAPUMP API to export the content into a flat file.
Sometimes (I don't have a 100% reproducible scenario) it fails with the error below. It has to be noted that 99.9% of the time these procedures run smootly. The database user has write access to the exports directory.
The procedure:
TYPE PARTITION_NAME_TYPE IS RECORD (
PARTITION_NAME VARCHAR2(32)
);
TYPE PARTITION_LIST IS REF CURSOR RETURN PARTITION_NAME_TYPE;
PROCEDURE EXPORT_PARTITIONS (
P_TABLE_NAME IN VARCHAR2,
P_PARTITION_LIST IN PARTITION_LIST,
P_EXPORT_DIR IN VARCHAR2,
P_PARALLELISM_DEGREE IN NUMBER DEFAULT 1,
P_FILE_PATH OUT VARCHAR2,
P_RESULT OUT VARCHAR2,
P_RESULT_MSG OUT VARCHAR2)
IS
V_PUMP_HANDLE NUMBER; -- Data Pump job handle
V_PUMP_STATE VARCHAR2(255);
V_PUMP_FILE VARCHAR2(255);
V_PUMP_DIR VARCHAR2(255);
V_JOB_STATE VARCHAR2(4000);
V_STATUS KU$_STATUS1010;
V_LOGS KU$_LOGENTRY1010;
V_ROW PLS_INTEGER;
V_ERRORDETAILS VARCHAR2(4000);
V_PARTITION_LIST VARCHAR2(32767);
V_PARTITION_LIST_TEMP PARTITION_NAME_TYPE;
BEGIN
FETCH P_PARTITION_LIST INTO V_PARTITION_LIST_TEMP;
IF P_PARTITION_LIST%NOTFOUND THEN
P_RESULT := 'FAILED';
P_RESULT_MSG := 'No partitions to export';
RETURN;
END IF;
V_PARTITION_LIST := V_PARTITION_LIST_TEMP.PARTITION_NAME;
V_PUMP_FILE := V_PARTITION_LIST_TEMP.PARTITION_NAME;
LOOP
FETCH P_PARTITION_LIST INTO V_PARTITION_LIST_TEMP;
IF P_PARTITION_LIST%NOTFOUND THEN
V_PUMP_FILE := V_PUMP_FILE || '_TO_' || V_PARTITION_LIST_TEMP.PARTITION_NAME;
EXIT;
END IF;
V_PARTITION_LIST := V_PARTITION_LIST || ',' || V_PARTITION_LIST_TEMP.PARTITION_NAME;
END LOOP;
V_PUMP_HANDLE := DBMS_DATAPUMP.OPEN('EXPORT', 'TABLE');
DBMS_DATAPUMP.ADD_FILE(HANDLE => V_PUMP_HANDLE, FILENAME => V_PUMP_FILE || '.dbf' , DIRECTORY => P_EXPORT_DIR, FILETYPE => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE);
DBMS_DATAPUMP.ADD_FILE(V_PUMP_HANDLE, V_PUMP_FILE || '.log',P_EXPORT_DIR, NULL, DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE);
DBMS_DATAPUMP.DATA_FILTER (V_PUMP_HANDLE, 'PARTITION_LIST', V_PARTITION_LIST);
DBMS_DATAPUMP.SET_PARALLEL(V_PUMP_HANDLE, P_PARALLELISM_DEGREE);
DBMS_DATAPUMP.START_JOB (V_PUMP_HANDLE);
DBMS_DATAPUMP.WAIT_FOR_JOB(V_PUMP_HANDLE, V_PUMP_STATE);
DBMS_DATAPUMP.DETACH(V_PUMP_HANDLE);
P_RESULT := 'SUCCESS';
-- get actual path to Export Directory
SELECT directory_path INTO V_PUMP_DIR FROM ALL_DIRECTORIES WHERE directory_name = P_EXPORT_DIR;
P_FILE_PATH := V_PUMP_DIR || '/' || V_PUMP_FILE || '.dbf';
P_RESULT_MSG := 'Successfully exported ' || P_TABLE_NAME || '(' || V_PARTITION_LIST || ') as ' || V_PUMP_FILE || '.dbf';
EXCEPTION
WHEN OTHERS THEN
DBMS_DATAPUMP.GET_STATUS(V_PUMP_HANDLE, 8, 0, V_JOB_STATE, V_STATUS);
V_LOGS := V_STATUS.ERROR;
V_ROW := V_LOGS.FIRST;
LOOP
EXIT WHEN V_ROW IS NULL;
V_ERRORDETAILS := 'LOGLINENUMBER='||V_LOGS(V_ROW).LOGLINENUMBER || ' ERRORNUMBER='||V_LOGS(V_ROW).ERRORNUMBER || ' LOGTEXT='||V_LOGS(V_ROW).LOGTEXT;
V_ROW := V_LOGS.NEXT (V_ROW);
END LOOP;
P_RESULT_MSG := 'Failed to export partitions on ' || P_TABLE_NAME || '(' || V_PARTITION_LIST || ') : ' || SQLERRM || ' Details: (' || V_ERRORDETAILS || ')';
P_RESULT := 'FAILED';
DBMS_DATAPUMP.STOP_JOB(V_PUMP_HANDLE);
END EXPORT_PARTITIONS;
PROCEDURE EXPORT_PARTITIONS (
P_TABLE_NAME IN VARCHAR2,
P_NUM_DAYS_TO_KEEP IN NUMBER,
P_EXPORT_DIR IN VARCHAR2,
P_PARALLELISM_DEGREE IN NUMBER DEFAULT 1,
P_FILE_PATH OUT VARCHAR2,
P_RESULT OUT VARCHAR2,
P_RESULT_MSG OUT VARCHAR2)
IS
V_MIN_DAYS_TO_KEEP NUMBER := 1;
V_CNT NUMBER;
V_PARTITIONS PARTITION_LIST;
V_EXPORT_PARTITIONS_RESULT VARCHAR2(2000);
V_EXPORT_PARTITIONS_RESULT_MSG VARCHAR2(2000);
BEGIN
IF P_NUM_DAYS_TO_KEEP < V_MIN_DAYS_TO_KEEP THEN
RAISE_APPLICATION_ERROR(-20000, 'Cannot drop partitions less than ' || V_MIN_DAYS_TO_KEEP || ' days old.');
END IF;
SELECT COUNT(*)
INTO V_CNT
FROM USER_TABLES
WHERE TABLE_NAME = P_TABLE_NAME;
IF V_CNT = 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Table Does Not Exist');
END IF;
OPEN V_PARTITIONS FOR SELECT partition_name
FROM USER_TAB_PARTITIONS
WHERE table_name = UPPER(P_TABLE_NAME)
AND
CASE
WHEN INSTR(PARTITION_NAME, 'MAXVALUE') = 0
THEN TO_DATE(SUBSTR(PARTITION_NAME, -8), 'YYYYMMDD')
END < TRUNC(SYSDATE) - P_NUM_DAYS_TO_KEEP
ORDER BY partition_name ASC;
EXPORT_PARTITIONS(P_TABLE_NAME, V_PARTITIONS, P_EXPORT_DIR, P_PARALLELISM_DEGREE, P_FILE_PATH, V_EXPORT_PARTITIONS_RESULT, V_EXPORT_PARTITIONS_RESULT_MSG);
CLOSE V_PARTITIONS;
IF INSTR(V_EXPORT_PARTITIONS_RESULT, 'SUCCESS') = 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Cannot export partitions. Logs purging will not proceede. ' || V_EXPORT_PARTITIONS_RESULT_MSG);
END IF;
P_RESULT := 'SUCCESS';
P_RESULT_MSG := V_EXPORT_PARTITIONS_RESULT_MSG;
EXCEPTION
WHEN OTHERS THEN
P_RESULT_MSG := 'Failed To Export Partition on ' || P_TABLE_NAME || ': ' || SQLERRM;
P_RESULT := 'FAILED';
END EXPORT_PARTITIONS;
The error:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at * . *, line 208
ORA-39001: invalid argument value
What's on line 208 can be found below:
SQL> select text from all_source where type = 'PACKAGE BODY' and owner = '***' and name = '***' and line = '208';
TEXT
--------------------------------------------------------------------------------
P_RESULT_MSG := 'Failed to export partitions on ' || P_TABLE_NAME || '(' || V_
PARTITION_LIST || ') : ' || SQLERRM || ' Details: (' || V_ERRORDETAILS || ')';
After busting my head around for several days, I still haven't managed to find the problematic piece of code.
Edit #1
The caller of the procedure is a Hibernate CallableStatement. The declaration of the resultMsg on the java side is as follows:
call.registerOutParameter("resultMsg", Types.VARCHAR);
This calls the EXPORT_PARTITIONS(tableName, daysToKeep, exportDir, paralelismDegree) procedure, which then calls the second EXPORT_PARTITIONS procedure.

How to send the result of a select query to a message body of a mail in oracle 10G

CREATE OR REPLACE PROCEDURE STATUS_MAIL(FROM_MAIL IN VARCHAR2, TO_MAIL IN VARCHAR2)
is
v_From VARCHAR2(80) := FROM_MAIL;
v_Recipient VARCHAR2(80) := TO_MAIL;
v_Subject VARCHAR2(80) := 'EMPLOYEE STATUS';
v_Mail_Host VARCHAR2(30) := 'xx.xx.xxx.xxx';
v_Mail_Conn utl_smtp.Connection;
v_msg_body VARCHAR2(5000);
v_output VARCHAR2(5000);
BEGIN
/*Result always returns 42 rows*/
v_output := 'select empid,ename,mobile,dept from employee';
EXECUTE IMMEDIATE v_output into v_msg_body;
v_Mail_Conn := utl_smtp.Open_Connection(v_Mail_Host, xx);
utl_smtp.Helo(v_Mail_Conn, v_Mail_Host);
utl_smtp.Mail(v_Mail_Conn, v_From);
utl_smtp.Rcpt(v_Mail_Conn, v_Recipient);
utl_smtp.Data(v_Mail_Conn,
'Date: ' || to_char(sysdate, 'Dy, DD Mon YYYY hh24:mi:ss') || UTL_TCP.crlf ||
'From: ' || v_From || UTL_TCP.crlf ||
'Subject: '|| v_Subject || UTL_TCP.crlf ||
'To: ' || v_Recipient || UTL_TCP.crlf ||
UTL_TCP.crlf || v_msg_body );
utl_smtp.Quit(v_mail_conn);
EXCEPTION
WHEN utl_smtp.Transient_Error OR utl_smtp.Permanent_Error then
raise_application_error(-20000, 'Unable to send mail: '||sqlerrm);
END;
Getting an error inconsistent datatypes
# EXECUTE IMMEDIATE v_output into v_msg_body
while executing the above procedure, please help me....
So in fact your real question is: "how do I aggregate multiple rows into a single string ?"
The answer is to use aggregate functions. Oracle has introduced listagg-function in 11gR2 that solves this problem nicely but in earlier releases one has to do a bit more work.
When you know the right keywords Google finds a plenty of great resources, e.g.
String Aggregation Techniques
listagg function in 11g release 2
the collect function in 10g
I have collected the following examples from the above mentioned resources. Hope this gives you a good starting point:
The table that will be queried:
create table foo (d1 number, d2 varchar2(10));
insert all
into foo values(1, 'a')
into foo values(2, 'b')
into foo values(3, 'c')
select 1 from dual;
commit;
Oracle 11gR2:
declare
v_str varchar2(32767);
begin
select listagg('key = ' || d1 || ' value = ' || d2, chr(10))
within group (order by d1)
into v_str
from foo;
dbms_output.put_line(v_str);
exception
when value_error then
dbms_output.put_line('Exception: trying to insert too many characters to a varchar2 variable.');
end;
/
Oracle 10g:
create or replace type str_list_t as table of varchar2(32676);
/
create function to_string (
nt_in in str_list_t,
delimiter_in in varchar2 default ','
) return varchar2 is
v_idx pls_integer;
v_str varchar2(32767);
v_dlm varchar2(10);
begin
v_idx := nt_in.first;
while v_idx is not null loop
v_str := v_str || v_dlm || nt_in(v_idx);
v_dlm := delimiter_in;
v_idx := nt_in.next(v_idx);
end loop;
return v_str;
end;
/
declare
v_str varchar2(32676);
begin
select to_string(cast(collect('key = ' || d1 || ' value = ' || d2) as str_list_t), chr(10))
into v_str
from foo;
dbms_output.put_line(v_str);
exception
when value_error then
dbms_output.put_line('Exception: trying to insert too many characters to a varchar2 variable.');
end;
/
Note how I'll catch value_error exception that will be raised if the aggregated string won't fit into the reserved varchar2 capacity.
Output of both examples:
key = 1 value = a
key = 2 value = b
key = 3 value = c

How can I get the table description (fields and types) from Firebird with dbExpress

I have written a tool for displaying database structures using the GetTableNames and GetFieldNames methods of TSQLConnection. How can I get the types of each field name similar to the following list (which is part of the DDL required to build the table)?
TABLE: ARTICLES
ID INTEGER NOT NULL
PRINTED SMALLINT DEFAULT 0
ACADEMIC SMALLINT
RELEVANCE SMALLINT
SOURCE VARCHAR(64) CHARACTER SET WIN1251 COLLATE WIN1251
NAME VARCHAR(128) CHARACTER SET WIN1251 COLLATE WIN1251
FILENAME VARCHAR(128) CHARACTER SET WIN1251 COLLATE WIN1251
NOTES VARCHAR(2048) CHARACTER SET WIN1251 COLLATE WIN1251
This is incomplete (because I've never used Firebird array data types) and not much tested but perhaps it will give you a good starting point:
SELECT
RF.RDB$FIELD_NAME FIELD_NAME,
CASE F.RDB$FIELD_TYPE
WHEN 7 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'SMALLINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 8 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'INTEGER'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 9 THEN 'QUAD'
WHEN 10 THEN 'FLOAT'
WHEN 12 THEN 'DATE'
WHEN 13 THEN 'TIME'
WHEN 14 THEN 'CHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ') '
WHEN 16 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'BIGINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 27 THEN 'DOUBLE'
WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN 'VARCHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')'
WHEN 40 THEN 'CSTRING' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')'
WHEN 45 THEN 'BLOB_ID'
WHEN 261 THEN 'BLOB SUB_TYPE ' || F.RDB$FIELD_SUB_TYPE
ELSE 'RDB$FIELD_TYPE: ' || F.RDB$FIELD_TYPE || '?'
END FIELD_TYPE,
IIF(COALESCE(RF.RDB$NULL_FLAG, 0) = 0, NULL, 'NOT NULL') FIELD_NULL,
CH.RDB$CHARACTER_SET_NAME FIELD_CHARSET,
DCO.RDB$COLLATION_NAME FIELD_COLLATION,
COALESCE(RF.RDB$DEFAULT_SOURCE, F.RDB$DEFAULT_SOURCE) FIELD_DEFAULT,
F.RDB$VALIDATION_SOURCE FIELD_CHECK,
RF.RDB$DESCRIPTION FIELD_DESCRIPTION
FROM RDB$RELATION_FIELDS RF
JOIN RDB$FIELDS F ON (F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE)
LEFT OUTER JOIN RDB$CHARACTER_SETS CH ON (CH.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID)
LEFT OUTER JOIN RDB$COLLATIONS DCO ON ((DCO.RDB$COLLATION_ID = F.RDB$COLLATION_ID) AND (DCO.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID))
WHERE (RF.RDB$RELATION_NAME = :TABLE_NAME) AND (COALESCE(RF.RDB$SYSTEM_FLAG, 0) = 0)
ORDER BY RF.RDB$FIELD_POSITION;
Use direct access to RDB$ tables. For example:
SELECT * FROM rdb$relations
will give you a list of all tables in a database.
SELECT
*
FROM
rdb$relation_fields rf JOIN rdb$fields f
ON f.rdb$field_name = rf.rdb$field_source
WHERE
rf.rdb$relation_name = :RN
will result in a list of all fields of given table
with information of field type. Param RN is a name of the table.
Using information from RDB$tables one can easily construct DDL
statement. The query below gives you a hint how to do it:
SELECT
TRIM(rf.rdb$field_name) || ' ' ||
IIF(rdb$field_source LIKE 'RDB$%',
DECODE(f.rdb$field_type,
8, 'INTEGER',
12, 'DATE',
37, 'VARCHAR',
14, 'CHAR',
7, 'SMALLINT'),
TRIM(rdb$field_source)) ||
IIF((rdb$field_source LIKE 'RDB$%') AND (f.rdb$field_type IN (37, 14)),
'(' || f.rdb$field_length || ')',
'') ||
IIF((f.rdb$null_flag = 1) OR (rf.rdb$null_flag = 1),
' NOT NULL', '')
FROM
rdb$relation_fields rf JOIN rdb$fields f
ON f.rdb$field_name = rf.rdb$field_source
WHERE
rf.rdb$relation_name = '<put_your_table_name_here>'
Using the link which TLama provided, I found my own solution, which is somewhat similar to the above solutions, but simpler.
SELECT R.RDB$FIELD_NAME AS field_name,
CASE F.RDB$FIELD_TYPE
WHEN 7 THEN 'SMALLINT'
WHEN 8 THEN 'INTEGER'
WHEN 9 THEN 'QUAD'
WHEN 10 THEN 'FLOAT'
WHEN 11 THEN 'D_FLOAT'
WHEN 12 THEN 'DATE'
WHEN 13 THEN 'TIME'
WHEN 14 THEN 'CHAR'
WHEN 16 THEN 'INT64'
WHEN 27 THEN 'DOUBLE'
WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN 'VARCHAR'
WHEN 40 THEN 'CSTRING'
WHEN 261 THEN 'BLOB'
ELSE 'UNKNOWN'
END AS field_type,
F.RDB$FIELD_LENGTH AS field_length,
CSET.RDB$CHARACTER_SET_NAME AS field_charset
FROM RDB$RELATION_FIELDS R
LEFT JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME
LEFT JOIN RDB$CHARACTER_SETS CSET ON F.RDB$CHARACTER_SET_ID = CSET.RDB$CHARACTER_SET_ID
WHERE R.RDB$RELATION_NAME= :p1
ORDER BY R.RDB$FIELD_POSITION
p1 is the table name which is passed as a parameter to the query.
In context, I have a treeview which has as its nodes the table names of a given database; for each node, the child nodes are the fields along with their definitions.
sqlcon.GetTableNames (dbTables); // sqlcon is the TSQLConnection
tv.items.Clear;
for i:= 1 to dbTables.count do
begin
node:= tv.items.Add (nil, dbTables[i - 1]);
with qFields do // the above query
begin
params[0].asstring:= dbTables[i - 1];
open;
while not eof do
begin
tv.items.addchild (node, trim (fieldbyname ('field_name').asstring) + ', ' +
trim (fieldbyname ('field_type').asstring) + ', ' +
fieldbyname ('field_length').asstring + ', ' +
fieldbyname ('field_charset').asstring);
next
end;
close
end
end;
Here is a screenshot of the program in action. I realise that the format is not the same as the DDL which I quoted, but it's obvious what each field means (at least to me, and this is a program for my private use).
I made a litle change to the first option to support computed by fields, add field_position and made a view to make more easy.
CREATE VIEW TABLES (
TABLE_NAME,
FIELD_NAME,
FIELD_POSITION,
FIELD_TYPE,
FIELD_NULL,
FIELD_CHARSET,
FIELD_COLLATION,
FIELD_DEFAULT,
FIELD_CHECK,
FIELD_DESCRIPTION
)
AS
SELECT
RF.RDB$RELATION_NAME,
RF.RDB$FIELD_NAME FIELD_NAME,
RF.RDB$FIELD_POSITION FIELD_POSITION,
CASE F.RDB$FIELD_TYPE
WHEN 7 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'SMALLINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 8 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'INTEGER'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 9 THEN 'QUAD'
WHEN 10 THEN 'FLOAT'
WHEN 12 THEN 'DATE'
WHEN 13 THEN 'TIME'
WHEN 14 THEN 'CHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ') '
WHEN 16 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'BIGINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 27 THEN 'DOUBLE'
WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN
IIF (COALESCE(f.RDB$COMPUTED_SOURCE,'')<>'',
'COMPUTED BY ' || CAST(f.RDB$COMPUTED_SOURCE AS VARCHAR(250)),
'VARCHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')')
WHEN 40 THEN 'CSTRING' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')'
WHEN 45 THEN 'BLOB_ID'
WHEN 261 THEN 'BLOB SUB_TYPE ' || F.RDB$FIELD_SUB_TYPE
ELSE 'RDB$FIELD_TYPE: ' || F.RDB$FIELD_TYPE || '?'
END FIELD_TYPE,
IIF(COALESCE(RF.RDB$NULL_FLAG, 0) = 0, NULL, 'NOT NULL') FIELD_NULL,
CH.RDB$CHARACTER_SET_NAME FIELD_CHARSET,
DCO.RDB$COLLATION_NAME FIELD_COLLATION,
COALESCE(RF.RDB$DEFAULT_SOURCE, F.RDB$DEFAULT_SOURCE) FIELD_DEFAULT,
F.RDB$VALIDATION_SOURCE FIELD_CHECK,
RF.RDB$DESCRIPTION FIELD_DESCRIPTION
FROM RDB$RELATION_FIELDS RF
JOIN RDB$FIELDS F ON (F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE)
LEFT OUTER JOIN RDB$CHARACTER_SETS CH ON (CH.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID)
LEFT OUTER JOIN RDB$COLLATIONS DCO ON ((DCO.RDB$COLLATION_ID = F.RDB$COLLATION_ID) AND (DCO.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID))
WHERE (COALESCE(RF.RDB$SYSTEM_FLAG, 0) = 0)
ORDER BY RF.RDB$FIELD_POSITION
;

SQLite/Postgres/Heroku: Problem translating query

Heroku throws an error on my Postgres-Query stating:
ActiveRecord::StatementInvalid
(PGError: ERROR: syntax error at or
near "date" 2011-05-03T13:58:22+00:00
app[web.1]: LINE 1: ...2011-05-31')
GROUP BY EXTRACT(YEAR FROM TIMESTAMP
date)||EXT...
The SQLite query in development works as expected. Here is the code:
def self.calculate(year, month, user_id, partner_id)
case ActiveRecord::Base.connection.adapter_name
when 'SQLite'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('entries.date, ' +
'sum(case when joint = "f" then amount_calc else 0 end) as sum_private, ' +
'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' +
'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +
'sum(case when compensation = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
'sum(case when compensation = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group("strftime('%Y-%m', date)")
when 'PostgreSQL'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('entries.date, ' +
'sum(case when joint = "f" then amount_calc else 0 end) as sum_private, ' +
'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' +
'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +
'sum(case when compensation = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
'sum(case when compensation = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group("EXTRACT(YEAR FROM TIMESTAMP date)||EXTRACT(MONTH FROM TIMESTAMP date)")
else
raise 'Query not implemented for this DB adapter'
end
end
I would really appreciate any hints. And as I am already asking a question here, I am uncertain about the case when joint = "t" in the sums in both queries too, is there a better way to do this?
UPDATE
Thanks to both peufeu and a horse with no name the code now looks like:
when 'PostgreSQL'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('min(entries.date) as date, ' +
'sum(case when joint = false then amount_calc else 0 end) as sum_private, ' +
'sum(case when joint = true and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' +
'sum(case when joint = true and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +
'sum(case when compensation = true and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
'sum(case when compensation = true and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group('EXTRACT(YEAR FROM "date"), EXTRACT(MONTH FROM "date")')
...and works like expected.
Another of my statement runs into troubles now and I edit it here as it seems related to the answer of peufeu. Model/Controller:
def self.all_entries_month(year, month, user_id, partner_id)
mydate = Date.new(year, month, 1)
where(':user_id = entries.user_id OR (:partner_id = entries.user_id AND entries.joint = :true)', {
:user_id => user_id,
:partner_id => partner_id,
:true => true
}).
where(':first_day <= entries.date AND entries.date <= :last_day', {
:first_day => mydate,
:last_day => mydate.at_end_of_month
})
end
# group by tag and build sum of groups named group_sum
def self.group_by_tag
group('tag').
select('entries.*, sum(amount_calc) as group_sum')
end
controller:
#income = Entry.all_entries_month(#year, #month, current_user.id, current_partner.id).income
#cost = Entry.all_entries_month(#year, #month, current_user.id, current_partner.id).cost
# group cost by categories
#group_income = #income.group_by_tag.order('group_sum desc')
#group_cost = #cost.group_by_tag.order('group_sum')
The error is:
ActionView::Template::Error (PGError: ERROR: column "entries.id" must appear in the GROUP BY clause or be used in an aggregate function
2011-05-03T18:35:20+00:00 app[web.1]: : SELECT entries.*, sum(amount_calc) as group_sum FROM "entries" WHERE (1 = entries.user_id OR (2 = entries.user_id AND entries.joint = 't')) AND ('2011-04-01' <= entries.date AND entries.date <= '2011-04-30') AND (amount_calc <= 0 AND compensation = 'f') GROUP BY tag ORDER BY group_sum):
2011-05-03T18:35:20+00:00 app[web.1]: 6: </thead>
2011-05-03T18:35:20+00:00 app[web.1]: 7: <tbody>
2011-05-03T18:35:20+00:00 app[web.1]: 8: <% if categories %>
2011-05-03T18:35:20+00:00 app[web.1]: 9: <% categories.each do |category| %>
2011-05-03T18:35:20+00:00 app[web.1]: 10: <tr>
2011-05-03T18:35:20+00:00 app[web.1]: 11: <td class="align-left"><%= category.tag %></td>
2011-05-03T18:35:20+00:00 app[web.1]: 12: <td class="align-right"><%= my_number_to_percentage (category.group_sum.to_f / total_cost) * 100 %></td>
UPDATE 2: I found the solution
# group by tag and build sum of groups named group_sum
def self.group_by_tag
group('tag').
select('tag, sum(amount_calc) as group_sum')
end
Problem is there :
EXTRACT(YEAR FROM TIMESTAMP date)||EXTRACT(MONTH FROM TIMESTAMP date)
Solution :
EXTRACT(YEAR FROM "date"), EXTRACT(MONTH FROM "date")
The word "TIMESTAMP" is a bit out of place there ;) ... also :
Since DATE is a reserved SQL keyword, it is a very bad idea to use it as a column name. Here, I quoted it using " so postgres doesn''t get confused.
And you don't need to waste CPU time building a string concatenating your two EXTRACTs, just remember GROUP BY can use several parameters.
Then of course the query will fail because you SELECT "date" but your dont' GROUP BY date. postgres has no way to know which date you want from all the rows which have the same Year-Month. MySQL will return a value at random from the rows, postgres likes correctness so it will throw an error. You could SELECT min(date) for instance, which would be correct.
I am uncertain about the case when joint = "t"
That depends on how you want to get your results, nothing wrong there.
Maybe using several queries would scan a smaller portion of the table (I don't know about your dataset) or maybe not.
I don't know ruby/heroku, but the expression
joint = "t"
refers to a column t in PostgreSQL because object names are quoted with double quotes. So unless Ruby/Heroku is replacing those double quotes with single quotes that will be an invalid condition.
If t should be string literal (the character t) then you need to use single quotes: joint = 't'
If joint is of type boolean then you should use joint = true in PostgreSQL

Resources