'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!
Solution 1:[1]
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
Solution 2:[2]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | astentx |
| Solution 2 | Sayan Malakshinov |
