Oracle Pivot query gives columns with quotes around the column names. What? - sql

I'm trying to use PIVOT in Oracle and I'm getting a weird result. It's probably just an option I need to set but what I know about Oracle/SQL I could fit into this comment box.
Here's an example of my query:
with testdata as
(
select 'Fred' First_Name, 10 Items from dual
union
select 'John' First_Name, 5 Items from dual
union
select 'Jane' First_Name, 12 Items from dual
union
select 'Fred' First_Name, 15 Items from dual
)
select * from testdata
pivot (
sum(Items)
for First_Name
in ('Fred','John','Jane')
The results come out as I expected except the Column names have single quotes around them (picture from Toad - if I export to Excel the quotes get carried to Excel):
How do I get rid of the single quotes around the column names? I tried taking them out in the "in" clause and I get an error:
in (Fred,John,Jane)
I also tried replacing the single quotes with double quotes and got the same error. I don't know if this is an Oracle option I need to set/unset before running my query or a Toad thing.

you can provide aliases to the new columns in the pivot statement's IN clause.
(NB: This is different from the standard where clause IN() which does not allow aliases.)
with testdata as
(
select 'Fred' First_Name, 10 Items from dual
union
select 'John' First_Name, 5 Items from dual
union
select 'Jane' First_Name, 12 Items from dual
union
select 'Fred' First_Name, 15 Items from dual
)
select * from testdata
pivot (
sum(Items)
for First_Name
in ('Fred' as fred,'John' as john,'Jane' as jane)
)
and also for your aggregate clause which is necessary if you have multiple clauses..
with testdata as
(
select 'Fred' First_Name, 10 Items from dual
union
select 'John' First_Name, 5 Items from dual
union
select 'Jane' First_Name, 12 Items from dual
union
select 'Fred' First_Name, 15 Items from dual
)
select * from testdata
pivot (
sum(Items) itmsum,
count(Items) itmcnt
for First_Name
in ('Fred' as fred,'John' as john,'Jane' as jane)
)
returns
FRED_ITMSUM FRED_ITMCNT JOHN_ITMSUM JOHN_ITMCNT JANE_ITMSUM JANE_ITMCNT
----------- ----------- ----------- ----------- ----------- -----------
25 2 5 1 12 1
Of course you can then go full circle and use standard oracle aliasing and rename them to whatever you like including putting quotes back in again..
with testdata as
(
select 'Fred' First_Name, 10 Items from dual
union
select 'John' First_Name, 5 Items from dual
union
select 'Jane' First_Name, 12 Items from dual
union
select 'Fred' First_Name, 15 Items from dual
)
select FRED_ITMSUM "Fred's Sum", FRED_ITMCNT "Fred's Count"
, JOHN_ITMSUM "John's Sum", JOHN_ITMCNT "John's Count"
, JANE_ITMSUM "Janes's Sum", JANE_ITMCNT "Janes's Count"
from testdata
pivot (
sum(Items) itmsum,
count(Items) itmcnt
for First_Name
in ('Fred' as fred,'John' as john,'Jane' as jane)
)
gives
Fred's Sum Fred's Count John's Sum John's Count Janes's Sum Janes's Count
---------- ------------ ---------- ------------ ----------- -------------
25 2 5 1 12 1

Related

How to dedup array_agg in bigquery

I created a new table with repeating records with duplicates.
I am trying to find the most efficient way to deduplicate records as this will be run
on a table with millions of records.
If you using multiple CTE's nested does it matter what your data structure is the processing is done in memory or does it write to temp tables when there is a lot of data.
create or replace table t1.cte4 as
WITH t1 AS (
SELECT 1 as id,'eren' AS last_name UNION ALL
SELECT 1 as id,'yilmaz' AS last_name UNION ALL
SELECT 1 as id,'kaya' AS last_name UNION ALL
SELECT 1 as id,'kaya' AS last_name UNION ALL
SELECT 2 as id,'smith' AS last_name UNION ALL
SELECT 2 as id,'jones' AS last_name UNION ALL
SELECT 2 as id,'jones' AS last_name UNION ALL
SELECT 2 as id,'jones' AS last_name UNION ALL
SELECT 2 as id,'brown' AS last_name
)
SELECT id,ARRAY_AGG(STRUCT(last_name)) AS last_name_rec
FROM t1
GROUP BY id;
I can remove duplicates as follows.
QUERY 1 How to dedup the concat_struct ?
select id,
STRING_AGG( distinct ln.last_name ,'~') as concat_string,
ARRAY_AGG(STRUCT( ln.last_name )) as concat_struct
from `t1.cte4`, unnest(last_name_rec) ln
group by id;
QUERY 1
QUERY 2 Is there a better way then this to dedup?
select distinct id,
TO_JSON_STRING(ARRAY_AGG(ln.last_name) OVER (PARTITION BY id)) json_string
from `t1.cte4`, unnest(last_name_rec) ln
group by id,
ln.last_name;
QUERY 2
How do I get it out of the table as distinct rather then using the CTE. This does not dedup.
select id, ARRAY_AGG(STRUCT( ln.last_name )) as concat_struct
from t1.cte4,
unnest(last_name_rec) ln group by id;
I can't do this.
select id, ARRAY_AGG(distinct STRUCT( ln.last_name )) as concat_struct from t1.cte4,
unnest(last_name_rec) ln group by id;
UPDATE: Decompose the struct before deduplication and then compose it back:
select id, ARRAY_AGG(STRUCT(last_name)) as concat_struct
from (
select id, ln.last_name
from cte4, unnest(last_name_rec) ln
group by id, ln.last_name
) d
group by id
(original answer based on unwanted change of table definition follows)
Just use array_agg(distinct ...):
WITH t1 AS (
SELECT 1 as id,'eren' AS last_name UNION ALL
SELECT 1 as id,'yilmaz' AS last_name UNION ALL
SELECT 1 as id,'kaya' AS last_name UNION ALL
SELECT 1 as id,'kaya' AS last_name UNION ALL
SELECT 2 as id,'smith' AS last_name UNION ALL
SELECT 2 as id,'jones' AS last_name UNION ALL
SELECT 2 as id,'jones' AS last_name UNION ALL
SELECT 2 as id,'jones' AS last_name UNION ALL
SELECT 2 as id,'brown' AS last_name
)
SELECT id,ARRAY_AGG(distinct last_name) AS last_name_rec
FROM t1
GROUP BY id;

Oracle DB: Select Values from a list that do not exist in DB

I have a list of values. Some of which do not exist in the DB. I'd like to find which values in the list do not exist.
Eg. Given the table:
id name
--------
1 John
2 Amy
3 Kevin
4 Matt
5 Mark
6 Jim
7 Angela
8 Erin
With a list containing:
John, Amy, Sarah, Sam
I'd like to only return:
Sarah, Sam
How can I achieve this using Oracle DB? I know I could create a new table, insert the values from my list, do a join, then drop the table. Is there an easier way?
Create a table on-the-fly:
select name from
(
select 'John' as name from dual union all
select 'Amy' as name from dual union all
select 'Sarah' as name from dual union all
select 'Sam' as name from dual
) names
where name not in (select name from mytable);
You would use a left join or not exists:
select name
from (select 'John' as name from dual union all
select 'Amy' as name from dual union all
select 'Sarah' as name from dual union all
select 'Sam' as name from dual
) n left join
t
using (name)
where t.name is null;
You can use a collection constructor, for example:
select column_value from soda_key_list_t('John', 'Amy', 'Sarah', 'Sam');
You can create your own "table of strings" collection type if you don't already have one, e.g.
create or replace type varchar2_tt as table of varchar2(4000);
or else see what existing types are available:
select owner, type_name, length
from all_coll_types t
where t.coll_type = 'TABLE'
and t.elem_type_name = 'VARCHAR2'
order by 1,2;
I've used soda_key_list_t which is included with Oracle 18c onwards as part of the Simple Oracle Document Access feature. Others include dbms_debug_vc2coll and ora_mining_varchar2_nt.
You can also use a unpivot table approach :
select Number as list from (
select * from (
select 'jn','jimmy','ronny' from dual )
unpivot
(
"Values" FOR "Number" IN ("'JN'","'JIMMY'","'RONNY'")
)
)
minus
select list from your_table;

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

Union and union all in oracle Database

While executing the below two queries (focus on the part between two asterisks * ____ *), I am really wondering how does the position of UNION ALL changes the output. I am unable to understand.
Query 1
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
*UNION All SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL*
UNION SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
Query Result
NAME MARKS
Jack 100
Query 2
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
UNION SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
*UNION ALL SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL*
Query Result
NAME MARKS
Jack 100
Jack 100
Thanks :)
If you don't specify parantheses, the selects would get executed one after the other. All set operators minus, union, union all, intersect have the same precedence.
Oracle Documentation
In the first query, UNION is performed at the end so there would be no dup rows per the query. In the second, UNION ALL is performed at the end, so there would be dup rows per your query.
The difference between Union and Union all is that Union all will not eliminate duplicate rows,
first query output:
step 1:
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
UNION All SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
result 2 rows because union all allows duplicates.
step 2:
UNION SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
this query will pick only row without duplicates from the above 2 rows and itself.
returns 1 row.
in the second query...
step 1
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
UNION SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
returns 1 row because union selects only distinct rows.
step 2
UNION ALL SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
return 2 rows because it allows duplicates.
Both union and union all have the same precedence as operations. So in the absence of parentheses, your two unions would be evaluated from top to bottom. Your first query is being evaluated like this:
SELECT Name, Marks
FROM
(
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
UNION All
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
) t
UNION
SELECT 'Jack' AS Name, 100 AS Marks FROM DUAL
The same reasoning applies to your second query.
To understand this behavior,you need to be clear with the difference between UNION and UNION ALL.
Query 1: Output of first two lines will return 2 rows. However, last line(sql statement) with 'UNION' operator will remove duplicate rows,including it's own output. So, total first query will return only 1 row.
Query 2: Now, UNION is on 2nd line. After getting combined with 1st line, it will again return only 1 row. But, on the 3rd line with 'UNION ALL',it will return 2 rows. Because, 'UNION ALL' won't remove duplicates.

In oracle, how to 'group by' properties that are in comma separated values?

Say, I have a table like
Name Pets
-------------------------
Anna Cats,Dogs,Hamsters
John Cats
Jake Dogs,Cats
Jill Parrots
And I want to count, how many people have different types of pets. The output would be something like
Pets Owners
---------------
Cats 3
Dogs 2
Hamsters 1
Parrots 1
Limitations:
Reworking of DB scheme is impractical. If I could do it, I wouldn't be here.
All logic must be done in one SQL query.
I can't take result table and deduce owner count later in code.
Using built-in Oracle functions is OK, but writing custom functions is discouraged.
Oracle version – 11 and up.
It's a terrible design - as you mentioned - so I don't envy you having to work with it!
It's possible to do what you're after, although I wouldn't like to say that it would be performant for larger datasets!
Assuming the name column is the primary key (or at least unique):
with t1 as (select 'Anna' name, 'Cats,Dogs,Hamsters' pets from dual union all
select 'John' name, 'Cats' pets from dual union all
select 'Jake' name, 'Dogs,Cats' pets from dual union all
select 'Jill' name, 'Parrots' pets from dual)
select pet pets,
count(*) owners
from (select name,
regexp_substr(pets, '(.*?)(,|$)', 1, level, null, 1) pet
from t1
connect by prior name = name
and prior sys_guid() is not null
and level <= regexp_count(pets, ',') + 1)
group by pet
order by owners desc, pet;
PETS OWNERS
---------- ----------
Cats 3
Dogs 2
Hamsters 1
Parrots 1
It is a bad design to store comma-separated values in a single column. You should consider normalizing the data. Having such a design will always push you to have an overhead of manipulating delimited-strings.
Anyway, as a workaround, you could use REGEXP_SUBSTR and CONNECT BY to split the comma-delimited string into multiple rows and then count the pets.
There are other ways of doing it too, like XMLTABLE, MODEL clause. Have a look at split the comma-delimited string into multiple rows.
SQL> WITH sample_data AS(
2 SELECT 'Anna' NAME, 'Cats,Dogs,Hamsters' pets FROM dual UNION ALL
3 SELECT 'John' NAME, 'Cats' pets FROM dual UNION ALL
4 SELECT 'Jake' NAME, 'Dogs,Cats' pets FROM dual UNION ALL
5 SELECT 'Jill' NAME, 'Parrots' pets FROM dual
6 )
7 -- end of sample_data mimicking a real table
8 SELECT pets,
9 COUNT(*) cnt
10 FROM
11 (SELECT trim(regexp_substr(t.pets, '[^,]+', 1, lines.COLUMN_VALUE)) pets
12 FROM sample_data t,
13 TABLE (CAST (MULTISET
14 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.pets, ',')+1
15 ) AS sys.odciNumberList ) ) lines
16 ORDER BY NAME,
17 lines.COLUMN_VALUE
18 )
19 GROUP BY pets
20 ORDER BY cnt DESC;
PETS CNT
------------------ ----------
Cats 3
Dogs 2
Hamsters 1
Parrots 1
SQL>
My try, only with substr and instr :)
with a as (
select 'Anna' as name, 'Cats,Dogs,Hamsters' as pets from dual union all
select 'John', 'Cats' from dual union all
select 'Jake', 'Dogs,Cats' from dual union all
select 'Jill', 'Parrots' from dual
),
b as(
select name, pets, substr(pets, starting_pos, ending_pos - starting_pos) pet
from (
select name, pets,
decode(lvl, 1, 0, instr(a.pets,',',1,lvl-1))+1 starting_pos,
instr(a.pets,',',1,lvl) ending_pos
from (select name, pets||',' pets from a
)a
join (select level lvl from dual connect by level < 10)
on instr(a.pets,',', 1, lvl) > 0
)
)
--select * from b
select pet, count(*) from b group by pet;
select x pets ,count(x) Owners from (
select extractvalue(value(x), '/b') x
from (select yourcolumn as str from yourtable) t,
table(
xmlsequence(
xmltype('<a><b>' || replace(str, ',', '</b><b>') || '</b></a>' ).extract('/*/*')
)
) x)
group by x;
/replace yourcolumn with your column name (pets) and yourtable with your table name./