Not able to assign values of select to variables in oracle - oracle

I have defined to nvachar variables and I am assigning like below
select sap_id, (CAST(TO_TIMESTAMP_TZ(bill_end_date, 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM') as date)- CAST(TO_TIMESTAMP_TZ(bill_start_date, 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM') as date)) as noofdays
into V_SAPID_NOOFDAYS, V_NOOFDAYS
from IPCOLO_IPFEE_CALC_BIL;
But it's giving me error as
ORA-01422: exact fetch returns more than requested number of rows
how to assign it and use it?

Your query is returning multiple rows, and as you are including the ID in the results and aren't filtering (via a where clause) that seems to be what you expect and want.
You could loop over the results, which doesn't need any PL/SQL variables to be defined as the cursor does that internally:
for r in (
select sap_id, (CAST(TO_TIMESTAMP_TZ(bill_end_date, 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM') as date)- CAST(TO_TIMESTAMP_TZ(bill_start_date, 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM') as date)) as noofdays
from IPCOLO_IPFEE_CALC_BIL
)
loop
-- do something with each pair of values
dbms_output.put_line(r.sap_id || ': ' || r.noofdays);
end loop;
Or you could define a record type and collection type, have a collection variable, and bulk collect into the collection. (Or separate collections for the two values, but then you have to keep track of them separately).
It depends what you need to do with the data once you've queried it.
As an aside, your calculation will give you fractions of days, so you might want to trunc or floor it to get an integer, again depending on what you are doing. It also ignores the time zones of the original strings - you are converting the strings to timestamps with time zones, but when you cast to date you're losing that. It's possible that could affect the result even if both times are in the same region, of they cross a DST boundary. But perhaps they're all UTC, so it wouldn't matter anyway. Still, you can subtract the timestamps to get an interval, and then extract the days from that.

Related

How to solve the error of this select writing in oracle?

I want to do the select from the range 06/01/2019 to 09/01/2019, but the program returns other ranges, like show the image.
Remember that my date field in the database is varchar2, and I store it in the format dd / MM / yyyy
Expanding #user7294900's answer, as you're storing your 'date' value as a string, you have to convert both the column value and your target-range values from strings to actual dates.
If you do:
WHERE DATA_TRANSACAO BETWEEN '06/01/2019' AND '09/01/2019'
then you are doing string comparisons between the column value and those literals, and as a string anything starting with '07' or '08' (and most starting with '06') will be considered part of that range. Oracle is treating them purely as strings, it doesn't know or care that you have the notion of days and months, or that you consider the order to be something different.
If you're really stuck with strings then you need to compare both sides of the comparison to dates:
WHERE TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') BETWEEN TO_DATE('06/01/2019', 'DD/MM/YYYY')
AND TO_DATE('09/01/2019', 'DD/MM/YYYY')
or with ANSI literals:
WHERE TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') BETWEEN DATE '2019-06-01' AND DATE '2019-01-09'
or (though this is more useful if there can be non-midnight times, which isn't the case in your data):
WHERE TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') >= DATE '2019-06-01'
AND TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') < DATE '2019-01-10' -- notice one day later
You should also be aware that having to convert the string value for every row in your data before you can compare it means that if you have a simple index on that column it cannot be used, and a full table scan will be done even for a tiny target date range. (You can add a function-based index to speed this up, but that's a sticking plaster approach.)
This kind of thing is why you should be using the correct data type. There is also no need to store the date and time parts separately, and that makes other types of comparison and data extract harder and more complicated than it needs to be too.
I would really recommend that you revisit your data model and change from separate strings for DATA_TRANSACAO and HORA_TRANSACAO to a single DATE column which contains the whole date/time value as the proper data type. You'll then be able to use the simpler form, with no conversion of your table data, of something like (with a made-up combined column name):
WHERE DATAHORA_TRANSACAO >= DATE '2019-06-01'
AND DATAHORA_TRANSACAO < DATE '2019-01-10' -- notice one day later
If you expect 06/08 values to represent days, be explicit:
BETWEEN TO_DATE('06/01/2019','DD/MM/YYYY')
AND TO_DATE('09/01/2019','DD/MM/YYYY')
Currently it return 06/08 values as months, In general when compare date column use dates and not strings
I am strongly ENCOURAGING you to use to_date to avoid ambiguities (compare strings to strings, numbers to numbers, dates to dates -- don't RISK confusion comparing strings to numbers, dates to strings).

Obtaining a list of date to control file import

I have a need to pull in via FTP only files from dates I haven't pulled data. The data pulls happen nightly, but occasionally there is an issue and a night gets skipped, and it will be picked up the following night.
I located a query on StackOverflow which addressed most of my problem. However, I'm left with an uncomfortable solution and a lingering question.
I have a table with data from each file downloaded. The crux of the data I'm using for this example is EXTRACT_DATE. The file format is filename_20160809.csv, as an example. Uses the 4-digit year, 2-digit month and 2-digit day to make unique. The FTP location has thousands of files, and I only want to grab the new, based on this date.
First, I get the latest EXTRACT_DATE from my table as such
SELECT MAX(EXTRACT_DATE) INTO checkDate FROM FILE_DETAILS;
I had attempted to do this in a single query, but couldn't work it, and finally attempted to create a variable, checkDate, and use this variable in a subsequent query to obtain my list. However, having two queries in a single script is not allowed, or I haven't found a way to do it. So my primary issue is to return the value of the latest date. Then call a new query or procedure with the value incorporated in to obtain my list, with this query
SELECT TO_DATE(checkDate, 'MM/DD/YYYY') + rownum AS EXTRACT_DATES
FROM ALL_OBJECTS
WHERE TO_DATE(checkDate, 'MM/DD/YYYY') + rownum <= TO_DATE(SYSDATE, 'DD-MM-YY');
This solution is messy and uncomfortable, I would prefer a single query to get back my results, rather then 2 scripts or a script and a procedure.
The lingering question, the result from the query returns dates in the mm/dd/yyyy format; 8/9/2016. I adjusted the TO_DATE to match.
Initially, it was set to return as YYYY-MM-DD; 2016-08-09.
However, it wouldn't return in this format. It would only come back as 8/9/2016, regardless of the TO_DATE formatting used. I don't understand why the date is coming back in this format.
SELECT TO_DATE('2-AUG-2016', 'DD-MON-RR') + rownum AS EXTRACT_DATES
FROM ALL_OBJECTS
WHERE TO_DATE('2-AUG-2016', 'DD-MON-RR') + rownum <= TO_DATE(SYSDATE, 'DD-MM-YY');
EXTRACT_DATES
8/3/2016
8/4/2016
8/5/2016
8/6/2016
8/7/2016
8/8/2016
8/9/2016
NLS_DATE_FORMAT is set to DD-MON-RR, with American as NLS_DATE_LANGUAGE.

Oracle a non-numeric character was found where a numeric was expected

I have problem with some thing input multiple date through prompt
my query something like
select something
from
something
where
to_date (bus.TDATE) not in (:didb1)
I want to input like '12-Jun-2016','11-Jun-2016'
i am doing php oracle,
My code following:
select bus.*,con.* from
BusinessPayment bus,
ConsumerPayment con
where bus.TDATE=con.TDATE
and to_date (bus.TDATE) not in (:didbv)
');
$bscode1='11-June-2016','10-June-2016','09-June-2016';
oci_bind_by_name($stid, ':didbv', $bscode1);
oci_execute($stid);
You can't use a single bind variable to represent a list of values in an in() clause. Bind variables don't contain multiple values; the value you bind is seen as a single string value. So you're effectively doing:
where
to_date (bus.TDATE) not in (q'['11-June-2016','10-June-2016','09-June-2016']')
If TDATE is already a date type - which it hopefully is, and the joins suggest it might be - then you should not be passing that through to_date() - you are doing an implicit conversion from date to string, then a semi-explicit conversion back to a date, both using your session's NLS_DATE_FORMAT. That is either doing unnecessary work, or losing resolution, depending on your format model and whether the dates have times. If the intention is to ignore the time part of the value then there are better ways to do that.
Anyway, since that is a date now whatever type it was before, the right had side of that filter will also be converted to a date, so you're doing:
where
to_date (bus.TDATE) not in (to_date(q'['11-June-2016','10-June-2016','09-June-2016']'))
... and it's that which is throwing the exception, whatever your NLS settings are. Even if you passed a single value it would error because of the embedded single quotes. You can replicate that with:
select to_date(q'['11-June-2016','10-June-2016','09-June-2016']') from dual;
which also gets ORA-01858: a non-numeric character was found where a numeric was expected.
Ideally you would pass the individual date strings as elements of an array, but you can also work around this with an XML hack. You can pass a single string containing comma-delimited values to XMLTable(), and it will split it in to rows:
select * from xmltable(q'['11-June-2016','10-June-2016','09-June-2016']');
COLUMN_VALUE
--------------------------------------------------------------------------------
11-June-2016
10-June-2016
09-June-2016
Then you can convert each of those values to a date:
select to_date(column_value, 'DD-Month-YYYY')
from xmltable(q'['11-June-2016','10-June-2016','09-June-2016']')
and use that in a subquery or a join:
select * from dual
where trunc(sysdate) not in (select to_date(column_value, 'DD-Month-YYYY')
from xmltable(q'['11-June-2016','10-June-2016','09-June-2016']'));
And that works with a string bind variable, so your code would be:
select bus.*,con.*
from BusinessPayment bus
join ConsumerPayment con
on bus.TDATE=con.TDATE
where bus.TDATE not in (select to_date(column_value, 'DD-Month-YYYY')
from xmltable(:didbv));
I've modified this to use ANSI join syntax, which isn't relevant but there's rarely a reason to use the ancient syntax you have. If TDATE does include the time you'll need to modify it a bit - truncating is simplest but might affect performance.
Or you could join:
select bus.*,con.*
from xmltable(:didbv) x
join BusinessPayment bus
on bus.TDATE >= to_date(x.column_value, 'DD-Month-YYYY')
and bus.TDATE < to_date(x.column_value, 'DD-Month-YYYY') + 1
join ConsumerPayment con
on bus.TDATE=con.TDATE
In this version TDATE can have a time, and it matches any time on the days specified in the bind variable.
It would be better to supply the dates in a format that doesn't have the month name in it, as that could give you problems when run in different sessions or locales. If you know the PHP side will always be English you could force that to be recognised:
on bus.TDATE >= to_date(x.column_value, 'DD-Month-YYYY', #NLS_DATE_LANGUAGE=ENGLISH')
but if you can change the PHP format to use month numbers, and change the Oracle format model to match, that would be a bit simpler.

How can I use a variable to pick which column is selected?

How can we use variable as column name ?
In my table days (MONDAY,TUESDAY..) are column names.
I want to get the DAY dynamically and use AS COLUMN NAME in my query.
My query :
SELECT EMP FROM SCHEDULE WHERE "DAY"(Dynamically I want) =1;
You simply can't use variables to change the actual text of the queries. variables can be used just in place of literal values (dates, strings, times, numbers) but they can't change the text of the actual command.
The technical reason is that (oversimplyfying the things) oracle FIRST parses the text, establishes an execution plan and only after this considers the values of the variables. more or less you can think (this is just an analogy, of course, it is not really the same thing!) that oracle "compiles" the queries like an C++ compiler compiles the source code of a function: it is not possible to pass a c++ procedure a variable that modifies the text of the procedure itself.
what you have to do is to rethink your approach taking in consideration what I just said:
SELECT EMP FROM SCHEDULE
WHERE
(case :DAY_I_WANT
WHEN 'MONDAY' then -- 'MONDAY' is the string value of the variable :DAY_I_WANT
monday -- monday, here is the column whose value I want CASE to return
WHEN 'TUESDAY' then tuesday
...
...
WHEN 'SUNDAY' then sunday
end) = 1
keep in mind that this solution will not take advantage on any index on the MONDAY..SUNDAY columns. the best approach would be to create a different data structure where you have a separate row for each day and a proper dayofweek column. If you do this, you will be able able to write:
select EMP from schedule
where schedule.DAY = :DAY_I_WANT
and it will allow you to create an index on the DAY column, speeding up searches.
Having a separate column for each day equals to be looking for troubles.

Oracle scalar function in WHERE clause leads to poor performance

I've written a scalar function (DYNAMIC_DATE) that converts a text value to a date/time. For example, DYANMIC_DATE('T-1') (T-1 = today minus 1 = 'yesterday') returns 08-AUG-2012 00:00:00. It also accepts date strings: DYNAMIC_DATE('10/10/1974').
The function makes use of CASE statements to parse the sole parameter and calculate a date relative to sysdate.
While it doesn't make use of any table in its schema, it does make use of TABLE type to store date-format strings:
TYPE VARCHAR_TABLE IS TABLE OF VARCHAR2(10);
formats VARCHAR_TABLE := VARCHAR_TABLE ('mm/dd/rrrr','mm-dd-rrrr','rrrr/mm/dd','rrrr-mm-dd');
When I use the function in the SELECT clause, the query returns in < 1 second:
SELECT DYNAMIC_DATE('MB-1') START_DATE, DYNAMIC_DATE('ME-1') END_DATE
FROM DUAL
If I use it against our date dimension table (91311 total records), the query completes in < 1 second:
SELECT count(1)
from date_dimension
where calendar_dt between DYNAMIC_DATE('MB-1') and DYNAMIC_DATE('ME-1')
Others, however, are having problems with the function if it is used against a larger table (26,301,317 records):
/*
cost: 148,840
records: 151,885
time: ~20 minutes
*/
SELECT count(1)
FROM ORDERS ord
WHERE trunc(ord.ordering_date) between DYNAMIC_DATE('mb-1') and DYNAMIC_DATE('me-1')
However, the same query, using 'hard coded' dates, returns fairly rapidly:
/*
cost: 144,257
records: 151,885
time: 62 seconds
*/
SELECT count(1)
FROM ORDERS ord
WHERE trunc(ord.ordering_date) between to_date('01-JUL-2012','dd-mon-yyyy') AND to_date('31-JUL-2012','dd-mon-yyyy')
The vendor's vanilla installation doesn't include an index on the ORDERING_DATE field.
The explain plans for both queries are similar:
with function:
with hard-coded dates:
Is the DYNAMIC_DATE function being called repeatedly in the WHERE clause?
What else might explain the disparity?
** edit **
A NONUNIQUE index was added to ORDERS table. Both queries execute in < 1 second. Both plans are the same (approach), but the one with the function is lower cost.
I removed the DETERMINISTIC keyword from the function; the query executed in < 1 second.
Is the issue really with the function or was it related to the table?
3 years from now, when this table is even larger, and if I don't include the DETERMINISTIC keyword, will query performance suffer?
Will the DETERMINISTIC keyword have any affect on the function's results? If I run DYNAMIC_DATE('T-1') tomorrow, will I get the same results as if I ran it today (08/09/2012)? If so, this approach won't work.
If the steps of the plan are identical, then the total amount of work being done should be identical. If you trace the session (something simple like set autotrace on in SQL*Plus or something more sophisticated like an event 10046 trace), or if you look at DBA_HIST_SQLSTAT assuming you have licensed access to the AWR tables, are you seeing (roughly) the same amount of logical I/O and CPU consumption for the two queries? Is it possible that the difference in runtime you are seeing is the result of the data being cached when you run the second query?
I am guessing that the problem isn't with your function. Try creating a function based index on trunc(ord.ordering_date) and see the explain plans.
CREATE INDEX ord_date_index ON ord(trunc(ord.ordering_date));

Resources