get one value from multiple values in Oracle SQL - sql

I tried the below query which returns two values but i need to get one value separated with ','. For example the query should return result :
TRPT,PRLD
Below is the query i tried:
SELECT
LISTAGG(T_NAME, ',') WITHIN GROUP (ORDER BY T_NAME) "ListValues"
FROM TST_TBL where T_DATE > sysdate-1
GROUP BY T_NAME
It returns:
ListValues
TRPT
PRLD

Remove the GROUP BY clause, it causes your "problems".
SQL> with tst_tbl (t_name, t_date) as
2 (select 'TRPT', sysdate from dual union all
3 select 'PRLD', sysdate from dual
4 )
5 SELECT
6 LISTAGG(T_NAME, ',') WITHIN GROUP (ORDER BY T_NAME) "ListValues"
7 FROM TST_TBL where T_DATE > sysdate-1
8 /
ListValues
--------------------
PRLD,TRPT
SQL>

Related

Using query results as In clause parameter

I know I've seen this before, but can't come up with the search terms to find it.
I have a CTE returning a comma separated list from the below table:
Table
Create table Table
(
ID Number
, Name varchar2(100)
);
insert all
into Table (ID, Name) values (1, 'Alex')
into Table (ID, Name) values (2, 'Amy')
into Table (ID, Name) values (3, 'Jim')
select * from dual;
ID
Name
1
Alex
2
Amy
3
Jim
select substr(
listagg(Table.ID || ',') within group (order by null)
, 1
, length(listagg(Table.ID || ',') within group (order by null)) - 1
) IDs
from Table
where Name like 'A%'
Which gives me the results: 1,2
I'm trying to use this result in a query's in clause:
with CTE as
(
select substr(
listagg(tbl.ID || ',') within group (order by null)
, 1
, length(listagg(tbl.ID || ',') within group (order by null)) - 1
) IDs
from Table
where Name like 'A%'
)
select *
from Table
where cast(ID as varhcar2(1000)) in (select IDs from CTE) --Use results here
--believe the cast is required to compare, otherwise get a ORA-01722: invalid number
Which I want to return:
ID
Name
1
Alex
2
Amy
How can I use the CTE's resulting IDs string as the parameter of my in clause?
I'm afraid I don't understand your "problem". CTE is really strange; SUBSTR of something? Why? LISTAGG returns the same result anyway. Then you want to ... what? split that result so that you could use it in another query? As if you want to make it as complex as possible (and beyond) to solve something "simple". Therefore: what real problem are you trying to solve?
Anyway, here you go: you'll have to split aggregated string into rows if you want to use it in IN clause:
SQL> with CTE as
2 (select listagg(ID || ',') within group (order by null) IDs
3 from Test
4 where Name like 'A%'
5 )
6 select *
7 from Test
8 where id in (select regexp_substr(IDs, '[^,]+', 1, level)
9 from CTE
10 connect by level <= regexp_count(IDS, ',') + 1
11 );
ID NAME
---------- ----------
1 Alex
2 Amy
SQL>
The same result is returned by a simple
SQL> select *
2 from Test
3 where Name like 'A%';
ID NAME
---------- ----------
1 Alex
2 Amy
SQL>
That's why I asked: what problem are you trying to solve?
[EDIT] As of trailing comma: there's none, at least not any Oracle version I used (11g, 12c, 18cXE, 21cXE):
SQL> select listagg(id, ',') within group (order by null) result from test;
RESULT
------------------------------
1,2,3
SQL> select listagg(name, ',') within group (order by null) result from test;
RESULT
------------------------------
Alex,Amy,Jim
SQL>

Oracle Select max where a certain key is matched

i'm working with oracle, plSql, i need to query a table and select the max id where a key is matched, now i have this query
select t.* from (
select distinct (TO_CHAR(I.DATE, 'YYMMDD') || I.AUTH_CODE || I.AMOUNT || I.CARD_NUMBER) as kies, I.SID as ids
from transactions I) t group by kies, ids order by ids desc;
It's displaying this data
If i remove the ID from the query, it displays the distinct keys (in the query i use the alias KIES because keys was in blue, so i thought it might be a reserved word)
How can i display the max id (last one inserted) for every different key without displaying all the data like in the first image??
greetings.
Do you just want aggregation?
select thekey, max(sid)
from (select t.*,
(TO_CHAR(t.DATE, 'YYMMDD') || t.AUTH_CODE || t.AMOUNT || t.CARD_NUMBER) as thekey,
t.SID
from transactions t
) t
group by thekey
order by max(ids) desc;
Since you haven't provided data in text format, its difficult to type such long numbers and recreated the data.
However I think you can simply use the MAX analytical function to achieve your results.
with data as (
select 1111 keys,1 id from dual
union
select 2222, 1 from dual
union
select 1111, 2 from dual
union
select 2222,3 from dual
union
select 9999, 1 from dual
union
select 1111, 5 from dual
)
select distinct keys, max(id) over( partition by (keys)) from data
This query returns -
KEYS MAX(ID)OVER(PARTITIONBY(KEYS))
1111 5
9999 1
2222 3

Oracle Regex - Remove duplicates including chinese

I'm trying to remove the duplicates in the results of a query involving listagg.
I'm using this syntax:
REGEXP_REPLACE (LISTAGG (PR.NAME, ',' ) WITHIN GROUP (ORDER BY 1),
'([^,]+)(,\1)+',
'\1') AS PRODUCERS
However, occurrences including chinese characters are not removed:
Any idea ?
Your regular expression does not work. If the LISTAGG output is A,A,AA then the regular expression ([^,]+)(,\1)+ does not check that it has matched a complete element of your list and will match A,A,A which is 2½ elements of the list and will give the output AA instead of the expected A,AA. Worse, if you have the string BA,BABAB,BABD then the regular expression will replace BA,BA with BA and then replace BAB,BAB with BAB and you end up with the string BABABD which does not match any of the elements of the original list.
An example demonstrating this is:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE names ( id, name ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 3, 'B' FROM DUAL UNION ALL
SELECT 4, 'C' FROM DUAL UNION ALL
SELECT 5, 'A' FROM DUAL UNION ALL
SELECT 6, 'AA' FROM DUAL UNION ALL
SELECT 7, 'A' FROM DUAL UNION ALL
SELECT 8, 'BA' FROM DUAL UNION ALL
SELECT 9, 'A' FROM DUAL
/
Query 1:
SELECT REGEXP_REPLACE (
LISTAGG (NAME, ',' ) WITHIN GROUP (ORDER BY 1),
'([^,]+)(,\1)+',
'\1'
) AS constant_sort
FROM names
Results:
| CONSTANT_SORT |
|---------------|
| AA,BA,C |
If you want to get the distinct elements then you can use DISTINCT (as per Littlefoot's answer) or you can COLLECT the values into a user-defined collection and then use the SET function to remove duplicates. You can then pass this de-duplicated collection to a table collection expression and use LISTAGG to get your output:
Oracle 11g R2 Schema Setup:
CREATE TYPE StringList IS TABLE OF VARCHAR2(4000)
/
Query 2:
SELECT (
SELECT LISTAGG( column_value, ',' )
WITHIN GROUP ( ORDER BY ROWNUM )
FROM TABLE( n.unique_names )
) AS agg_names
FROM (
SELECT SET( CAST( COLLECT( name ORDER BY NAME ) AS StringList ) )
AS unique_names
FROM names
) n
Results:
| AGG_NAMES |
|-------------|
| A,AA,B,BA,C |
Regarding your comment:
in the context of a bigger query involving a lot of join and given my begginers skills I would have no idea how to implement this model
For example, if your query was:
SELECT REGEXP_REPLACE(
LISTAGG (PR.NAME, ',' ) WITHIN GROUP (ORDER BY 1),
'([^,]+)(,\1)+',
'\1'
) AS PRODUCERS,
other_column1,
other_column2
FROM table1 pr
INNER JOIN table2 t2
ON (pr.some_condition = t2.some_condition )
WHERE t2.some_other_condition = 'TRUE'
GROUP BY other_column1, other_column2
Then you can change it to:
SELECT (
SELECT LISTAGG( COLUMN_VALUE, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
FROM TABLE( t.PRODUCERS )
) AS producers,
other_column1,
other_column2
FROM (
SELECT SET( CAST( COLLECT( PR.name ORDER BY PR.NAME ) AS StringList ) )
AS PRODUCERS,
other_column1,
other_column2
FROM table1 pr
INNER JOIN table2 t2
ON (pr.some_condition = t2.some_condition )
WHERE t2.some_other_condition = 'TRUE'
GROUP BY other_column1, other_column2
) t
(I can't see images; company policy).
Why wouldn't you remove duplicates before applying LISTAGG? Something like
select listagg(x.distinct_name, ',') within group (order by 1) producers
from (select DISTINCT name distinct_name
from some_table
) x
Another way to remove duplicates is to use window functions and case:
select listagg(case when seqnum = 1 then name end, ',') within group (order by 1) as producers
from (select . . .,
row_number() over (partition by name order by name) as seqnum
from . . .
) t
This does require modifications to the rest of the query, but you should still be able to do the rest of the aggregations and computations.

Query to return dynamic number of rows using SQL in SQL Server 2012

I have a unique requirement to return number of result rows in multiples of 10. Example, if actual data rows are 3, I must add another 7 blank rows to make it 10. If actual data rows are 16, I must add another 4 blank rows to make it 20, and so on.
Without using a procedure, is it possible to achieve this using SELECT statement?
The blank rows can simply contain NULL values or spaces or zeroes.
You can assume any simple query for data rows; the objective is to understand how to return rows dynamically in multiples of 10.
Example:
Select EmpName FROM Employees
If there are 3 employees, I should still return 10 rows, with the balance 7 rows containing either NULL value or blanks.
I am using SQL Server 2012.
This is very raw idea how it can be achieved:
WITH data(r) AS (
SELECT 1 r FROM dual
UNION ALL
SELECT r+1 r FROM data WHERE r < 10
)
SELECT sd.*
FROM data d
left join some_data sd on d.r = sd.id
This is dual table structure:
create table dual (dummy varchar(1));
insert into dual values ('x');
Fiddle: http://sqlfiddle.com/#!6/5ffcc/4
One of the possible options is this:
WITH data(r) AS (
SELECT 1 r FROM dual
UNION ALL
SELECT r+1 r FROM data WHERE r < 10
)
SELECT sd.*
FROM
(select r, row_number() over (order by r) rn from data) d
left join (
select id, name, row_number() over (order by id) rn from some_data sd
) sd
on d.rn = sd.rn
The obvious disadvantages of this colutions:
'r' value generation rule most probably is not as simple in your
case.
Number of rows must be known before query execution.
But maybe it will help you to find better solution.
Here's another, fairly easy, way to handle it...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
EmpID INT NOT NULL,
EmpName VARCHAR(20) NOT NULL
);
INSERT #TestData(EmpID, EmpName) VALUES
(47, 'Bob'),(33, 'Mary'), (88, 'Sue');
-- data as it exists...
SELECT
td.EmpID,
td.EmpName
FROM
#TestData td;
-- the desired output...
WITH
cte_AddRN AS (
SELECT
td.EmpID,
td.EmpName,
RN = ROW_NUMBER() OVER (ORDER BY td.EmpName)
FROM
#TestData td
),
cte_TenRows AS (
SELECT n.RN FROM ( VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10) ) n (RN)
)
SELECT
ar.EmpID,
ar.EmpName
FROM
cte_TenRows tr
LEFT JOIN cte_AddRN ar
ON tr.RN = ar.RN
ORDER BY
tr.RN;
Results...
-- data as it exists...
EmpID EmpName
----------- --------------------
47 Bob
33 Mary
88 Sue
-- the desired output...
EmpID EmpName
----------- --------------------
47 Bob
33 Mary
88 Sue
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
Based on the above 2 answers, here is what I did:
WITH DATA AS
(SELECT EmpName FROM Employees),
DataSummary AS
(SELECT COUNT(*) AS NumDataRows FROM DATA),
ReqdDataRows AS
(SELECT CEILING(NumDataRows/10.0)*10 AS NumRowsReqd FROM DataSummary),
FillerRows AS
(
SELECT 1 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 2 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 3 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 4 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 5 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 6 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 7 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 8 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 9 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 10 AS SLNO, '00000' AS FillerCol
)
SELECT * FROM DATA
--UNION ALL
--SELECT CONVERT(VARCHAR(10), NumDataRows) FROM DataSummary
--UNION ALL
--SELECT CONVERT(VARCHAR(10), NumRowsReqd) FROM ReqdDataRows
UNION ALL
SELECT FillerCol FROM FillerRows
WHERE (SELECT NumDataRows FROM DataSummary) + SLNO <= (SELECT NumRowsReqd FROM ReqdDataRows)
This gives me the output what I want. This avoids use of ROW_NUMBER and ORDERing. The table FillerRows can be further simplified using SELECT * FROM (VALUES...), and the 2nd and 3rd table DataSummary and ReqdDataRows can be merged into a single SELECT statement.
This approach is a step by step approach and easy to understand and debug, like:
Get the actual data rows
Get count of the data rows
Calculate required no. data rows
UNION the actual data rows with filler rows
Any suggestions on further simplifying this are welcome.

How to do sorting and then numbering on an Oracle database

As an example I have a database with the following information
Name Number
Boris
Trevor
Arthur
bessie
big Dave
BOB
I want to be able to sort that data in the below order and then add a number to the number column in that specific order
Name Number
Arthur 1
BOB 2
Boris 3
big Dave 4
bessie 5
Trevor 6
I can select using the order I have specified using
select DB.TABLE.NAME , case
when row_number() over(partition by lower(DB.TABLE.NAME )
order by DB.TABLE.NAME ) = 1
then 1
else 0
end as result
from DB.TABLE;
but I then have no idea how to apply the numbers to the numbers column.
If I try a different method of sorting, I can use a sequence to apply the numbers but the order is not what I want. It seems to be the row_number() function that is causing me problems.
Any help would be appreciated.
I think what you're after is something like:
with sample_data as (select 'Boris' name from dual union all
select 'Trevor' name from dual union all
select 'BO Derek' name from dual union all
select 'Arthur' name from dual union all
select 'big dave' name from dual union all
select 'big Dave' name from dual union all
select 'BOB' name from dual union all
select 'BORAT' name from dual union all
select 'Brian' name from dual union all
select 'Big Bad Dom' name from dual)
-- end of creating a subquery "sample_data" to mimic a table with data in it.
-- see SQL below:
select name,
row_number() over (order by upper(substr(name, 1, 1)),
name) row_num
from sample_data
order by upper(substr(name, 1, 1)),
name;
NAME ROW_NUM
----------- ----------
Arthur 1
BO Derek 2
BOB 3
BORAT 4
Big Bad Dom 5
Boris 6
Brian 7
big Dave 8
big dave 9
Trevor 10
To update a table, you'd do something like (assuming name is a unique column):
merge into some_table tgt
using (select name,
row_number() over (order by upper(substr(name, 1, 1)),
name) row_num
from some_table) src
on (tgt.name = src.name)
when matched then
update set tgt.number = src.row_num;
Use a MERGE statement:
merge into the_table t
using (
select rowid as rid,
row_number() over(order by lower(name)) as result
from the_table
) nr on (nr.rid = t.rowid)
when matched then update
set "number" = nr.result;
I am not sure what the CASE should do. It only returns 1 or 0 but the expected result shows you want numbers from 1 to 6, so I removed the CASE
If you have a proper primary key on the table, it's better to use that instead of rowid
Try this.
select DB.TABLE.NAME ,
row_number() over(ORDER by DB.TABLE.NAME ) as Number
from DB.TABLE
order by DB.TABLE.NAME;
Maybe you are looking to update db.table in that case:
update DB.TABLE
set number = (select row_number() over(ORDER by DB.TABLE.NAME ) as Number
from DB.TABLE t1 where t1.name = DB.TABLE.NAME );
Thanks all for your suggestions.
I went with this hacky approach to the answer by #a_horse_with_no_name
CREATE SEQUENCE NEWSEQ
START WITH 1
MAXVALUE 999999999999999999999999999
MINVALUE 1;
merge into DB.TABLE t
using (
select rowid as rid, DB.TABLE.NAME, case
when row_number() over(partition by lower(DB.TABLE.NAME )
order by DB.TABLE.NAME ) = 1
then 1
else 0
end as result
from DB.TABLE
) nr on (nr.rid = t.rowid)
when matched then update
set NUMBER = NEWSEQ.NEXTVAL;
drop sequence NEWSEQ;
It may not be the most efficient way to do it, but it works