Oracle: Need number values converted to text AND preserved - sql

If a decimal value is 0.40 I'd like to TO_CHAR(0.40) and get '0.40' back. Likewise, 50 should be to_char'd to '50'. As simple as what goes in, comes out, but as a CHAR, in all cases. So far I've tried the following:
select to_char(a, '99D90') test1,
to_char(a, '90D90') test2,
to_char(a, 'FM90D99') test3,
rtrim(to_char(a, 'FM90D99'), to_char(0, 'D')) test4
from (
select 50 a from dual
union all select 50.57 from dual
union all select 5.57 from dual
union all select 0.35 from dual
union all select 0.4 from dual
union all select 0.80 from dual
union all select .88 from dual
)
order by a;
returns:
TEST1 TEST2 TEST3 TEST4
------ ------ ------ ------
.35 0.35 0.35 0.35
.40 0.40 0.4 0.4
.80 0.80 0.8 0.8
.88 0.88 0.88 0.88
5.57 5.57 5.57 5.57
50.00 50.00 50. 50
50.57 50.57 50.57 50.57
As you can see, what may work in one case doesn't in another. Do I have to test for whole numbers, decimal numbers, then leading and trailing zeros and apply a different formatter for each? Is there just a simple cast-like construct? (tried cast too btw, similar issues).
Solved
Oracle does not store or display formatting (even on non-persisted values as shown below). A Formatter must be applied for anything other than that.

0.4, .4 and 0.4000 are all the same. It is just the way you want to display the number.
As I understand, you want to preserve the rows as string, and not just display them. Then you could handle the conditions using a CASE expression.
TRUNC(NUMBER) truncates the decimal portion and gives you a whole number. This will be the driving condition.
So, when TRUNC(NUMBER) = 0, it means it has only decimal part, and we need to append a zero in the beginning, else just convert it into text.
For example,
SQL> WITH DATA AS(
2 select 50 a from dual
3 union all select 50.57 from dual
4 union all select 5.57 from dual
5 union all select 0.35 from dual
6 union all select 0.4 from dual
7 UNION ALL SELECT 0.80 FROM dual
8 UNION ALL SELECT .88 FROM dual
9 )
10 SELECT
11 CASE
12 WHEN TRUNC(A) = 0
13 THEN ltrim(to_char(a, '0D99'), ' ')
14 ELSE TO_CHAR(a)
15 END text
16 FROM DATA;
TEXT
----------------------------------------
50
50.57
5.57
0.35
0.40
0.80
0.88
7 rows selected.
SQL>

Related

Get point geometries as rows for each vertex in SDO_GEOMETRY line

Oracle 18c:
It's possible to get SDO_GEOMETRY line vertex ordinates as rows using the sdo_util.getvertices() function:
with cte as (
select 100 as asset_id, sdo_geometry('linestring (10 20, 30 40)') shape from dual union all
select 200 as asset_id, sdo_geometry('linestring (50 60, 70 80, 90 100)') shape from dual union all
select 300 as asset_id, sdo_geometry('linestring (110 120, 130 140, 150 160, 170 180)') shape from dual)
select
cte.asset_id,
id as vertex_id,
v.x,
v.y
from
cte, sdo_util.getvertices(shape) v
ASSET_ID VERTEX_ID X Y
---------- ---------- ---------- ----------
100 1 10 20
100 2 30 40
200 1 50 60
200 2 70 80
200 3 90 100
300 1 110 120
300 2 130 140
300 3 150 160
300 4 170 180
The resulting rows have columns with ordinates as numbers.
I want to do something similar, but I want to get point geometries as rows for each vertex in the lines, instead of numbers.
The result would look like this:
ASSET_ID VERTEX_ID SHAPE
---------- ---------- ----------------
100 1 [SDO_GEOMETRY]
100 2 [SDO_GEOMETRY]
200 1 [SDO_GEOMETRY]
200 2 [SDO_GEOMETRY]
200 3 [SDO_GEOMETRY]
300 1 [SDO_GEOMETRY]
300 2 [SDO_GEOMETRY]
300 3 [SDO_GEOMETRY]
300 4 [SDO_GEOMETRY]
Idea:
There is an undocumented function called SDO_UTIL.GET_COORDINATE(geometry, point_number).
(The name of that function seems misleading: it returns a point geometry, not a coordinate.)
select
cte.asset_id,
sdo_util.get_coordinate(shape,1) as first_point
from
cte
ASSET_ID FIRST_POINT
---------- ---------------------
100 [MDSYS.SDO_GEOMETRY]
200 [MDSYS.SDO_GEOMETRY]
300 [MDSYS.SDO_GEOMETRY]
That function could be useful for getting vertices as point geometries.
Question:
Is there a way to get point geometries as rows for each vertex in the SDO_GEOMETRY lines?
If you want the output as an MDSYS.ST_POINT data type then convert the MDSYS.SDO_GEOMETRY type to an MDSYS.ST_LINESTRING type and use the ST_NumPoints() and ST_PointN(index) member functions (from the MDSYS.ST_CURVE super-type) in a LATERAL joined hierarchical sub-query:
with cte (asset_id, shape) as (
select 100, sdo_geometry('linestring (10 20, 30 40)') from dual union all
select 200, sdo_geometry('linestring (50 60, 70 80, 90 100)') from dual union all
select 300, sdo_geometry('linestring (110 120, 130 140, 150 160, 170 180)') from dual
)
select c.asset_id,
p.point
from cte c
CROSS JOIN LATERAL (
SELECT ST_LINESTRING(c.shape).ST_PointN(LEVEL) AS point
FROM DUAL
CONNECT BY LEVEL <= ST_LINESTRING(c.shape).ST_NumPoints()
) p;
db<>fiddle here
Try...
with cte as (
select 100 as asset_id, sdo_geometry('linestring (10 20, 30 40)') shape from dual union all
select 200 as asset_id, sdo_geometry('linestring (50 60, 70 80, 90 100)') shape from dual union all
select 300 as asset_id, sdo_geometry('linestring (110 120, 130 140, 150 160, 170 180)') shape from dual
)
select
c.asset_id,
id as vertex_id,
sdo_geometry(c.shape.sdo_gtype/10 * 10+1,
c.shape.sdo_srid,
sdo_point_type(v.x, v.y, v.z),
null,null) as point
from
cte c, sdo_util.getvertices(shape) v
I came up with a cross join and connect by level solution that seems to work.
Although, there might be more succinct ways of doing it.
with
data as (
select 100 as asset_id, sdo_geometry('linestring (10 20, 30 40)') shape from dual union all
select 200 as asset_id, sdo_geometry('linestring (50 60, 70 80, 90 100)') shape from dual union all
select 300 as asset_id, sdo_geometry('linestring (110 120, 130 140, 150 160, 170 180)') shape from dual),
vertices as (
select level as vertex_index from dual connect by level <= (select max(sdo_util.getnumvertices(shape)) from data))
select
d.asset_id,
v.vertex_index,
sdo_util.get_coordinate(d.shape,v.vertex_index) as sdo_geom_point, --the ordinates are stored in the SDO_GEOMETRY's SDO_POINT attribute. Example: MDSYS.SDO_POINT_TYPE(10, 20, NULL)
sdo_util.get_coordinate(d.shape,v.vertex_index).sdo_point.x as x,
sdo_util.get_coordinate(d.shape,v.vertex_index).sdo_point.y as y
from
data d
cross join
vertices v
where
v.vertex_index <= sdo_util.getnumvertices(shape)
order by
asset_id,
vertex_index
Result:
ASSET_ID VERTEX_INDEX SDO_GEOM_POINT X Y
---------- ------------ -------------------- ---------- ----------
100 1 [MDSYS.SDO_GEOMETRY] 10 20
100 2 [MDSYS.SDO_GEOMETRY] 30 40
200 1 [MDSYS.SDO_GEOMETRY] 50 60
200 2 [MDSYS.SDO_GEOMETRY] 70 80
200 3 [MDSYS.SDO_GEOMETRY] 90 100
300 1 [MDSYS.SDO_GEOMETRY] 110 120
300 2 [MDSYS.SDO_GEOMETRY] 130 140
300 3 [MDSYS.SDO_GEOMETRY] 150 160
300 4 [MDSYS.SDO_GEOMETRY] 170 180
I added the X & Y columns to the query to show what the [MDSYS.SDO_GEOMETRY] values represent. I don't actually need the X&Y columns in my query.
Edit:
I borrowed #MT0's cross join lateral technique and adapted it for SDO_GEOMETRY instead of MDSYS.ST_POINT.
It's cleaner than my original cross join / connect by level approach.
with cte (asset_id, shape) as (
select 100, sdo_geometry('linestring (10 20, 30 40)') from dual union all
select 200, sdo_geometry('linestring (50 60, 70 80, 90 100)') from dual union all
select 300, sdo_geometry('linestring (110 120, 130 140, 150 160, 170 180)') from dual
)
select c.asset_id,
vertex_index,
p.point,
sdo_util.get_coordinate(c.shape,p.vertex_index).sdo_point.x as x,
sdo_util.get_coordinate(c.shape,p.vertex_index).sdo_point.y as y
from cte c
cross join lateral (
select sdo_util.get_coordinate(c.shape,level) as point, level as vertex_index
from dual
connect by level <= sdo_util.getnumvertices(c.shape)
) p;
The result is the same:
ASSET_ID VERTEX_INDEX SDO_GEOM_POINT X Y
---------- ------------ -------------------- ---------- ----------
100 1 [MDSYS.SDO_GEOMETRY] 10 20
100 2 [MDSYS.SDO_GEOMETRY] 30 40
200 1 [MDSYS.SDO_GEOMETRY] 50 60
200 2 [MDSYS.SDO_GEOMETRY] 70 80
200 3 [MDSYS.SDO_GEOMETRY] 90 100
300 1 [MDSYS.SDO_GEOMETRY] 110 120
300 2 [MDSYS.SDO_GEOMETRY] 130 140
300 3 [MDSYS.SDO_GEOMETRY] 150 160
300 4 [MDSYS.SDO_GEOMETRY] 170 180

add a an extra row in the outcome

I have a ORACLE sql query that needs to add a header (Only one row) with the name of the columns in the outcome.
How could that be achieved?
1-1-2022 08:32:00 xxx1 166 1 04641127 8 1
1-1-2022 07:05:00 xxx1 167 1 10205792 8 1
1-1-2022 09:20:00 xxx1 176 1 10256841 8 1
1-1-2022 10:10:00 xxx1 177 1 10193856 8 1
Regards
Date dep room nr rec type count
6-4-2022 08:32:00 xxx1 166 1 04641127 8 1
6-4-2022 07:05:00 xxx1 167 1 10205792 8 1
5-4-2022 09:20:00 xxx2 176 1 10256841 8 1
5-4-2022 10:10:00 xxx2 177 1 10193856 8 1
UNION is one option; note that - in that case - both SELECT statements have to share the same number of columns and their datatypes.
Something like this:
select 'Date' col1, 'dep' col2, 'room' col3, 'nr' col4, 'rec' col5, 'type' col6, 'count' col7
from dual
union all
select to_char(date_column, 'dd-mm-yyyy hh24:mi:ss'),
dep,
to_char(room),
to_char(nr),
rec,
to_char(type),
to_char(count_column)
from some_table

How to query mismatch data from multiple table in SQL

key1
key2
val
23
215
4489
23
216
4489
86
326
5245
86
325
4489
86
323
4489
04
369
1200
04
370
1673
04
368
4489
10
402
1673
10
400
5971
10
404
1200
10
401
9189
Output should be like this
key1
key2
val
86
326
5245
04
369
1200
04
370
1673
04
368
4489
10
402
1673
10
400
5971
10
404
1200
10
401
9189
How to compare two rows having same key1 but different key2 where val is same for key2's.
I tried having and group by query but as this outcome render from views and 3 different tables, which is hard to understand for me.
Any help will be appreciated
Added :
where key1 is same for key2 having same val need to exclude
but when
key1 is same for key2 having different val need to show as outcome
To me, it looks like
SQL> WITH
2 test (key1, key2, val)
3 AS
4 -- sample data
5 (SELECT '23', 215, 4489 FROM DUAL
6 UNION ALL
7 SELECT '23', 216, 4489 FROM DUAL
8 UNION ALL
9 SELECT '86', 326, 5245 FROM DUAL
10 UNION ALL
11 SELECT '86', 325, 4489 FROM DUAL
12 UNION ALL
13 SELECT '86', 323, 4489 FROM DUAL
14 UNION ALL
15 SELECT '04', 369, 1200 FROM DUAL
16 UNION ALL
17 SELECT '04', 370, 1673 FROM DUAL
18 UNION ALL
19 SELECT '04', 368, 4489 FROM DUAL
20 UNION ALL
21 SELECT '10', 402, 1673 FROM DUAL
22 UNION ALL
23 SELECT '10', 400, 5971 FROM DUAL
24 UNION ALL
25 SELECT '10', 404, 1200 FROM DUAL
26 UNION ALL
27 SELECT '10', 401, 9189 FROM DUAL),
28 temp
29 AS
30 -- count > 1 means that there are values that match
31 ( SELECT key1, val, COUNT (*) cnt
32 FROM test
33 GROUP BY key1, val)
34 SELECT a.key1, a.key2, a.val
35 FROM test a
36 WHERE (a.key1, a.val) IN (SELECT b.key1, b.val
37 FROM temp b
38 WHERE b.cnt = 1) --> no match
39 ORDER BY a.key1, a.key2;
KEY1 KEY2 VAL
-------- ---------- ----------
04 368 4489
04 369 1200
04 370 1673
10 400 5971
10 401 9189
10 402 1673
10 404 1200
86 326 5245
8 rows selected.
SQL>
If sample data come from a query you already wrote, then you'd use it as a CTE:
with your_query as
(select ... --> put your query in here
from ...
where ...
),
-- code I wrote goes here, starting from line #28
temp as
...
After you posted your query, it would be something like this; note that I don't know which columns you want to compare because there are no longer KEY1, KEY2 and VAL columns; you'll have to fix it yourself.
WITH
your_query
AS
( SELECT COUNT (*) cnt,
t2.ORIGINATOR_ID,
t3.name,
t4.MULTI_TRANCHE_FLAG,
v1.SOURCE_OF_DEAL,
ta1.PRICING_DATE
FROM vw_origination_deal_advice ta1
LEFT JOIN vw_orig_deal_advice_summary v1
ON v1.deal_id = ta1.orig_deal_advice_id
LEFT JOIN tbl_issue_tranche t4 ON t4.id = ta1.ISSUE_TRANCHE_ID
LEFT JOIN tbl_issue_originator t2
ON ta1.issue_tranche_id = t2.ISSUE_TRANCHE_ID
LEFT JOIN tbl_staff t3 ON t2.originator_id = t3.staff_id
WHERE ta1.ISSUE_TRANCHE_ID IN
( SELECT ta2.ISSUE_TRANCHE_ID
FROM vw_origination_deal_advice ta2
GROUP BY ta2.ISSUE_TRANCHE_ID)
AND ta1.PRICING_DATE LIKE '%-21%'
AND t4.MULTI_TRANCHE_FLAG = 1
GROUP BY ta1.orig_deal_advice_id,
ta1.ISSUE_TRANCHE_ID,
t4.ACTIVE,
t2.ORIGINATOR_ID,
t3.name,
t4.MULTI_TRANCHE_FLAG,
v1.SOURCE_OF_DEAL,
ta1.PRICING_DATE),
temp
AS
-- count > 1 means that there are values that match.
-- As KEY1, KEY2, VAL don't exist in your query, you'll have to use appropriate
-- names from in the rest of the query
( SELECT key1, val, COUNT (*) cnt
FROM test
GROUP BY key1, val)
SELECT a.key1, a.key2, a.val
FROM test a
WHERE (a.key1, a.val) IN (SELECT b.key1, b.val
FROM temp b
WHERE b.cnt = 1) --> no match
ORDER BY a.key1, a.key2;

Modify data in a specific format

I want to represent data in a specific format. Currently, the data looks like below-
product_id order_id product_type day1_sale day2_sale day3_sale day4_sale
123 456 A null 0.2 0.3 null
123 456 B null null 0.4 null
111 222 A null null null null
333 444 B 0.7 0.1 0.2 0.6
I want to represent it in the below format-
product_id order_id product_type sale_day %sales_on_day
123 456 A day2 0.2
123 456 A day3 0.3
123 456 B day3 0.4
111 222 A null null
333 444 B day1 0.7
333 444 B day2 0.1
333 444 B day3 0.2
333 444 B day4 0.6
Is is there a way to get the data in this format?
Below is for BigQuery Standard SQL
#standardSQL
SELECT product_id, order_id, product_type, x.*
FROM `project.dataset.table`,
UNNEST([STRUCT('day1' AS sale_day, day1_sale AS sales_on_day), ('day2', day2_sale), ('day3', day3_sale), ('day4', day4_sale)]) x
WHERE NOT sales_on_day IS NULL
if to apply to sample data from your question - result is
Row product_id order_id product_type sale_day sales_on_day
1 123 456 A day2 0.2
2 123 456 A day3 0.3
3 123 456 B day3 0.4
4 333 444 B day1 0.7
5 333 444 B day2 0.1
6 333 444 B day3 0.2
7 333 444 B day4 0.6
You want to unpivot and filter. Here is a BigQuery'ish way to do this:
with t as (
select 123 as product_id, 456 as order_id, 'A' as product_type, null as day1_sale, 0.2 as day2_sale, 0.3 as day3_sale, null as day4_sale UNION ALL
select 123, 456, 'B', null, null, 0.4, null UNION ALL
select 111, 222, 'A', null, null, null, null UNION ALL
select 333, 444, 'B', 0.7, 0.1, 0.2, 0.6
)
select t.product_id, t.order_id, t.product_type, ds.*
from t cross join
unnest(array[struct('1' as day, day1_sale as day_sale),
('2', day2_sale),
('3', day3_sale),
('4', day4_sale)
]
) ds
where day_sale is not null;

Round to .5 or 1.0 in Oracle SQL

I'm looking to round values like
2.39 -> 2.40
4.66 -> 4.70
2.11 -> 2.15
2.10 -> 2.10
2.50 -> 2.50
5.89 -> 5.90
How can I manage this in SQL?
Thanks
problem solved :
SELECT FLOOR ( (1.11 + 0.04) * 20) / 20 FROM DUAL;
test:
WITH t (my_number)
AS (SELECT 3.1001 FROM DUAL
UNION ALL
SELECT 2.39 FROM DUAL
UNION ALL
SELECT 4.66 FROM DUAL
UNION ALL
SELECT 2.11 FROM DUAL
UNION ALL
SELECT 2.10 FROM DUAL
UNION ALL
SELECT 2.50 FROM DUAL
UNION ALL
SELECT 5.89 FROM DUAL)
SELECT my_number, FLOOR ( (my_number + 0.04) * 20) / 20 round_on_number
FROM t;
3.1001 3.1
2.39 2.4
4.66 4.7
2.11 2.15
2.1 2.1
2.5 2.5
5.89 5.9