Need a query to seperate char values and number values from table in oracle [closed] - oracle

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I have table TAB1 which is having one column COL1. as shown below.
TAB1
COL1
123
Xyz
CM
44
I need single query which will give following output.
Ccol | Ncol
Xyz | 123
CM | 45

From Oracle 12 you can define a function in a sub-query factoring clause and this can easily determine whether a value is numeric:
Oracle Setup:
CREATE TABLE table_name (COL1) AS
SELECT '123' FROM DUAL UNION ALL
SELECT 'Xyz' FROM DUAL UNION ALL
SELECT 'CM' FROM DUAL UNION ALL
SELECT '44' FROM DUAL UNION ALL
SELECT '1E3' FROM DUAL UNION ALL
SELECT '-1.2' FROM DUAL
Query:
WITH
FUNCTION isNumeric( value VARCHAR2 ) RETURN NUMBER
IS
n NUMBER;
BEGIN
n := TO_NUMBER( value );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
SELECT Ccol,
TO_NUMBER( Ncol ) AS Ncol
FROM (
SELECT col1,
isNumeric( col1 ) AS isNumber,
ROW_NUMBER() OVER ( PARTITION BY isNumeric( col1 ) ORDER BY ROWNUM ) AS rn
FROM table_name
)
PIVOT ( MAX( Col1 ) FOR isNumber IN ( 0 AS Ccol, 1 AS Ncol ) )
ORDER BY rn
Output:
CCOL | NCOL
:--- | ---:
Xyz | 123
CM | 44
null | 1000
null | -1.2
db<>fiddle here
In earlier versions you can use CREATE FUNCTION rather than defining it in the query.

You can try this query:
WITH TAB1(COL1) AS
(
SELECT '123' FROM DUAL UNION ALL
SELECT 'Xyz' FROM DUAL UNION ALL
SELECT 'CM' FROM DUAL UNION ALL
SELECT '44' FROM DUAL
)
-- Actual query starts from here
, CTE AS (SELECT
COL1,
NUMERIC,
ROW_NUMBER() OVER(
PARTITION BY NUMERIC
ORDER BY LENGTH(COL1) DESC -- here I considered that Xyz and 123 both have length 3 and are related and same for CM and 44
) AS RN
FROM
(
SELECT
COL1,
CASE
WHEN REGEXP_LIKE ( COL1,
'^[[:digit:]]+$' ) THEN 'NUMBER'
ELSE 'NOT NUMBER'
END AS NUMERIC
FROM
TAB1
))
SELECT
C.COL1 AS "Ccol",
N.COL1 AS "Ncol"
FROM
CTE N
FULL OUTER JOIN CTE C ON ( N.RN = C.RN )
WHERE
N.NUMERIC = 'NUMBER'
AND C.NUMERIC = 'NOT NUMBER';
Output:
Cco Nco
--- ---
Xyz 123
CM 44
db<>fiddle demo
Cheers!!

Related

ORACLE - How to use LAG to display strings from all previous rows into current row

I have data like below:
group
seq
activity
A
1
scan
A
2
visit
A
3
pay
B
1
drink
B
2
rest
I expect to have 1 new column "hist" like below:
group
seq
activity
hist
A
1
scan
NULL
A
2
visit
scan
A
3
pay
scan, visit
B
1
drink
NULL
B
2
rest
drink
I was trying to solve with LAG function, but LAG only returns one row from previous instead of multiple.
Truly appreciate any help!
Use a correlated sub-query:
SELECT t.*,
(SELECT LISTAGG(activity, ',') WITHIN GROUP (ORDER BY seq)
FROM table_name l
WHERE t."GROUP" = l."GROUP"
AND l.seq < t.seq
) AS hist
FROM table_name t
Or a hierarchical query:
SELECT t.*,
SUBSTR(SYS_CONNECT_BY_PATH(PRIOR activity, ','), 3) AS hist
FROM table_name t
START WITH seq = 1
CONNECT BY
PRIOR seq + 1 = seq
AND PRIOR "GROUP" = "GROUP"
Or a recursive sub-query factoring clause:
WITH rsqfc ("GROUP", seq, activity, hist) AS (
SELECT "GROUP", seq, activity, NULL
FROM table_name
WHERE seq = 1
UNION ALL
SELECT t."GROUP", t.seq, t.activity, r.hist || ',' || r.activity
FROM rsqfc r
INNER JOIN table_name t
ON (r."GROUP" = t."GROUP" AND r.seq + 1 = t.seq)
)
SEARCH DEPTH FIRST BY "GROUP" SET order_rn
SELECT "GROUP", seq, activity, SUBSTR(hist, 2) AS hist
FROM rsqfc
Which, for the sample data:
CREATE TABLE table_name ("GROUP", seq, activity) AS
SELECT 'A', 1, 'scan' FROM DUAL UNION ALL
SELECT 'A', 2, 'visit' FROM DUAL UNION ALL
SELECT 'A', 3, 'pay' FROM DUAL UNION ALL
SELECT 'B', 1, 'drink' FROM DUAL UNION ALL
SELECT 'B', 2, 'rest' FROM DUAL;
All output:
GROUP
SEQ
ACTIVITY
HIST
A
1
scan
null
A
2
visit
scan
A
3
pay
scan,visit
B
1
drink
null
B
2
rest
drink
db<>fiddle here
To aggregate strings in Oracle we use LISAGG function.
In general, you need a windowing_clause to specify a sliding window for analytic function to calculate running total.
But unfortunately LISTAGG doesn't support it.
To simulate this behaviour you may use model_clause of the select statement. Below is an example with explanation.
select
group_
, activity
, seq
, hist
from t
model
/*Where to restart calculation*/
partition by (group_)
/*Add consecutive numbers to reference "previous" row per group.
May use "seq" column if its values are consecutive*/
dimension by (
row_number() over(
partition by group_
order by seq asc
) as rn
)
measures (
/*Other columnns to return*/
activity
, cast(null as varchar2(1000)) as hist
, seq
)
rules update (
/*Apply this rule sequentially*/
hist[any] order by rn asc =
/*Previous concatenated result*/
hist[cv()-1]
/*Plus comma for the third row and tne next rows*/
|| presentv(activity[cv()-2], ',', '') /**/
/*lus previous row's value*/
|| activity[cv()-1]
)
GROUP_ | ACTIVITY | SEQ | HIST
:----- | :------- | --: | :---------
A | scan | 1 | null
A | visit | 2 | scan
A | pay | 3 | scan,visit
B | drink | 1 | null
B | rest | 2 | drink
db<>fiddle here
Few more variants (without subqueries):
SELECT--+ NO_XML_QUERY_REWRITE
t.*,
regexp_substr(
listagg(activity, ',')
within group(order by SEQ)
over(partition by "GROUP")
,'^([^,]+,){'||(row_number()over(partition by "GROUP" order by seq)-1)||'}'
)
AS hist1
,xmlcast(
xmlquery(
'string-join($X/A/B[position()<$Y]/text(),",")'
passing
xmlelement("A", xmlagg(xmlelement("B", activity)) over(partition by "GROUP")) as x
,row_number()over(partition by "GROUP" order by seq) as y
returning content
)
as varchar2(1000)
) hist2
FROM table_name t;
DBFIddle: https://dbfiddle.uk/?rdbms=oracle_21&fiddle=9b477a2089d3beac62579d2b7103377a
Full test case with output:
with table_name ("GROUP", seq, activity) AS (
SELECT 'A', 1, 'scan' FROM DUAL UNION ALL
SELECT 'A', 2, 'visit' FROM DUAL UNION ALL
SELECT 'A', 3, 'pay' FROM DUAL UNION ALL
SELECT 'B', 1, 'drink' FROM DUAL UNION ALL
SELECT 'B', 2, 'rest' FROM DUAL
)
SELECT--+ NO_XML_QUERY_REWRITE
t.*,
regexp_substr(
listagg(activity, ',')
within group(order by SEQ)
over(partition by "GROUP")
,'^([^,]+,){'||(row_number()over(partition by "GROUP" order by seq)-1)||'}'
)
AS hist1
,xmlcast(
xmlquery(
'string-join($X/A/B[position()<$Y]/text(),",")'
passing
xmlelement("A", xmlagg(xmlelement("B", activity)) over(partition by "GROUP")) as x
,row_number()over(partition by "GROUP" order by seq) as y
returning content
)
as varchar2(1000)
) hist2
FROM table_name t;
GROUP SEQ ACTIV HIST1 HIST2
------ ---------- ----- ------------------------------ ------------------------------
A 1 scan
A 2 visit scan, scan
A 3 pay scan,visit, scan,visit
B 1 drink
B 2 rest drink, drink

Why can't a query using 2 CTEs be linked to another table [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have something similar to the code below. I'm new to Oracle. It works fine until I try to join it to "anothertable" then states it can't see cte2.a. I can't find anything that explains why it can't see cte2.a
I need the data created by cte2 to further my query. Should i just do that inside of cte2 instead?
WITH cte 1 as (......),
cte2 as (....join tablename on tablename.x = cte1.x)
select *
from cte2
join anothertable on anothertable.a = cte2.a
Here is the actual query. It states "MAX_HEM"."PATIENT_ID": invalid identifier
WITH HGB AS(
SELECT DISTINCT
PH.PATIENT_ID,
PH.HEMATOLOGY_CD, --EX HGB
PH.PATIENT_HEMATOLOGY_ID,
PH.HEMATOLOGY_RESULT,
PH.TRANSACTION_DATE,
PH.TRANSACTION_TIME,
(TO_CHAR(PH.TRANSACTION_DATE) || ' ' || (TO_CHAR(TO_DATE(LPAD(PH.TRANSACTION_TIME,4,'0'),'HH24MI'),'HH:MI AM'))) AS HGB_DATETIME,
TO_NUMBER(TRIM(LEADING 0 FROM(TO_CHAR(ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME,'HH24MI')))) AS ISSUE_TIME,
ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME || ' ' || (TO_CHAR(ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME, 'HH:MI AM')) AS ISSUE_DTTM,
PRODUCT_INVENTORY.UNIT_NO,
CD_TRANSACTION.WTCODE
FROM
PRODUCT_INVENTORY_ACTIVITY PRODUCT_INVENTORY_ACTIVITY
JOIN CD_TRANSACTION CD_TRANSACTION ON PRODUCT_INVENTORY_ACTIVITY.TRANSACTION_CD = CD_TRANSACTION.TRANSACTION_CD
AND (CD_TRANSACTION.WTCODE='TA' OR CD_TRANSACTION.WTCODE='TX' OR CD_TRANSACTION.WTCODE='IE')
JOIN ORDER_PRODUCT_INVENTORY ORDER_PRODUCT_INVENTORY ON PRODUCT_INVENTORY_ACTIVITY.PRODINV_ID = ORDER_PRODUCT_INVENTORY.PRODINV_ID
JOIN ORDERS ORDERS ON ORDER_PRODUCT_INVENTORY.ORDER_ID = ORDERS.ORDER_ID
JOIN PATIENT_HEMATOLOGY PH ON PH.PATIENT_ID = ORDERS.PATIENT_ID
JOIN CD_ORDER_PRODUCT_INV_STAT CD_ORDER_PRODUCT_INV_STAT ON ORDER_PRODUCT_INVENTORY.ORDER_PRODUCT_INV_STAT_CD = CD_ORDER_PRODUCT_INV_STAT.ORDER_PRODUCT_INV_STAT_CD
JOIN PRODUCT_INVENTORY PRODUCT_INVENTORY ON ORDER_PRODUCT_INVENTORY.PRODINV_ID = PRODUCT_INVENTORY.PRODINV_ID
WHERE PH.HEMATOLOGY_CD = 'HGB'
AND PH.TRANSACTION_DATE <= TRUNC(ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME)
AND PH.TRANSACTION_TIME <= TO_NUMBER(TRIM(LEADING 0 FROM(TO_CHAR(ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME,'HH24MI'))))
AND CD_ORDER_PRODUCT_INV_STAT.WTCODE='TRANSFUSED'
AND (CD_TRANSACTION.WTCODE='TA' OR CD_TRANSACTION.WTCODE='TX' OR CD_TRANSACTION.WTCODE='IE')
AND ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME IS NOT NULL
AND (ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME>=TO_DATE ('01-09-2020 00:00:00', 'DD-MM-YYYY HH24:MI:SS')
AND ORDER_PRODUCT_INVENTORY.ISSUE_DATETIME<TO_DATE ('06-09-2020 00:00:00', 'DD-MM-YYYY HH24:MI:SS'))
ORDER BY PH.PATIENT_ID, UNIT_NO,TO_DATE(PH.TRANSACTION_DATE) DESC, PH.TRANSACTION_TIME DESC
),
MAX_HEM AS
(
SELECT
HGB.PATIENT_ID "PatID",
MAX(HGB.PATIENT_HEMATOLOGY_ID) "HemID",
MAX(HGB.TRANSACTION_DATE) "TransDate",
MAX(HGB.TRANSACTION_TIME) "TransTime",
MAX(HGB.HEMATOLOGY_RESULT) "HGB_Results",
MAX(HGB.UNIT_NO) "UnitNo"
FROM PATIENT_HEMATOLOGY PH1
LEFT JOIN HGB ON PH1.PATIENT_ID = HGB.PATIENT_ID
GROUP BY HGB.PATIENT_ID
ORDER BY HGB.PATIENT_ID
)
select
HGB.*,
MAXHEM.*
FROM MAX_HEM
LEFT JOIN ORDERS ORDERS ON ORDERS.PATIENT_ID = MAX_HEM.PATIENT_ID
I tried the below and its working for me. Just check is it something like this you are expecting?
WITH cte AS
( select 'a' as a from dual
union
select 'g' as a from dual
), cte2 AS
(select 'a' as b from dual
union
select 'c' as b from dual
)
SELECT *
FROM cte c inner JOIN cte2 c2 ON c.a = c2.b
Your query works:
WITH cte1 (x) as (
SELECT dummy FROM DUAL
),
cte2 (x, a) as (
SELECT tablename.x,
tablename.a
FROM cte1
join tablename
on tablename.x = cte1.x
)
select *
from cte2
join anothertable
on anothertable.a = cte2.a;
Which, for the sample data:
CREATE TABLE tablename ( x, a ) AS
SELECT dummy, ROWNUM FROM DUAL;
CREATE TABLE anothertable ( a ) AS
SELECT ROWNUM FROM DUAL;
Outputs:
X | A | A
:- | -: | -:
X | 1 | 1
db<>fiddle here

Select default in case of no value returned

I am trying to get some default value in my resultset if query does not return anything. I am trying nvl for the same but it is not returning the expected default value. To simulate, Consider following query,
select nvl(null, '10') from dual where 1=0;
I want to get 10 in case of given condition is not true and query does not return any value. However above query not returning any row.
Your query returns zero rows. NVL() isn't going to change that (*).
The correct solution is for the program which executes the query to handle NO_DATA_FOUND exception rather than fiddling the query.
However, you need a workaround so here is one using two sub-queries, one for your actual query, one to for the default.
When your_query returns an empty set you get this:
SQL> with your_qry as
2 ( select col1 from t42 where 1=0 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
10
SQL>
And when it returns a row you get this:
SQL> with your_qry as
2 ( select col1 from t42 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
12
13
SQL>
The WITH clause is optional here, it just makes it easier to write the query without duplication. This would have the same outcome:
select col1
from t42
where col0 is null
union all
select 10
from dual
where not exists (select col1
from t42
where col0 is null)
;
(*) Okay, there are solutions which use NVL() or COALESCE() with aggregations to do this. They work with single column projections in a single row as this question poses, but break down when the real query has more than one row and/or more than one column. Aggregations change the results.
So this looks alright ...
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual where 1=0 union all
3 select 'X' as col0, 13 as col1 from dual where 1=0 )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Y 10
SQL>
... but this not so much:
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual union all
3 select 'X' as col0, 13 as col1 from dual )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Z 13
SQL>
May be something like this is what you need
You could change WHERE clause (in this case WHERE COL > 1) similarly in both places.
WITH T(COL) AS(
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL
)
SELECT COL FROM T WHERE COL > 1
UNION ALL
SELECT 10 AS COL FROM DUAL WHERE NOT EXISTS( SELECT 1 FROM T WHERE COL > 1)
You can use aggregation. An aggregation query always returns one row:
select coalesce(max(null), '10')
from dual
where 1 = 0;
I prefer coalesce() to nvl() because coalesce() is the ANSI standard function. But, nvl() would work here just as well.

Match characters in oracle sql where condition

I want to match Col1 from Table a to colum1 from table B.
A B
123 123-ab
234 234-bc
3443 3443-dd
However, value in table b has concatenated data. I want to match only the characters until before special character occurs(-).
I tried this : substr(table1.a,1,3) = substr(table2.b,1,3)
But this doesn’t work as some values have 4 digits.
use join and substr
select * from table_a
inner join table_b on table_a.col_a = substr(table_b.col_b, 1, length(table_a.col_a);
Using REGEXP_SUBSTR() to match on one or more numbers from the beginning of the string up to but not including the first hyphen:
SQL> with a(col1) as (
select '123' from dual union
select '234' from dual union
select '3443' from dual
),
b(col1) as (
select '123-ab' from dual union
select '234-bc' from dual union
select '3443-dd' from dual
)
select a.col1, b.col1
from a, b
where a.col1 = regexp_substr(b.col1, '^(\d+)-', 1, 1, NULL, 1);
COL1 COL1
---- -------
123 123-ab
234 234-bc
3443 3443-dd
SQL>

How to get count by using UNION operator

i'm trying to get total count by using UNION operator but it gives wrong count.
select count(*) as companyRatings from (
select count(*) hrs from (
select distinct hrs from companyA
)
union
select count(*) financehrs from (
select distinct finance_hrs from companyB
)
union
select count(*) hrids from (
select regexp_substr(hr_id,'[^/]+',1,3) hrid from companyZ
)
union
select count(*) cities from (
select regexp_substr(city,'[^/]+',1,3) city from companyY
)
);
individual query's working fine but total count not matching.
individual results here: 12 19 3 6
present total count: 31
Actual total count:40.
so there is any alternate solution without UNION operator?
To add values you'd use +. UNION is to add data sets.
select
(select count(distinct hrs) from companyA)
+
(select count(distinct finance_hrs) from companyB)
+
(select count(regexp_substr(hr_id,'[^/]+',1,3)) from companyZ)
+
(select count(regexp_substr(city,'[^/]+',1,3)) from companyY)
as total
from dual;
But I agree with juergen d; you should not have separate tables per company in the first place.
Edit. Updated query using Sum
select sum(cnt) as companyRatings from
(
select count(*) as cnt from (select distinct hrs from companyA)
union all
select count(*) as cnt from (select distinct finance_hrs from companyB)
union all
select count(*) as cnt from (select regexp_substr(hr_id,'[^/]+',1,3) hrid from companyZ)
union all
select count(*) as cnt from (select regexp_substr(city,'[^/]+',1,3) city from companyY)
)
Previous answer:
Try this
SELECT (
SELECT count(*) hrs
FROM (
SELECT DISTINCT hrs
FROM companyA
)
)
+
(
SELECT count(*) financehrs
FROM (
SELECT DISTINCT finance_hrs
FROM companyB
)
)
+
(
SELECT count(*) hrids
FROM (
SELECT regexp_substr(hr_id, '[^/]+', 1, 3) hrid
FROM companyZ
)
)
+
(
SELECT count(*) cities
FROM (
SELECT regexp_substr(city, '[^/]+', 1, 3) city
FROM companyY
)
)
AS total_count
FROM dual

Resources