add row value to column in oracle - sql

I have a table like this:
now;
I want map row value to column with SELECT statement.
result should be this:

You can do something like this.
Select * from (select id, person_id, f_device, report_date, originaldate)
pivot
(
count(*)
for person_id in (1, 2, 3, 4)
);
That is you need to aggregate and you will get columns that are in the select statement and for clause. This query will count rows for person_id in (1, 2, 3, 4) and group by the remaining columns internally.
Since your question is not clear if this is not the case please try to improve your question.
Update
WITH cte as ( select personid id, reportdate c_date, listagg(originaldate||' ') og FROM my_table GROUP BY personid, reportdate),
cte2 as (SELECT id, c_date, regexp_substr(og, '(.*?)( |$)', 1, 1 ) one,
regexp_substr(og, '(.*?)( |$)', 1, 2) two,
regexp_substr(og, '(.*?)( |$)', 1, 3 ) three,
regexp_substr(og, '(.*?)( |$)', 1, 4 ) four
from cte)
SELECT * FROM cte2;
If you want a distinct personid each row then reportdate needs to be aggregated
WITH cte as ( select personid id, max(reportdate ) c_date, listagg(originaldate||' ') WITHIN GROUP(ORDER BY personid ) og FROM my_table GROUP BY personid),
cte2 as (SELECT id, c_date, regexp_substr(og, '(.*?)( |$)', 1, 1 ) one,
regexp_substr(og, '(.*?)( |$)', 1, 2) two,
regexp_substr(og, '(.*?)( |$)', 1, 3 ) three,
regexp_substr(og, '(.*?)( |$)', 1, 4 ) four
from cte)
SELECT * FROM cte2;

Related

multiple INSERTs in single CTE pipeline

I have an input table that looks like this:
intput(code, persons)
'ID1', 'Pete Sampras,Ricky Martin,Albert Einstein'
'ID2', 'Georges Mikael, Diego Maradona'
I want to produce 3 output tables:
ids_table(idcode, code)
1, 'ID1'
2, 'ID2'
persons(idperson, FirstName, LastName)
1, 'Pete', 'Sampras'
2, 'Ricky', 'Martin'
3, 'Albert', 'Einstein'
4, 'Georges', 'Mikael'
5, 'Diego', 'Maradona'
code_persons(idcode, idperson)
1, 1
1, 2
1, 3
2, 4
2, 5
I am using a pipeline of CTEs to do this. I was wondering if you could do multiple inserts in the same CTE pipeline. Something like:
WITH cte AS (
SELECT code, [value] AS person
FROM input
CROSS APPLY STRING_SPLIT(persons, ',')
), cte2 AS (
SELECT code, person, CHARINDEX(' ',person) AS splitIndex
FROM cte
), cte3 AS (
SELECT code, person, LEFT(person, splitIndex) AS FirstName,
SUBSTRING(person, splitIndex, 100) AS LastName
FROM cte2
), cte4 AS (
INSERT INTO ids_table(code)
SELECT DISTINCT code
FROM cte3
), cte5 AS (
INSERT INTO persons(FirstName, LastName)
SELECT FirstName, LastName
FROM cte3
), cte6 AS (
INSERT INTO code_persons(idcode, idpersons)
SELECT it.idcode, p.idperson
FROM cte3
JOIN persons p ON p.FirstName = cte3.FirstName AND p.LastName=cte3.LastName
JOIN ids_table it ON cte3.code=it.code
)
--Some code to trigger execution
One solution I can think of is to store the output of cte3 into a temporary table.
Is there a way to avoid this ?

Concat columns from multiple tables into one row without duplicates

I need to concatenate two columns from diffrent tables, delimited with ";" into one row without duplicates.
Table 1:
Name
John;Sue
Table 2:
Name
Mary;John
Desired output
Names
John;Sue;Mary
I tried with :
select listagg(a.Name, ';') within group (order by a.Name) as Names
from Table1 a
join Table2 b on a.id = b.id;
but I get "ORA-01489: result of string concatenation is too long" error.
How to do that properly in Oracle?
You can do it with simple string functions:
WITH t1_positions (id, name, spos, epos) AS (
SELECT id,
name,
1,
INSTR(name, ';', 1)
FROM table1
UNION ALL
SELECT id,
name,
epos + 1,
INSTR(name, ';', epos + 1)
FROM t1_positions
WHERE epos > 0
),
t1_strings (id, item) AS (
SELECT id,
CASE epos
WHEN 0
THEN SUBSTR(name, spos)
ELSE SUBSTR(name, spos, epos - spos)
END
FROM t1_positions
),
t2_positions (id, name, spos, epos) AS (
SELECT id,
name,
1,
INSTR(name, ';', 1)
FROM table2
UNION ALL
SELECT id,
name,
epos + 1,
INSTR(name, ';', epos + 1)
FROM t2_positions
WHERE epos > 0
),
t2_strings (id, item) AS (
SELECT id,
CASE epos
WHEN 0
THEN SUBSTR(name, spos)
ELSE SUBSTR(name, spos, epos - spos)
END
FROM t2_positions
)
SELECT id,
LISTAGG(item, ';') WITHIN GROUP (ORDER BY item) AS name
FROM (SELECT * FROM t1_strings
UNION
SELECT * FROM t2_strings)
GROUP BY id;
Which, for the sample data:
CREATE TABLE Table1 (id, name) AS
SELECT 1, 'John;Sue' FROM DUAL;
CREATE TABLE Table2 (id, name) AS
SELECT 1, 'Mary;John' FROM DUAL;
Outputs:
ID
NAME
1
John;Mary;Sue
Note: you can do it with regular expressions; however, for a large dataset, it is likely to be of an order of magnitude slower.
Update
How to do that properly in Oracle?
Do not store delimited strings and store the data in first normal form (1NF):
CREATE TABLE table1 (id, name) AS
SELECT 1, 'John' FROM DUAL UNION ALL
SELECT 1, 'Sue' FROM DUAL;
CREATE TABLE table2 (id, name) AS
SELECT 1, 'Mary' FROM DUAL UNION ALL
SELECT 1, 'John' FROM DUAL;
Then the query is simply:
SELECT id,
LISTAGG(name, ';') WITHIN GROUP (ORDER BY name) AS name
FROM (SELECT * FROM table1
UNION
SELECT * FROM table2)
GROUP BY id;
db<>fiddle here
Presuming those are names and the result doesn't span over more than 4000 characters (which is the listagg limit) then one option is to do this (read comments within code):
SQL> with
2 -- sample data
3 table1 (id, name) as
4 (select 1, 'John;Sue' from dual union all
5 select 2, 'Little;Foot' from dual),
6 table2 (id, name) as
7 (select 1, 'Mary;John' from dual),
8 --
9 union_jack (id, name) as
10 -- union those two tables
11 (select id, name from table1
12 union
13 select id, name from table2
14 ),
15 distname as
16 -- distinct names
17 (select distinct
18 id,
19 regexp_substr(name, '[^;]+', 1, column_value) name
20 from union_jack cross join
21 table(cast(multiset(select level from dual
22 connect by level <= regexp_count(name, ';') + 1
23 ) as sys.odcinumberlist))
24 )
25 select id,
26 listagg(d.name, ';') within group (order by d.name) as names
27 from distname d
28 group by id;
ID NAMES
---------- ------------------------------
1 John;Mary;Sue
2 Foot;Little
SQL>
If it really spans over more than 4000 characters, switch to XMLAGG; lines #25 onward would be
25 select id,
26 rtrim(xmlagg (xmlelement (e, d.name || ';') order by d.name).extract
27 ('//text()'), ';') as names
28 from distname d
29 group by id;
ID NAMES
---------- ------------------------------
1 John;Mary;Sue
2 Foot;Little
SQL>
You can use a XML-style tecnique before applying LISTAGG() in order to provide writing distinct names such as
WITH t AS
(
SELECT RTRIM(DBMS_XMLGEN.CONVERT(
XMLAGG(
XMLELEMENT(e,name||';')
).EXTRACT('//text()').GETCLOBVAL() ,1),
';') AS name
FROM ( SELECT t1.name||';'||t2.name AS name
FROM table1 t1 JOIN table2 t2 ON t1.id=t2.id )
)
SELECT LISTAGG(REGEXP_SUBSTR(name,'[^;]+',1,level),';')
WITHIN GROUP (ORDER BY 0) AS "Names"
FROM t
CONNECT BY level <= REGEXP_COUNT(name,';')
Demo

filtering the expression before creating an array with array_agg

Is there any way to filter the data inside the content of an
array_agg
in one step, without writing a CTE to filter the content first?
https://cloud.google.com/bigquery/docs/reference/standard-sql/arrays
Use below construct:
ARRAY_AGG(IF(condition, NULL, column_value) IGNORE NULLS)
Below is simplified example illustrating the approach
#standardSQL
WITH `project.dataset.table` AS (
SELECT 1 id, 1 val UNION ALL
SELECT 1, 2 UNION ALL
SELECT 1, 3 UNION ALL
SELECT 1, 4 UNION ALL
SELECT 1, 5 UNION ALL
SELECT 1, 6
)
SELECT id, ARRAY_AGG(IF(val < 4, NULL, val) IGNORE NULLS)
FROM `project.dataset.table`
GROUP BY id

Finding sequence in data and grouping by it

Data in Phone_number column of my Temp_table looks like this
1234560200
1234560201
1234560202
2264540300
2264540301
2264540302
2264540303
2264540304
2264540305
2264540306
I want it to find sequence of last 4 digits and and find First and Last number of sequence of it. For eg.
There is sequence of first 3 rows as 0200, 0201, 0202, so First = 0200 and Last = 0202
Final Output of this query should be
First Last
0200 0202
0300 0306
I tried below query, but not sure about this approach.
WITH get_nxt_range AS
(
select substr(a.PHONE_NUMBER,7,4) range1,
LEAD(substr(a.PHONE_NUMBER,7,4)) OVER (ORDER BY a.PHONE_NUMBER ) nxt_range
from Temp_table a
)
SELECT range1,nxt_range FROM get_nxt_range
WHERE nxt_range = range1 +1
ORDER BY range1
One method to get sequences is to use the difference of row numbers approach. This works in your case as well:
select substr(phone_number, 1, 6),
min(substr(phone_number, 7, 4)), max(substr(phone_number, 7, 4))
from (select t.*,
(row_number() over (order by phone_number) -
row_number() over (partition by substr(phone_number, 1, 6) order by phone_number)
) as grp
from temp_table t
) t
group by substr(phone_number, 1, 6), grp;
I think something like this might work:
select
min (substr (phone_number, -4, 4)) as first,
max (substr (phone_number, -4, 4)) as last
from temp_table
group by
substr (phone_number, -4, 2)
SELECT DISTINCT
COALESCE(
first_in_sequence,
LAG( first_in_sequence ) IGNORE NULLS OVER ( ORDER BY phone_number )
) AS first_in_sequence,
COALESCE(
last_in_sequence,
LAG( last_in_sequence ) IGNORE NULLS OVER ( ORDER BY phone_number )
) AS last_in_sequence
FROM (
SELECT phone_number,
CASE phone_number
WHEN LAG( phone_number ) OVER ( ORDER BY phone_number ) + 1
THEN NULL
ELSE phone_number
END AS first_in_sequence,
CASE phone_number
WHEN LEAD( phone_number ) OVER ( ORDER BY phone_number ) - 1
THEN NULL
ELSE phone_number
END AS last_in_sequence
FROM temp_table
);
Update:
CREATE TABLE phone_numbers ( phone_number ) AS
select 1234560200 from dual union all
select 1234560201 from dual union all
select 1234560202 from dual union all
select 2264540300 from dual union all
select 2264540301 from dual union all
select 2264540302 from dual union all
select 2264540303 from dual union all
select 2264540304 from dual union all
select 2264540305 from dual union all
select 2264540306 from dual;
SELECT MIN( phone_number ) AS first_in_sequence,
MAX( phone_number ) AS last_in_sequence
FROM (
SELECT phone_number,
phone_number - ROW_NUMBER() OVER ( ORDER BY phone_number ) AS grp
FROM phone_numbers
)
GROUP BY grp;
Output:
FIRST_IN_SEQUENCE LAST_IN_SEQUENCE
----------------- ----------------
2264540300 2264540306
1234560200 1234560202
If 1234560201 1234560203 1234560204 are two instances then this should work:
with tt as (
select substr(PHONE_NUMBER,7,4) id from Temp_table
),
t as (
select
t1.id,
case when t3.id is null then 1 else 0 end start,
case when t2.id is null then 1 else 0 end "end"
from tt t1
-- no next adjacent element - we have an end of interval
left outer join tt t2 on t2.id - 1 = t1.id
-- not previous adjacent element - we have a start of interval
left outer join tt t3 on t3.id + 1 = t1.id
-- select starts and ends only
where t2.id is null or t3.id is null
)
-- find nearest end record for each start record (it may be the same record)
select t1.id, (select min(id) from t where id >= t1.id and "end" = 1)
from t t1
where t1.start = 1
I see guys already have answered for your question.
I just want to propose my variant how resolve this task:
with list_num (phone_number) as (
select 1234560200 from dual union all
select 1234560201 from dual union all
select 1234560202 from dual union all
select 2264540300 from dual union all
select 2264540301 from dual union all
select 2264540302 from dual union all
select 2264540303 from dual union all
select 2264540304 from dual union all
select 2264540305 from dual union all
select 2264540306 from dual)
select root as from_value,
max(phone_number) keep (dense_rank last order by lvl) as to_value
from
(select phone_number, level as lvl, CONNECT_BY_ROOT phone_number as root
from
(select phone_number,
decode(phone_number-lag (phone_number) over(order by phone_number),1,1,0) as start_value
from list_num) b
connect by nocycle phone_number = prior phone_number + 1
start with start_value = 0)
group by root
having count(1) > 1
If you need only last 4 numbers just substr it.
substr(root,7,4) as from_value,
substr(max(phone_number) keep (dense_rank last order by lvl),7,4) as to_value
Thanks.

Oracle SQL: Adding row to select result

Say I have a table with columns: id, group_id, type, val
Some example data from the select:
1, 1, 'budget', 100
2, 1, 'budget adjustment', 10
3, 2, 'budget', 500
4, 2, 'budget adjustment', 30
I want the result to look like
1, 1, 'budget', 100
2, 1, 'budget adjustment', 10
5, 1, 'budget total', 110
3, 2, 'budget', 500
4, 2, 'budget adjustment', 30
6, 2, 'budget total', 530
Please advise,
Thanks.
This will get the you two added lines desired, but not the values for ID and type that you want.
Oracle examples: http://docs.oracle.com/cd/B19306_01/server.102/b14223/aggreg.htm
Select id, group_id, type as myType, sum(val) as sumVal
FROM Table name
Group by Grouping sets ((id, group_id, type, val), (group_ID))
As #Serpiton suggested, it seems the functionality you're really looking for is the ability to add sub-totals to your result set, which indicates that rollup is what you need. The usage would be something like this:
SELECT id,
group_id,
coalesce(type, 'budget total') as type,
sum(val) as val
FROM your_table
GROUP BY ROLLUP (group_id), id, type
You can using union all to add more row to original select.
select group_id,type,val from tableA
union all
select group_id, 'budget total' as type,sum(val) as val from tableA group by group_id,type
To show right order and id you can using nested select
select rownum, group_id,type,val from (select group_id,type,val from tableA
union all
select group_id, 'budget total' as type,sum(val) as val from tableA group by group_id,type) order by group_id asc
with foo as
(select 1 group_id, 'budget' type, 100 val
from dual
union
select 1, 'budget adjustment', 10
from dual
union
select 2, 'budget', 500
from dual
union
select 2, 'budget adjustment', 30
from dual)
SELECT rank() over(order by type, group_id) rk,
group_id,
nvl(type, 'budget total') as type,
sum(val) as val
FROM foo
group by Grouping sets((group_id, type, val),(group_id))
its just the continuation of xQbert post to have id values!