I have a table which have schema like this
id name
1 jack
2 jack of eden
3 eden of uk
4 m of s
I want to execute a query which gives me count of words like this
count word
2 jack
2 eden
3 of
this means jack has been here 2 times, eden 2 times and of has been 3 times.
Hope you got the question, m trying too but not getting the right query or approach to it
thnx
Assuming your table is named temp (probably not - change it to the right name of your table)
I used a subquery for finding all the words in your table:
select distinct regexp_substr(t.name, '[^ ]+',1,level) word , t.name, t.id
from temp t
connect by level <= regexp_count(t.name, ' ') + 1
this query splits all the words from all records. I aliased it words.
Then I joined it with your table (in the query it's called temp) and counted the number of occurences in every record.
select words.word, count(regexp_count(tt.name, words.word))
from(
select distinct regexp_substr(t.name, '[^ ]+',1,level) word , t.name, t.id
from temp t
connect by level <= regexp_count(t.name, ' ') + 1) words, temp tt
where words.id= tt.id
group by words.word
You can also add:
having count(regexp_count(tt.name, words.word)) > 1
update: for better performance we can replace the inner subquery with the results of a pipelined function:
first, create a schema type and a table of it:
create or replace type t is object(word varchar2(100), pk number);
/
create or replace type t_tab as table of t;
/
then create the function:
create or replace function split_string(del in varchar2) return t_tab
pipelined is
word varchar2(4000);
str_t varchar2(4000) ;
v_del_i number;
iid number;
cursor c is
select * from temp; -- change to your table
begin
for r in c loop
str_t := r.name;
iid := r.id;
while str_t is not null loop
v_del_i := instr(str_t, del, 1, 1);
if v_del_i = 0 then
word := str_t;
str_t := '';
else
word := substr(str_t, 1, v_del_i - 1);
str_t := substr(str_t, v_del_i + 1);
end if;
pipe row(t(word, iid));
end loop;
end loop;
return;
end split_string;
now the query should look like:
select words.word, count(regexp_count(tt.name, words.word))
from(
select word, pk as id from table(split_string(' '))) words, temp tt
where words.id= tt.id
group by words.word
Related
I'd like get a count of every possible value from one table associated with each possible value from another table. So if my (combined) table basically look like:
Order ID Employee Product Category
-------------------------------------------
1 Alan Automobile
2 Barry Beauty
3 Charlie Clothing
4 Alan Beauty
I would like to be able to query and get a result of:
Employee Count Auto Count Beauty Count Clothing
------------------------------------------------------------
Alan 1 1 0
Barry 0 1 0
Charlie 0 0 1
I could manually query for each count, but then if I later add new product categories, it will no longer work. What I'm doing now is basically just:
SELECT employee, category, COUNT(*) FROM sales GROUP BY employee, category;
Which returns:
Employee Category Count
-------------------------------
Alan Automobile 1
Alan Beauty 1
Alan Clothing 0
etc. But with a large number of categories this can get a bit redundant. Is there any way to have it returned as a single row for each employee with a column for every category?
You can use JSON approach
SELECT employee,
json_object_agg(ProductCategory,total ORDER BY ProductCategory)
FROM (
SELECT employee, ProductCategory, count(*) AS total
FROM tbl
GROUP BY employee,ProductCategory
) s
GROUP BY employee
ORDER BY employee;
or with two step approach
CREATE FUNCTION dynamic_pivot(central_query text, headers_query text)
RETURNS refcursor AS
$$
DECLARE
left_column text;
header_column text;
value_column text;
h_value text;
headers_clause text;
query text;
j json;
r record;
curs refcursor;
i int:=1;
BEGIN
-- find the column names of the source query
EXECUTE 'select row_to_json(_r.*) from (' || central_query || ') AS _r' into j;
FOR r in SELECT * FROM json_each_text(j)
LOOP
IF (i=1) THEN left_column := r.key;
ELSEIF (i=2) THEN header_column := r.key;
ELSEIF (i=3) THEN value_column := r.key;
END IF;
i := i+1;
END LOOP;
-- build the dynamic transposition query (based on the canonical model)
FOR h_value in EXECUTE headers_query
LOOP
headers_clause := concat(headers_clause,
format(chr(10)||',min(case when %I=%L then %I::text end) as %I',
header_column,
h_value,
value_column,
h_value ));
END LOOP;
query := format('SELECT %I %s FROM (select *,row_number() over() as rn from (%s) AS _c) as _d GROUP BY %I order by min(rn)',
left_column,
headers_clause,
central_query,
left_column);
-- open the cursor so the caller can FETCH right away
OPEN curs FOR execute query;
RETURN curs;
END
$$ LANGUAGE plpgsql;
then
=> BEGIN;
-- step 1: get the cursor (we let Postgres generate the cursor's name)
=> SELECT dynamic_pivot(
'SELECT employee,ProductCategory,count(*)
FROM tbl GROUP BY employee,ProductCategory
ORDER BY 1',
'SELECT DISTINCT productCategory FROM tbl ORDER BY 1'
) AS cur
\gset
-- step 2: read the results through the cursor
=> FETCH ALL FROM :"cur";
Reference
I have a table "test_calculate" this has a column "CONN_BY" having values
column can have more than 2 number to multiply and this table may contain millions of rows , I need to get the result of the calculation from "CONN_BY" to "MVP".
I have used xmlquery for the calculation and dynamic query but these are quite slow. Is there another way which is much faster .Please suggest.
You can try the dynamic query.
Create a function which returns the calculated value and use it in your insert or select queries.
CREATE OR REPLACE FUNCTION UFN_CALCULATE (CLM_VALUE VARCHAR2)
RETURN NUMBER IS
RES_VAL NUMBER;
BEGIN
EXECUTE IMMEDIATE 'select '||CLM_VALUE||' FROM DUAL' INTO RES_VAL;
RETURN RES_VAL;
END;
You can use that function like below.
SELECT UFN_CALCULATE('.0876543 * .09876') FROM DUAL;
SELECT UFN_CALCULATE(CONN_BY) FROM YOUR_TABLE;
One option is using select ... connect by level <= regexp_count(conn_by,'[^*]+')... query for the implicit cursor within a PL/SQL code block
SQL> set serveroutput on
SQL> declare
mvp owa.nc_arr; -- numeric array to initialize each multiplication to 1 for each id value
begin
dbms_output.put_line('ID MVP');
dbms_output.put_line('--------');
for c in
(
select id,
to_number( regexp_substr(conn_by,'[^*]+',1,level) ) as nr,
level as lvl , max( level ) over ( partition by id ) as mx_lvl
from test_calculate
connect by level <= regexp_count(conn_by,'[^*]+')
and prior sys_guid() is not null
and prior conn_by = conn_by
order by id, lvl
)
loop
if c.lvl = 1 then mvp(c.id) := 1; end if;
mvp(c.id) := c.nr * mvp(c.id);
if c.lvl = c.mx_lvl then
dbms_output.put_line(c.id||' '||mvp(c.id));
end if;
end loop;
end;
/
where test_calculate is assumed to have an identity column(id)
Demo
here is my serial table.it has more than 1000 records.its with start number and end number.but between numbers not exist.
i need to add all number [start/between & end numbers] records in another temp table number by number
like below
EXIST TABLE
select concat(CARD_BULK_CODE,start_serial) startserial,concat(CARD_BULK_CODE,end_serial) endserial
from TSR_BULK_CARD_SERIALS
---------------------------
STARTSERIAL ENDSERIAL |
---------------------------
18126944 18126946 |
18141101 18141122 |
15150722 15150729 |
19069303 19069317 |
---------------------------
REQUIRED TABLE
-----------
SERIAL_NO |
-----------
18126944
18126945
18141101
18141102
....
-----------
seem its need pl-sql to implement this.
please help me to sort out this issue
I tried with below query with the help of dual.
but its very slow and not yet got results :-) running more than 1 Hour
select distinct concat(t.CARD_BULK_CODE,t.START_SERIAL)+level-1 SERIAL
from TSR_BULK_CARD_SERIALS t, dual
connect by level-1<=(concat(t.CARD_BULK_CODE,t.END_SERIAL ))-concat(t.CARD_BULK_CODE,t.START_SERIAL)
order by 1
EDIT :
Dear Alen & Dba.i tried with your ones and below error occured.
DECLARE
l_st NUMBER;
l_en NUMBER;
BEGIN
FOR rec IN (select concat(card_bulk_code, start_serial) startserial,concat(card_bulk_code, end_serial) endserial from tsr_bulk_card_serials)
LOOP
l_st := rec.startserial;
l_en := rec.endserial;
FOR rec1 IN l_st..l_en
LOOP
INSERT INTO temp(serial_no) values(rec1);
END LOOP;
END LOOP;
COMMIT;
END;
Error at line 1
ORA-01426: numeric overflow
ORA-06512: at line 9
Script Terminated on line 1.
One way to do it without resorting to plsql
WITH ranges AS
(
SELECT CONCAT(CARD_BULK_CODE, start_serial) startserial,
CONCAT(CARD_BULK_CODE, end_serial) endserial
FROM TSR_BULK_CARD_SERIALS
),
numbers(n) AS (
SELECT 0 n
FROM dual
UNION ALL
SELECT n + 1
FROM numbers
WHERE n <=
(
SELECT MAX(endserial - startserial)
FROM ranges
)
)
SELECT t.startserial + n.n SERIAL_NO
FROM ranges t JOIN numbers n
ON n.n <= t.endserial - t.startserial
ORDER BY SERIAL_NO
Here is SQLFiddle demo
Just write some PL/SQL - iterate through your table and insert rows in the temp table.
declare
l_start number;
l_end number;
begin
for r_rec in (select to_number(concat(card_bulk_code, start_serial)) startserial
, to_number(concat(card_bulk_code, end_serial)) endserial
from tsr_bulk_card_serials )
loop
l_start := r_rec.startserial;
l_end := r_rec.endserial;
for l_i in l_start..l_end loop
insert into your_temp_table;
end loop;
end loop;
end;
Try like this,
WITH t(ST, EN) AS
(
SELECT 18126944, 18126946 FROM dual
UNION
SELECT 18141101, 18141122 FROM dual
UNION
SELECT 15150722, 15150729 FROM dual
UNION
SELECT 19069303 , 19069317 FROM dual
)
SELECT DISTINCT st + LEVEL -1
FROM t
CONNECT BY LEVEL <= (SELECT en - st + 1 FROM DUAL)
ORDER BY 1;
/
Try something like this for PL/SQL,
DECLARE
l_st NUMBER;
l_en NUMBER;
BEGIN
FOR rec IN (SELECT * FROM t)
LOOP
l_st := rec.st;
l_en := rec.en;
FOR rec1 IN l_st..l_en
LOOP
INSERT INTO <your_tab>;
END LOOP;
END LOOP;
COMMIT;
END;
DECLARE
l_st NUMBER (20);
l_en NUMBER (20);
testnum NUMBER (4);
BEGIN
FOR rec IN (SELECT CONCAT (card_bulk_code, start_serial) startserial,CONCAT (card_bulk_code, end_serial) endserial FROM tsr_bulk_card_serials)
LOOP
l_st := TO_NUMBER (rec.startserial);
l_en := TO_NUMBER (rec.endserial);
testnum := l_en - l_st;
DBMS_OUTPUT.put_line (l_st);
DBMS_OUTPUT.put_line (l_en);
IF l_st < l_en
THEN
FOR rec1 IN 0 .. testnum
LOOP
l_st := l_st + 1;
INSERT INTO temp(serial_no) VALUES (l_st);
END LOOP;
END IF;
END LOOP;
COMMIT;
END;
above code helped me to sorted my issue
thanks all :-)
I have a table Product with a varchar2 datatype of column name Value, in this column values are stored as
All,10:23,0.84522,1.245,10:54:68,
All,1:22:00,0.245,45:12:00
etc.
We have to extract all the floating values like (0.84522,1.245,0.245) and ones that ends with ":00" like (1:22:00,45:12:00).
I have following query, but it doesn't seems to work; it gives me all the values except characters.
select * from Product where Values BETWEEN to_char (0) and to_char (2);
I think this would work
select *
FROM Product
WHERE
(Value LIKE '%:00' AND Value<> 'ALL') AND (Value BETWEEN to_NUMBER (0) and to_NUMBER (2))
Try this query:
select *
from (select distinct regexp_substr(t.value, '[^,]+', 1, level) phrase
from Product t
connect by regexp_substr(t.value, '[^,]+', 1, level) is not null) ph
where regexp_like(ph.phrase, '(\d+\.\d+)|(.+:00)')
The regular expression in the where clause may need some tunning
What it does is-
seperates all phrases (the inner query)
selects only those that matches your criteria
UPDATE
If you suffer from performance you can try a different approach:
create or replace type phrase_typ is object
(
phrase varchar2(100)
)
;
/
create or replace type phrase_tab as table of phrase_typ;
/
create or replace function split_string(del in varchar2) return phrase_tab
pipelined is
phrase varchar2(1000);
str_t varchar2(1000);
v_del_i number;
cursor c is with t as
select value from product;
begin
for r in c loop
str_t := r.value;
while str_t is not null loop
v_del_i := instr(str_t, del, 1, 1);
if v_del_i = 0 then
phrase := str_t;
str_t := '';
else
phrase := substr(str_t, 1, v_del_i - 1);
str_t := substr(str_t, v_del_i + 1);
end if;
if regexp_like(phrase, '(\d+\.\d+)|(.+:00)') then
pipe row(phrase_typ(phrase));
end if;
end loop;
end loop;
return;
end split_string;
/
Now your query should look like this:
select * from table(split_string(','))
This question already has answers here:
SQL Query to concatenate column values from multiple rows in Oracle
(10 answers)
Closed 8 years ago.
I have a simple query:
select * from countries
with the following results:
country_name
------------
Albania
Andorra
Antigua
.....
I would like to return the results in one row, so like this:
Albania, Andorra, Antigua, ...
Of course, I can write a PL/SQL function to do the job (I already did in Oracle 10g), but is there a nicer, preferably non-Oracle-specific solution (or may be a built-in function) for this task?
I would generally use it to avoid multiple rows in a sub-query, so if a person has more then one citizenship, I do not want her/him to be a duplicate in the list.
My question is based on the similar question on SQL server 2005.
UPDATE:
My function looks like this:
CREATE OR REPLACE FUNCTION APPEND_FIELD (sqlstr in varchar2, sep in varchar2 ) return varchar2 is
ret varchar2(4000) := '';
TYPE cur_typ IS REF CURSOR;
rec cur_typ;
field varchar2(4000);
begin
OPEN rec FOR sqlstr;
LOOP
FETCH rec INTO field;
EXIT WHEN rec%NOTFOUND;
ret := ret || field || sep;
END LOOP;
if length(ret) = 0 then
RETURN '';
else
RETURN substr(ret,1,length(ret)-length(sep));
end if;
end;
The WM_CONCAT function (if included in your database, pre Oracle 11.2) or LISTAGG (starting Oracle 11.2) should do the trick nicely. For example, this gets a comma-delimited list of the table names in your schema:
select listagg(table_name, ', ') within group (order by table_name)
from user_tables;
or
select wm_concat(table_name)
from user_tables;
More details/options
Link to documentation
Here is a simple way without stragg or creating a function.
create table countries ( country_name varchar2 (100));
insert into countries values ('Albania');
insert into countries values ('Andorra');
insert into countries values ('Antigua');
SELECT SUBSTR (SYS_CONNECT_BY_PATH (country_name , ','), 2) csv
FROM (SELECT country_name , ROW_NUMBER () OVER (ORDER BY country_name ) rn,
COUNT (*) OVER () cnt
FROM countries)
WHERE rn = cnt
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1;
CSV
--------------------------
Albania,Andorra,Antigua
1 row selected.
As others have mentioned, if you are on 11g R2 or greater, you can now use listagg which is much simpler.
select listagg(country_name,', ') within group(order by country_name) csv
from countries;
CSV
--------------------------
Albania, Andorra, Antigua
1 row selected.
For Oracle you can use LISTAGG
You can use this as well:
SELECT RTRIM (
XMLAGG (XMLELEMENT (e, country_name || ',')).EXTRACT ('//text()'),
',')
country_name
FROM countries;
you can try this query.
select listagg(country_name,',') within group (order by country_name) cnt
from countries;
The fastest way it is to use the Oracle collect function.
You can also do this:
select *
2 from (
3 select deptno,
4 case when row_number() over (partition by deptno order by ename)=1
5 then stragg(ename) over
6 (partition by deptno
7 order by ename
8 rows between unbounded preceding
9 and unbounded following)
10 end enames
11 from emp
12 )
13 where enames is not null
Visit the site ask tom and search on 'stragg' or 'string concatenation' . Lots of
examples. There is also a not-documented oracle function to achieve your needs.
I needed a similar thing and found the following solution.
select RTRIM(XMLAGG(XMLELEMENT(e,country_name || ',')).EXTRACT('//text()'),',') country_name from
In this example we are creating a function to bring a comma delineated list of distinct line level AP invoice hold reasons into one field for header level query:
FUNCTION getHoldReasonsByInvoiceId (p_InvoiceId IN NUMBER) RETURN VARCHAR2
IS
v_HoldReasons VARCHAR2 (1000);
v_Count NUMBER := 0;
CURSOR v_HoldsCusror (p2_InvoiceId IN NUMBER)
IS
SELECT DISTINCT hold_reason
FROM ap.AP_HOLDS_ALL APH
WHERE status_flag NOT IN ('R') AND invoice_id = p2_InvoiceId;
BEGIN
v_HoldReasons := ' ';
FOR rHR IN v_HoldsCusror (p_InvoiceId)
LOOP
v_Count := v_COunt + 1;
IF (v_Count = 1)
THEN
v_HoldReasons := rHR.hold_reason;
ELSE
v_HoldReasons := v_HoldReasons || ', ' || rHR.hold_reason;
END IF;
END LOOP;
RETURN v_HoldReasons;
END;
I have always had to write some PL/SQL for this or I just concatenate a ',' to the field and copy into an editor and remove the CR from the list giving me the single line.
That is,
select country_name||', ' country from countries
A little bit long winded both ways.
If you look at Ask Tom you will see loads of possible solutions but they all revert to type declarations and/or PL/SQL
Ask Tom
SELECT REPLACE(REPLACE
((SELECT TOP (100) PERCENT country_name + ', ' AS CountryName
FROM country_name
ORDER BY country_name FOR XML PATH('')),
'&<CountryName>', ''), '&<CountryName>', '') AS CountryNames
you can use this query to do the above task
DECLARE #test NVARCHAR(max)
SELECT #test = COALESCE(#test + ',', '') + field2 FROM #test SELECT field2= #test
for detail and step by step explanation visit the following link
http://oops-solution.blogspot.com/2011/11/sql-server-convert-table-column-data.html