What I want is to count strings ocurrences in this way:
ID | NAME | ITEMS
1 | JFK | 100/100/100/200/300/300
I want to turn it into
ID | NAME | ITEMS
1 | JFK | 100(3),200(1),300(2)
A function will be fine, but if you can do within a SELECT will be awesome.
I am using Oracle 11, but if you can provide generic may help more people.
Thanks :)
This is a bit long-winded, but you can tokenize your strings into separate rows:
select id, name, regexp_substr(items, '(.*?)(/|$)', 1, level, null, 1) as item
from your_table
connect by level < regexp_count(items, '(.*?)(/|$)')
and prior id = id
and prior dbms_random.value is not null;
and then group by item to get the counts:
select id, name, item, count(*) as item_count
from (
select id, name, regexp_substr(items, '(.*?)(/|$)', 1, level, null, 1) as item
from your_table
connect by level < regexp_count(items, '(.*?)(/|$)')
and prior id = id
and prior dbms_random.value is not null
)
group by id, name, item;
and then group again by just ID and name to aggregate back to a single string:
select id, name,
listagg(item || '(' || item_count || ')', '/') within group (order by item) as items
from (
select id, name, item, count(*) as item_count
from (
select id, name, regexp_substr(items, '(.*?)(/|$)', 1, level, null, 1) as item
from your_table
connect by level < regexp_count(items, '(.*?)(/|$)')
and prior id = id
and prior dbms_random.value is not null
)
group by id, name, item
)
group by id, name
order by id;
With your example and another one in a different order provided via a CTE:
with your_table (id, name, items) as (
select 1, 'JFK', '100/100/100/200/300/300' from dual
union all select 2, 'LBJ', '100/300/100/200/100' from dual
)
select ...
that query gets
ID NAM ITEMS
---------- --- --------------------------------------------------
1 JFK 100(3)/200(1)/300(2)
2 LBJ 100(3)/200(1)/300(1)
Related
I have some data that looks like this:
UserID Category
------ --------
1 a
1 b
2 c
3 b
3 a
3 c
A I'd like to binary-encode this grouped by UserID: three different values exist in Category, so a binary encoding would be something like:
UserID encoding
------ --------
1 "1, 1, 0"
2 "0, 0, 1"
3 "1, 1, 1"
i.e., all three values are present for UserID = 3, so the corresponding vector is "1, 1, 1".
Is there a way to do this without doing a bunch of CASE WHEN statements? There may be dozens of possible values in Category
Cross join the distinct users to distinct categories and left join to the table.
Then use GROUP_CONCAT() window function which supports an ORDER BY clause, to collect the 0s and 1s:
WITH
users AS (SELECT DISTINCT UserID FROM tablename),
categories AS (
SELECT DISTINCT Category, DENSE_RANK() OVER (ORDER BY Category) rn
FROM tablename
),
cte AS (
SELECT u.UserID, c.rn,
'"' || GROUP_CONCAT(t.UserID IS NOT NULL)
OVER (PARTITION BY u.UserID ORDER BY c.rn) || '"' encoding
FROM users u CROSS JOIN categories c
LEFT JOIN tablename t
ON t.UserID = u.UserID AND t.Category = c.Category
)
SELECT DISTINCT userID,
FIRST_VALUE(encoding) OVER (PARTITION BY UserID ORDER BY rn DESC) encoding
FROM cte
ORDER BY userID
This will work for any number of categories.
See the demo.
Results:
UserID
encoding
1
"1,1,0"
2
"0,0,1"
3
"1,1,1"
First create an encoding table to explicit establish order of categories in the bitmap:
create table e (Category int, Encoding int);
insert into e values ('a', 1), ('b', 2), ('c', 4);
First generate a list of users u (cross) joined with the encoding table e to get a fully populated (UserId, Category, Encoding) table. Then left join the fully populated table with the user supplied data t. The right hand side t can now be used to drive if we need to set a bit or not:
select
u.UserId,
'"' ||
group_concat(case when t.UserId is null then 0 else 1 end, ', ')
|| '"' 'encoding'
from
(select distinct UserID from t) u
join e
left natural join t
group by 1
order by e.Encoding
and it gives the expected result:
1|"1, 1, 0"
2|"0, 0, 1"
3|"1, 1, 1"
I have a "tt_Results" table which contains exactly three row / ID like:
ID | ResultType
---------------
1 | first
1 | second
1 | third
My query looks like:
select t.resultType
from tt_Results
where ID = 1;
Normally, this query should return all the three row. My problem is if one or more row not exist I must union all the three "type".
select res.* from
(
select resultType, '1' as exists
from tt_Results
where ID = 1
union all
select 'first' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'first' and ID = 1)
union all
select 'second' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'second' and ID = 1)
union all
select 'third' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'third' and ID = 1)
) res
The final query result looks good but it is very slow. Anyone have a better PL/SQL solution for it? Thanks for the answers!
Left join to your table from a table of all types, using a case to calculate exists based on whether a join was made or not:
select
type,
case when resultType is null then '0' else '1' end as exists
from (select 'first' type from dual union
select 'second' from dual union
select 'third' from dual) t
left join tt_Results on resultType = type
and ID = 1
Note that the condition ID = 1 is part of the join condition, not in a where clause.
I recommend creating a 3-row table for the types, then the query would become simply:
select
type,
case when resultType is null then '0' else '1' end as exists
from types
left join tt_Results on resultType = type
and ID = 1
You can do this by creating a subquery to hold the result types you're interested in, and then using a partitioned outer join, like so:
with tt_results as (select 1 id, 'first' resulttype from dual union all
select 1 id, 'second' resulttype from dual union all
select 1 id, 'third' resulttype from dual union all
select 2 id, 'second' resulttype from dual union all
select 2 id, 'third' resulttype from dual union all
select 3 id, 'first' resulttype from dual),
dummy as (select 1 position, 'first' resulttype from dual union all
select 2 position, 'second' resulttype from dual union all
select 3 position, 'third' resulttype from dual)
select res.id,
d.resulttype,
case when res.resulttype is not null then 1 else 0 end res_exists
from dummy d
left outer join tt_results res partition by (res.id) on d.resulttype = res.resulttype
order by res.id,
d.position;
ID RESULTTYPE RES_EXISTS
---------- ---------- ----------
1 first 1
1 second 1
1 third 1
2 first 0
2 second 1
2 third 1
3 first 1
3 second 0
3 third 0
Adrian Billington has produced an excellent article on partititioned outer joins if you want to learn more about them.
If your tt_results table could contain more resulttypes than you're interested in, you might want/need to add a predicate to only grab the rows from the dummy subquery, otherwise you could get ids where all 3 resulttypes don't exist (although that may be what you want to see).
ETA: This will work if you need to select multiple ids in one go.
I would like to know if, and if yes, how I could accomplsh the following:
Lets say I have two tables:
Table A has two Columns: id, name
Table B columns: owner, argument
Now I am trying to find in table A all rows with specific name (animal) and use their ids to find it's argument value in table b. Those argument values are different ids in table a. So as a result I would like to get two columns. first has the id of the items who has the specific name (animal) I am looking for and second column has the name of the item which has the id that is argument of the initial ids.
table a (example)
id || name
1 || animal
2 || animal
3 || animal
4 || animal
15 || cat
16 || dog
17 || horse
18 || bird
...
table b (example)
owner || argument
1 || 15
2 || 16
3 || 17
4 || 18
...
result (example)
id || name
1 || cat
2 || dog
3 || horse
4 || bird
Thanks in advance for any hints / help.
Andreas
You need a double join from tablea to tableb and again doublea:
select
a.name ownwename,
t.name name
from tablea a
inner join tableb b
on b.owner = a.id
inner join tablea t
on t.id = b.argument
where a.name = 'animal'
See the demo
I believe the following will do what you want
SELECT owner, name FROM tableb JOIN tablea ON argument = id;
However, as using a subquery you could use :-
SELECT owner, (SELECT name FROM tablea WHERE argument = id) AS name FROM tableb;
Working Example :-
DROP TABLE If EXISTS tablea;
CREATE TABLE IF NOT EXISTS tablea (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO tablea (name) VALUES ('animal'),('animal'),('animal'),('animal'),('cat'),('dog'),('horse'),('bird'),
('animal'),('cat'),('dog'),('horse'),('bird'),('animal'),
('cat'),('dog'),('horse'),('bird') -- id's 15-18 inclusive
;
DROP TABLE IF EXISTS tableb;
CREATE TABLE IF NOT EXISTS tableb (owner INTEGER PRIMARY KEY, argument INTEGER);
INSERT INTO tableb (argument) VALUES(15),(16),(17),(18);
SELECT owner, name FROM tableb JOIN tablea ON argument = id;
SELECT owner, (SELECT name FROM tablea WHERE argument = id) AS name FROM tableb;
Results :-
and the second
I have two tables Procedures and ProcedureTypes.
Procedures has a column Type which is a varchar with the values (1, 2), (3, 4), (4, 5) etc...
ProcedureType has a primary key 'ID' 1 to 9.
ID Description
1 Drug
2 Other-Drug
etc...
ID is an integer value and Type is varchar value.
Now I need to join these two tables to show the values
ID in the Procedures table
ProcedureType in the Procedures table
Description in the ProceduresType table with the value separated by a "-".
For example if he value in Type is (1,2) the new table after join should show values in the description like (Drug-Other Drug)
I have used this query bot to no avail
SELECT * FROM dbo.[Split]((select RequestType from GPsProcedures), ',')
Can anyone tell me how to do it and why the above query is not working
with Procedures as (
select 1 as ID, '1,2,3' as Typ
),
ProcedureTypes as (
select 1 as TypeID, 'Drug' as Name
union select 2 , 'Other-Drug'
union select 3 , 'Test 3'
)
/*Get one extra column of type xml*/
,Procedures_xml as (
select id,CONVERT(xml,' <root> <s>' + REPLACE(Typ,',','</s> <s>') + '</s> </root> ') as Typ_xml
from Procedures
)
/*Convert the field string to multiple rows then join to procedure types*/
, Procdure_With_Type as (
select ID,T.c.value('.','varchar(20)') as TypeID,
ProcedureTypes.Name
from Procedures_xml
CROSS APPLY Typ_xml.nodes('/root/s') T(c)
INNER JOIN ProcedureTypes ON T.c.value('.','varchar(20)') = ProcedureTypes.TypeID
)
/*Finally, group the procedures type names by procedure id*/
select id,
STUFF((
SELECT ', ' + [Name]
FROM Procdure_With_Type inn
WHERE (Procdure_With_Type.ID = inn.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
from Procdure_With_Type
group by ID
You can't have a select statement as a parameter for a function, so instead of this:
SELECT * FROM dbo.[Split]((select RequestType from GPsProcedures), ',')
Use this:
select S.*
from GPsProcedures P
cross apply dbo.[Split](P.RequestType, ',') S
I'm attempting to create a derived table of country data from several other tables. Those tables look something like this:
Countries
ID | Name
Country_demographics
ID | date | Population | urban_pop | birth_rate
country_financials
ID | date | GDP | GDP_per_capita
Now, I'm trying to make a new table with
New_Table
ID | Name | date | population | urban_pop | birth_rate | gdp | gdp_per_capita
I have a stored procedure that currently looks something like this:
CREATE OR REPLEACE PROCEDURE SP_COUNTRY (
chunkSize IN INT
) AS
--create tables to hold IDs and stats
TYPE idTable IS TABLE OF COUNTRIES.ID%TYPE;
TYPE dateTable IS TABLE OF COUNTRY_DEMOGRAPHICS.EVALUATION_DATE%TYPE;
TYPE totPopTable IS TABLE OF COUNTRY_DEMOGRAPHICS.POPULATION_TOTAL_COUNT%TYPE;
TYPE urbanPopTable IS TABLE OF COUNTRY_DEMOGRAPHICS.POPULATION_URBAN_COUNT%TYPE;
--constructors
ids idTable;
dates dateTable;
totpop totPopTable;
urbanpop urbanPopTable;
--cursors
CURSOR countryCur IS
SELECT c.ID,cd.EVALUATION_DATE,cd.POPULATION_TOTAL_COUNT,cd.POPULATION_URBAN_COUNT
FROM COUNTRIES c,COUNTRY_DEMOGRAPHICS cd
WHERE c.id=cd.COUNTRY_ID
ORDER BY ID,EVALUATION_DATE;
BEGIN
dbms_output.enable(999999);
--open cursor
OPEN countryCur;
LOOP
--fetch and bulk collect
FETCH countryCur BULK COLLECT INTO ids,dates,totpop,urbanpop
LIMIT chunkSize;
--loop over collections
FOR j in ids.FIRST..ids.LAST
LOOP
--populate record
country.COUNTRY_ID := ids(j);
country.EVALUATION_DATE := dates(j);
country.POPULATION_TOTAL_COUNT := totpop(j);
country.POPULATION_URBAN_COUNT := urbanpop(j);
--update/insert table with record (much confusion here on how to update/insert and check if already exists in derived table..)
UPDATE NEW_TABLE SET ROW = country WHERE COUNTRY_ID = ids(j);
dbms_output.put_line('id: ' || country.COUNTRY_ID || ' date: ' || country.EVALUATION_DATE);
dbms_output.put_line(' pop: ' || country.POPULATION_TOTAL_COUNT || ' urban: ' || country.POPULATION_URBAN_COUNT);
END LOOP;
END LOOP;
--close cursor
CLOSE countryCur;
END;
As you can see, I'm using a different table type for each piece of data. I then plan on making a loop and then just inserting/updating in my new_table. I think there must be a better way to do this with %rowtype, or maybe creating a record and inserting the record? I'm not sure
Unless I'm missing something by simplifying this, and assuming cd.date and cf.date are equal, this should work:
INSERT INTO NEW_TABLE (ID, Name, date, population, urban_pop, birth_rate, gdp, gdp_per_capita)
values
(select c.id, c.name, cd.date,
cd.population, cd.urban_pop, cd.birthrate,
cf.gdp, cf.gdp_per_capita)
from Countries c, country_demographics cd, country_financials cf
where c.id = cd.id
and cd.id = cf.id);
Edit: Use the MERGE statement to update or insert depending on if the primary key exists:
MERGE INTO NEW_TABLE nt
USING ( select c.id, c.name, cd.date,
cd.population, cd.urban_pop, cd.birthrate,
cf.gdp, cf.gdp_per_capita
from Countries c, country_demographics cd, country_financials cf
where c.id = cd.id
and cd.id = cf.id ) a
ON (nt.id = a.id )
WHEN MATCHED THEN
UPDATE SET nt.Name = a.Name,
nt.date = a.date,
nt.population = a.population,
nt.urban_pop = a.urban_pop,
nt.birth_rate = a.birth_rate,
nt.gdp = a.gdp,
nt.gdp_per_capita = a.gdp_per_capita
WHEN NOT MATCHED THEN
INSERT (ID, Name, date, population, urban_pop, birth_rate, gdp, gdp_per_capita)
VALUES (a.id, a.Name, a.date, a.population, a.urban_pop, a.birth_rate, a.gdp, a.gdp_per_capita);