Oracle Apex multiple value in Column - oracle

I'm having a text field with comma sepeated values in my Oracle Apex page (e.g. "F01C, F01D, G01A"). The values are stored in only one column of my table (Table: HC_DEFHANDBUCH_B5 Column DRG).
------
PK_ID OPS MDC DRG OPS_FILL MDC_FILL YEAR
214098 1-100 2 C01A, C01B, C14Z 1-100 2 2017
214099 - 15 P67D 1-100 15 2017
214100 1-204.2 15 P67D 1-204.2 15 2017
214101 1-204.3 15 P67D 1-204.3 15 2017
-------
I tried to run a query in an interactive report to select all DRGs for PK_ID '214098' from the Table HC_GDRG_FPK which is having one Row per DRG:
Select * FROM HC_GDRG_FPK a
WHERE a.DRG IN (Select '(''' || REPLACE(b.DRG,', ',''', ''') || ''')' FROM HC_DEFHANDBUCH_B5 b WHERE b.PK_ID='214098')
AND a.Jahr='2017'
Does anyone have an idea how to get this working? I get it working on a single value.
Thanks in advance.

I could finally solve it with regular expressions. #Justin Cave: Thanks for the hint.
select * from HC_GDRG_FPK where DRG in (
select regexp_substr(:P39_CALL_DRGS,'[^,]+', 1, level) from HC_DEFHANDBUCH_B5
connect by regexp_substr(:P39_CALL_DRGS, '[^,]+', 1, level) is not null )
AND Jahr ='2017'

Related

In oracle sql queries - Is it valid to use % in between a search string

For example
select * from tbl where msg like ‘%\<CDT\>5000%\<DBT\>1000%’
msg: <TXN1><CDT>5000<\CDT><\TXN1><something else><TXN2><DBT>1000<\DBT><\TXN2>
I am looking to extract column values, if it has CDT as 5000 and DBT as 1000
Title question is:
is it valid to use % in between a search string, for example
where msg like ‘%5000%1000%’
Yes, it is valid.
What would CDT and DBT be? Extract which column values?
I'm not sure but I think what you want is to take the value of everything between and
The following code does that
with raw_text(t) as( select '<TXN1><CDT>5000</CDT></TXN1>' from dual)
SELECT *
FROM raw_text
CROSS JOIN
XMLTABLE (
'//CDT'
PASSING XMLTYPE (raw_text.t)
COLUMNS CDT VARCHAR2 (1000) PATH './text()')
Lets assume that you have valid XML data (a single root element, matching opening and closing tags and using /, and not \, for the closing tags). For example:
CREATE TABLE tbl (id, msg) AS
SELECT 1, '<ROOT><TXN1><CDT>5000</CDT></TXN1><something /><TXN2><DBT>1000</DBT></TXN2></ROOT>' FROM DUAL UNION ALL
SELECT 2, '<ROOT><TXN1><CDT>50000</CDT></TXN1><something /><TXN2><DBT>1000</DBT></TXN2></ROOT>' FROM DUAL UNION ALL
SELECT 3, '<ROOT><TXN1><CDT>5000</CDT></TXN1><something /><TXN2><DBT>10000</DBT></TXN2></ROOT>' FROM DUAL UNION ALL
SELECT 4, '<ROOT><TXN2><DBT>1000</DBT></TXN2><TXN1><CDT>5000</CDT></TXN1><something /></ROOT>' FROM DUAL UNION ALL
SELECT 5, '<ROOT><TXN1><CDT note="match me too">5000</CDT></TXN1><something /><TXN2><DBT>1000</DBT></TXN2></ROOT>' FROM DUAL;
Where ids 1, 4 and 5 all have values 5000 and 1000 but id 4 has the order of the transactions reversed in the XML (which is completely valid XML and does not change the data at all) and id 5 is the same as id 1 but with an added attribute on the CDT element. ids 2 has CDT of 50000 instead of 5000 and 3 has DBT of 10000 instead of 1000.
Then, yes, you could use:
SELECT *
FROM tbl
WHERE msg LIKE '%<CDT>5000%<DBT>1000%'
But it would return rows 1, 2 and 3 and would not match row 4 or 5. This is probably not what you want.
You could eliminate rows 2 and 3 by matching the end tags as well:
SELECT *
FROM tbl
WHERE msg LIKE '%<CDT>5000</CDT>%<DBT>1000</DBT>%'
But that still does not match when the tags are reversed or when there are additional attributes.
If you want to match the values then you can use XMLEXISTS:
SELECT *
FROM tbl
WHERE XMLEXISTS('//CDT[text()=5000]' PASSING XMLTYPE(msg))
AND XMLEXISTS('//DBT[text()=1000]' PASSING XMLTYPE(msg))
Which outputs:
ID
MSG
1
<ROOT><TXN1><CDT>5000</CDT></TXN1><something /><TXN2><DBT>1000</DBT></TXN2></ROOT>
4
<ROOT><TXN2><DBT>1000</DBT></TXN2><TXN1><CDT>5000</CDT></TXN1><something /></ROOT>
5
<ROOT><TXN1><CDT note="match me too">5000</CDT></TXN1><something /><TXN2><DBT>1000</DBT></TXN2></ROOT>
db<>fiddle here

Oracle SQL Developer get table rows older than n months

In Oracle SQL Developer, I have a table called t1 who have two columns col1 defined as NUMBER(19,0) and col2 defined as TIMESTAMP(3).
I have these rows
col1 col2
1 03/01/22 12:00:00,000000000
2 03/01/22 13:00:00,000000000
3 26/11/21 10:27:11,750000000
4 26/11/21 10:27:59,606000000
5 16/12/21 11:47:04,105000000
6 16/12/21 12:29:27,101000000
My sysdate looks like this:
select sysdate from dual;
SYSDATE
03/03/22
I want to create a stored procedure (SP) which will delete rows older than 2 months and displayed message n rows are deleted
But when i execute this statement
select * from t1 where to_date(TRUNC(col2), 'DD/MM/YY') < add_months(sysdate, -2);
I don't get the first 2 rows of my t1 table. I get more than 2 rows
1 03/01/22 12:00:00,000000000
2 03/01/22 13:00:00,000000000
How can i get these rows and deleted it please ?
In Oracle, a DATE data type is a binary data type consisting of 7 bytes (century, year-of-century, month, day, hour, minute and second). It ALWAYS has all of those components and it is NEVER stored with a particular formatting (such as DD/MM/RR).
Your client application (i.e. SQL Developer) may choose to DISPLAY the binary DATE value in a human readable manner by formatting it as DD/MM/RR but that is a function of the client application you are using and not the database.
When you show the entire value:
SELECT TO_CHAR(ADD_MONTHS(sysdate, -2), 'YYYY-MM-DD HH24:MI:SS') AS dt FROM DUAL;
Then it outputs (depending on time zone):
DT
2022-01-03 10:11:28
If you compare that to your values then you can see that 2022-01-03 12:00:00 is not "more than 2 months ago" so it will not be matched.
What you appear to want is not "more than 2 months ago" but "equal to or more than 2 months, ignoring the time component, ago"; which you can get using:
SELECT *
FROM t1
WHERE col2 < add_months(TRUNC(sysdate), -2) + INTERVAL '1' DAY;
or
SELECT *
FROM t1
WHERE TRUNC(col2) <= add_months(TRUNC(sysdate), -2);
(Note: the first query would use an index on col2 but the second query would not; it would require a function-based index on TRUNC(col2) instead.)
Also, don't use TO_DATE on a column that is already a DATE or TIMESTAMP data type. TO_DATE takes a string as the first argument and not a DATE or TIMESTAMP so Oracle will perform an implicit conversion using TO_CHAR and if the format models do not match then you will introduce errors (and since any user can set their own date format in their session parameters at any time then you may get errors for one user that are not present for other users and is very hard to debug).
db<>fiddle here
Perhaps just:
select *
from t1
where col2 < add_months(sysdate, -2);

Allow multiple values from SSRS in oracle

I have a query that gets contract_types 1 to 10. This query is being used in an SSRS report to filter out a larger dataset. I am using -1 for nulls and -2 for all.
I would like to know how we would allow multiple values - does oracle concatenate the inputs together so '1,2,3' would be passed in? Say we get select -1,0,1 in SSRS, how could we alter the bottom query to return values?
My query to get ContractTypes:
SELECT
ContractType,
CASE WHEN ContractType = -2 THEN 'All'
WHEN ContractType = -1 THEN'Null'
ELSE to_Char(ContractType)
END AS DisplayFigure
FROM ContractTypes
which returns
ContractType DisplayFig
-1 Null
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
This currently is only returning single values or all, not muliple values:
SELECT *
FROM Employee
WHERE NVL(CONTRACT_TYPE, -1) = :contract_type or :contract_type = -2
I'm assuming we want to do something like:
WHERE NVL(CONTRACT_TYPE, -1) IN (:contract_type)
But this doesn't seem to work.
Data in Employee
Name ContractType
Bob 1
Sue 0
Bill Null
Joe 2
In my report, I want to be able to select contract_type as -1(null),0,1 using the 'allow muliple values' checkbox. At the moment, I can only select either 'all' using my -2 value, or single contract types.
My input would be: contract type = -1,1,2
My output would be Bill, Bob, Joe.
This is how I'm executing my code
I use SSRS with Oracle a lot so I see where you're coming from. Thankfully, they work pretty well together.
First make sure the parameter is set to allow multiple values. This adds a Select All option to your dropdown so you don't have to worry about adding a special case for "All". You'll want to make sure the dataset for the parameter has a row with -1 as the Value and a friendly description for the Label.
Next, the WHERE clause would be just as you mentioned:
WHERE NVL(CONTRACT_TYPE, -1) IN (:contract_type)
SSRS automatically populates the values. There is no XML or string manipulation needed. Keep in mind that this will not work with single-value parameters.
If for some reason this still doesn't work as expected in your environment, there is another workaround you can use which is more universal and works even with ODBC connections.
In the dataset parameter properties, use an expression like this to concatenate the values into a single, comma-separated string:
="," + Join(Parameters!Parameter.Value, ",") + ","
Then use an expression like this in your WHERE clause:
where :parameter like '%,' + Column + ',%'
Obviously, this is less efficient because it most likely won't be using an index, but it works.
I don't know SSRS, but - if I understood you correctly, you'll have to split that comma-separated values list into rows. Something like in this example:
SQL> select *
2 from dept
3 where deptno in (select regexp_substr('&&contract_type', '[^,]+', 1, level)
4 from dual
5 connect by level <= regexp_count('&&contract_type', ',') + 1
6 );
Enter value for contract_type: 10,20,40
DEPTNO DNAME LOC
---------- -------------------- --------------------
20 RESEARCH DALLAS
10 ACCOUNTING NEW YORK
40 OPERATIONS BOSTON
SQL>
Applied to your code:
select *
from employee
where nvl(contract_type, -1) in (select regexp_substr(:contract_type, '[^,]+', 1, level)
from dual
connect by level <= regexp_substr(:contract_type, ',') + 1
)
If you have the comma separated list of numbers and then if you like to split it then, the below seems simple and easy to maintain.
select to_number(column_value) from xmltable(:val);
Inputs: 1,2,3,4
Output:
I guess I understood your problem. If I am correct the below should solve your problem:
with inputs(Name, ContractType) as
(
select 'Bob', 1 from dual union all
select 'Sue', 0 from dual union all
select 'Bill', Null from dual union all
select 'Joe', 2 from dual
)
select *
from inputs
where decode(:ContractType,'-2',-2,nvl(ContractType,-1)) in (select to_number(column_value) from xmltable(:ContractType))
Inputs: -1,1,2
Output:
Inputs: -2
Output:

Consolidate rows

I'm trying to cut down on rows a report has. There are 2 assets that return on this query but I want them to show up on one row.
Basically if dc.name LIKE '%CT/PT%' then I want it to be same row as the asset. The SP.SVC_PT_ID is the common field to join them.
There will be times when there is no dc.name LIKE '%CT/PT%' however I still want the DV.MFG_SERIAL_NUM to populated just with a Null to the right.
Select SP.SVC_PT_ID, SP.DEVICE_ID, DV.MFG_SERIAL_NUM, dc.name,
substr(dc.name,26)
From EIP.SVC_PT_DEVICE_REL SP,
eip.device_class dc,
EIP.DEVICE DV
Where SP.EFF_START_TIME < To_date('20170930', 'YYYYMMDD') + 1
and SP.EFF_END_TIME is null
and dc.id = DV.device_class_id
and DV.ID = SP.device_id
ORDER BY SP.SVC_PT_ID, DV.MFG_SERIAL_NUM;
I'm not sure I understand what you are saying; test case would certainly help. You said that query you posted returns two rows (only if we saw which ones ...) but you want them to be displayed as the image you attached to the message.
Generally speaking, you can do that using an aggregate function (such as MAX) on certain column(s), along with the GROUP BY clause that contains the rest of them.
Just for example:
select svc_pt_id, max(ctpt_name) ctpt_name, sum(ctpt_multipler) ctpt_multipler
from ...
group by svc_pt_id
As I said: a test case would help people who'd want to answer the question. True - someone might have understood it far better than I did and will provide assistance nevertheless.
EDIT: after you posted sample data (which, by the way, don't match screenshot you posted previously), maybe something like this might do the job: use analytic function to check whether name contains CT/PT; if so, take its data. Otherwise, display both rows.
SQL> with test as (
2 select 14 svc_pt_id, 446733 device_id, 'Generic Electric' name from dual union
3 select 14, 456517, 'Generic CT/PT, Multiplier' from dual
4 ),
5 podaci as
6 (select svc_pt_id, device_id, name,
7 rank() over (partition by svc_pt_id
8 order by case when instr(name, 'CT/PT') > 1 then 1
9 else 2
10 end) rnk
11 from test
12 )
13 select svc_pt_id, device_id, name
14 from podaci
15 where rnk = 1;
SVC_PT_ID DEVICE_ID NAME
---------- ---------- -------------------------
14 456517 Generic CT/PT, Multiplier
SQL>
My TEST table (created by WITH factoring clause) would be the result of your current query.

Applying where condition on Casted number coulmn in ORACLE from a inner query result

For Sample purpose lets create a table with below schema and fill some sample values
CREATE TABLE games(ID INT ,Name VARCHAR(20));
INSERT INTO games(ID,Name) VALUES (2008,'Beijing');
INSERT INTO games(ID,Name) VALUES (2012,'London');
INSERT INTO games(ID,Name) VALUES (2012,12);
INSERT INTO games(ID,Name) VALUES (2012,654);
Output:
ID NAME
2008 Beijing
2012 London
2012 12
2012 654
In the above table we have both number and string data in the name column lets write a query that uses the REGX to filter only the numerical rows
SELECT TO_NUMBER(Name)as Trimmed FROM games where REGEXP_LIKE(Name, '(?<=\s|^)\d+(?=\s|$)', '')
Output:
TRIMMED
12
654
Now here is the problem if write a where clause of getting values greater than 12 from the above result it throws invalid number.
Select * from (SELECT TO_NUMBER(Name)as Trimmed FROM games where REGEXP_LIKE(Name, '(?<=\s|^)\d+(?=\s|$)', '')) T1 where T1.Trimmed >12 ;
I found this is how the oracle query planning works but is there any other way i can achieve this
This will work:
Select * from
(SELECT Name as Trimmed
FROM games where REGEXP_LIKE(Name, '(?<=\s|^)\d+(?=\s|$)', '')) T1
where to_number(T1.Trimmed) >12 ;
Unfortunately you need a subquery. It can't be done with one where.
This can be done in a single query:
with
inputs as (
select 2008 as id, 'Beijing' as name from dual union all
select 2012 , 'London' from dual union all
select 2012 , '12' from dual union all
select 2012 , '654' from dual
)
select id, name
from inputs
where translate(name, 'a0123456789', 'a') is null
and to_number(regexp_replace(name, '[^[:digit:]]', '')) > 12
;
ID NAME
---------- -------
2012 654
1 row selected.
regexp_replace removes all the characters except digits, so the test can be done regardless of what the name is. If there are no digits in the name, the result is NULL, which can be converted to number (it is still null).
The translate solution for testing for "all-digits" is more efficient than using regexp_like. The odd-looking 'a' in translate is needed due to an oddity in the translate function itself (see the documentation). This test is not needed if all the names are either "all letters" or "all digits" (if "all letters", the second test with regexp_replace would suffice); but the first test is needed if names like "Sydney 2000" are possible in the name column.

Resources