How do you make an Oracle SQL query to - sql

This table is rather backwards from a normal schema, and I'm not sure how to get the data I need from it.
Here is some sample data,
Value (column) Info (column)
---------------------------------------------
Supplier_1 'Some supplier'
Supplier_1_email 'foo#gmail.com'
Supplier_1_rating '5'
Supplier_1_status 'Active'
Supplier_2 'Some other supplier'
Supplier_2_email 'bar#gmail.com'
Supplier_2_rating '4'
Supplier_2_status 'Active'
Supplier_3 'Yet another supplier'
...
I need a query to find the email of the supplier which has the highest rating and is currently of status 'Active'.

select
m.sup_email, r.sup_rating
from
(select substr(value, 1, length(value) - length('_email') as sup_name, info as sup_email from table where value like '%_email') as m
left join
(select substr(value, 1, length(value) - length('_rating') as sup_name), info as sub_rating from table where value like '%_rating') as r on m.sup_name = r.sup_name
order by
sup_rating desc
limit
1;

For a single pass solution, try:
select "email" from
(select
substr("value", 1, 8 + instr(substr("value", 10, length("value")-9),'_')) "supplier",
max(case when "value" like '%_status' then "info" end) as "status",
max(case when "value" like '%_rating' then cast("info" as integer) end) as "rating",
max(case when "value" like '%_email' then "info" end) as "email"
from "table" t
where "value" like '%_rating' or "value" like '%_email' or "value" like '%_status'
group by substr("value", 1, 8 + instr(substr("value", 10, length("value")-9),'_'))
having max(case when "value" like '%_status' then "info" end) = 'Active'
order by 3 desc
) where rownum = 1
(Column names are all double-quoted as some are reserved words.)

Expanding on Mike's excellent suggestion:
CREATE VIEW supplier_names AS
SELECT SUBSTR(Value,INSTR(Value,'_')+1) AS supplier_id
,Info AS supplier_name
FROM the_table
WHERE INSTR(Value,'_',1,2) = 0;
CREATE VIEW supplier_emails AS
SELECT SUBSTR(Value,INSTR(Value,'_')+1,INSTR(Value,'_',1,2)-INSTR(Value,'_')-1)
AS supplier_id
,Info AS supplier_email
FROM the_table
WHERE Value LIKE '%email';
CREATE VIEW supplier_ratings AS
SELECT SUBSTR(Value,INSTR(Value,'_')+1,INSTR(Value,'_',1,2)-INSTR(Value,'_')-1)
AS supplier_id
,Info AS supplier_rating
FROM the_table
WHERE Value LIKE '%rating';
CREATE VIEW supplier_statuses AS
SELECT SUBSTR(Value,INSTR(Value,'_')+1,INSTR(Value,'_',1,2)-INSTR(Value,'_')-1)
AS supplier_id
,Info AS supplier_rating
FROM the_table
WHERE Value LIKE '%status';
The queries will perform like dogs, so I'd suggest you look into creating some virtual columns, or at least function-based indexes, to optimise these queries.

Related

How to transform columns to rows in oracle sql

I have a table having below data , total 8 rows here sample for 3 rows -
now I transformed the query using case statement to this using below query -
select
case when entity ='PRODUCT' then prd_table_main end P_main_prd ,
case when entity ='PRODUCT' then prd_table_sec end P_sec_prd,
case when entity ='CUSTOMER' then cus_table_main end P_main_cus ,
case when entity ='CUSTOMER' then cus_table_sec end p_sec_cus,
case when entity ='PROFIT' then prof_table_main end p_main_prof ,
case when entity ='PROFIT' then prof_table_sec end p_sec_prof
from (
select * from above table);
Now I want to have the o/p as in one row removing all the nulls. Basically I want to create a cursor and pass the value of tables to be used in the procedure as p_main_prd or p_sec_prd or the remaining ones as the requirement.
You are almost there, you just need to aggeregate:
select MAX( case when entity = 'product' then table1 end ) AS P_main_prd,
MAX( case when entity = 'product' then table2 end ) AS P_sec_prd,
MAX( case when entity = 'customer' then table1 end ) AS P_main_cus,
MAX( case when entity = 'customer' then table2 end ) AS p_sec_cus,
MAX( case when entity = 'profit' then table1 end ) AS p_main_prof,
MAX( case when entity = 'profit' then table2 end ) AS p_sec_prof
from table_name;
or use PIVOT:
SELECT prd_p_main AS p_main_prd,
prd_p_sec AS p_sec_prd,
cus_p_main AS p_main_cus,
cus_p_sec AS p_sec_cus,
prof_p_main AS p_main_prof,
prof_p_sec AS p_sec_prof
FROM table_name
PIVOT (
MAX( table1 ) AS p_main,
MAX( table2 ) AS p_sec
FOR entity IN (
'product' AS prd,
'customer' AS cus,
'profit' AS prof
)
)
Which, for the sample data:
CREATE TABLE table_name ( entity, table1, table2 ) AS
SELECT 'product', 'prd_table_main', 'prd_table_sec' FROM DUAL UNION ALL
SELECT 'customer', 'cus_table_main', 'cus_table_sec' FROM DUAL UNION ALL
SELECT 'profit', 'prof_table_main', 'prof_table_sec' FROM DUAL
Outputs:
P_MAIN_PRD
P_SEC_PRD
P_MAIN_CUS
P_SEC_CUS
P_MAIN_PROF
P_SEC_PROF
prd_table_main
prd_table_sec
cus_table_main
cus_table_sec
prof_table_main
prof_table_sec
db<>fiddle here
Maybe you can try UNPIVOT. I'm not an expert on this, so I can only help you by giving you directions. Check this out, probably this will help you out, or at least show you another way:
Oracle Unpivot

how to convert jsonarray to multi column from hive

example:
there is a json array column(type:string) from a hive table like:
"[{"filed":"name", "value":"alice"}, {"filed":"age", "value":"14"}......]"
how to convert it into :
name age
alice 14
by hive sql?
I've tried lateral view explode but it's not working.
thanks a lot!
This is working example of how it can be parsed in Hive. Customize it yourself and debug on real data, see comments in the code:
with your_table as (
select stack(1,
1,
'[{"field":"name", "value":"alice"}, {"field":"age", "value":"14"}, {"field":"something_else", "value":"somevalue"}]'
) as (id,str) --one row table with id and string with json. Use your table instead of this example
)
select id,
max(case when field_map['field'] = 'name' then field_map['value'] end) as name,
max(case when field_map['field'] = 'age' then field_map['value'] end) as age --do the same for all fields
from
(
select t.id,
t.str as original_string,
str_to_map(regexp_replace(regexp_replace(trim(a.field),', +',','),'\\{|\\}|"','')) field_map --remove extra characters and convert to map
from your_table t
lateral view outer explode(split(regexp_replace(regexp_replace(str,'\\[|\\]',''),'\\},','}|'),'\\|')) a as field --remove [], replace "}," with '}|" and explode
) s
group by id --aggregate in single row
;
Result:
OK
id name age
1 alice 14
One more approach using get_json_object:
with your_table as (
select stack(1,
1,
'[{"field":"name", "value":"alice"}, {"field":"age", "value":"14"}, {"field":"something_else", "value":"somevalue"}]'
) as (id,str) --one row table with id and string with json. Use your table instead of this example
)
select id,
max(case when field = 'name' then value end) as name,
max(case when field = 'age' then value end) as age --do the same for all fields
from
(
select t.id,
get_json_object(trim(a.field),'$.field') field,
get_json_object(trim(a.field),'$.value') value
from your_table t
lateral view outer explode(split(regexp_replace(regexp_replace(str,'\\[|\\]',''),'\\},','}|'),'\\|')) a as field --remove [], replace "}," with '}|" and explode
) s
group by id --aggregate in single row
;
Result:
OK
id name age
1 alice 14

Oracle - Extract numbers for comparison from varchar2 column

I have encountered a following problem while solving a task:
In Oracle database I have got a table ENTITY_INFO that is fairly simple in structure. It contains 3 columns:
ENTITY_ID (VARCHAR2) - PK of the entity in database
NAME (VARCHAR2) - name of the information, i.e. "location", "cost", "last encounter"
VALUE (VARCHAR2) - a value of the information, i.e. "assets/music", "1500", "1.1.2000"
Currently, I need to filter out entities that have its "cost" < 1000.
A naive approach via
SELECT ENTITY_ID FROM ENTITY_INFO WHERE NAME = 'cost' AND TO_NUMBER(VALUE)<1000
does not work, because column VALUE contains values that are not number.
But all column values that match the filter NAME = 'cost' are numbers, so the case I need to do is valid.
I found Select string as number on Oracle topic, but the information inside prove not useful to solving this problem.
Due to nature of ENTITY_INFO and a state of project, the change of datamodel is not viable solution too.
Thanks for any hints.
You could make the conversion to a number conditional:
SELECT ENTITY_ID
FROM ENTITY_INFO
WHERE NAME = 'cost'
AND TO_NUMBER(CASE WHEN NAME = 'cost' THEN VALUE ELSE NULL END) < 1000
Alternate approach leveraging WITH clause, on the presumption that all the records with name are numbers
In the tab1 part, use the filter condition and query from tab1 with TO_NUMBER
WITH tab1
AS (SELECT entity_id, name, VALUE
FROM entity_info
WHERE name = 'cost')
SELECT *
FROM tab1
WHERE TO_NUMBER (VALUE) < 1000
Having numbers and characters in one column is an accident waiting to happen. Adding another column to distinguish numeric and non-numeric is not an option, I would reckon to have a constraint to deter entering non-numeric if name is cost
In my compiler, I see no problem with your code (or this equivalence of it's):
SELECT ENTITY_ID
FROM ENTITY_INFO
WHERE NAME = 'cost'
AND VALUE < 1000
Example with data samples:
with ENTITY_INFO as (
select 1 as ENTITY_ID, 'cost' as name, '2000' as value from dual
union all
select 2 as ENTITY_ID, 'cost' as name, '900' as value from dual
union all
select 3 as ENTITY_ID, 'cost' as name, '3000' as value from dual
union all
select 4 as ENTITY_ID, 'cost' as name, '2500' as value from dual
union all
select 5 as ENTITY_ID, 'cost' as name, '700' as value from dual
union all
select 6 as ENTITY_ID, 'frf' as name, '250sasd0' as value from dual
union all
select 7 as ENTITY_ID, 'corfrst' as name, '70fa0' as value from dual
)
SELECT ENTITY_ID
FROM ENTITY_INFO
WHERE NAME = 'cost'
AND VALUE < 1000
Result:
ENTITY_ID
2
5
Alternatively, you can use the subquery that would assure that all of the resulting column values from it would be number-like strings:
SELECT ENTITY_ID
FROM (SELECT ENTITY_ID,
VALUE
FROM ENTITY_INFO
WHERE NAME = 'cost' )
WHERE TO_NUMBER(VALUE)<1000
I hope I helped!

Select table value by range

In my table I have the primary key id. It is a two byte varchar type.
If the id is from 00 to 89 then it returns a set of data, otherwise returns different data. My query in the stored procedure is
BEGIN
select * from MyTable where (id like '0%' or
id like '1%' or
id like '2%' or
id like '3%' or
id like '4%' or
id like '5%' or
id like '6%' or
id like '7%' or
id like '8%')
and status = 'active'
union all
select * from MyTable where id like '9%' and status='inactive'
END
My question is how can I improve it? May I convert the string to number then using >89 or <90?
You can also use regular expressions:
select * from MyTable
where REGEXP_LIKE(id, '^[0-8][0-9]') and status='active'
or REGEXP_LIKE(id, '^9[0-9]') and status='inactive'
How about:
select * from mytable
where ( id < '9' and status = 'active' )
or ( id >= '9' and status = 'inactive' )

How to find which word has max occurance in three fields of a table sql server

i have a table which has many fields but i want to get count of every word in any three fields of that table
find all title in a table that exist more than once...so for that i can issue this statement
SELECT title, COUNT(title) AS NumOccurrences FROM users
GROUP BY titleHAVING ( COUNT(title) > 1 )
suppose my table has three fields called title,url,description.
basically i do not know which word has been stored in which 3 fields in that table maximum time.
i want to issue a sql statement which can show me which word found maximum time...like
word-name occurance
--------- -------
sqlserver 300
jquery 120
ajax 110
please guide me with sample sql for sql server 2000/2005 thanks
Here is my updated full code.....please have look
IF OBJECT_ID('tempdb..#tempSearch') IS NOT NULL
BEGIN
DROP TABLE #tempSearch
END
CREATE TABLE #tempSearch(
ID INT,
Title nvarchar(4000),
Description ntext,
Url nvarchar(4000),
Type char(1))
INSERT INTO #tempSearch
SELECT * from vwProductSearch
INSERT INTO #tempSearch
SELECT * from vwContentSearch
SELECT Word,
COUNT(Word) AS TotalOccurrences,
COUNT(CASE WHEN Field = 'Title' THEN Word END) AS OccurancesInTitle,
COUNT(CASE WHEN Field = 'URL' THEN Word END) AS OccurancesInURL,
COUNT(CASE WHEN Field = 'Description' THEN Word END) AS OccurancesInDescription
FROM ( SELECT CONVERT(NTEXT, Title) AS Word, 'Title' AS Field
FROM #tempSearch
UNION ALL
SELECT CONVERT(NTEXT, URL), 'URL' AS Field
FROM #tempSearch
UNION ALL
SELECT CONVERT(NTEXT, Description), 'Description' AS Field
FROM #tempSearch
) As Fields
GROUP BY Word
HAVING COUNT(Word) > 1
DROP TABLE #tempSearch
You need to use UNION to combine your 3 fields into a single column so you can use this to group by. I've also added a few more counts in case you need to drill down as to where the word occurs the most.
SELECT Word,
COUNT(Word) AS TotalOccurrences,
COUNT(CASE WHEN Field = 'Title' THEN Word END) AS OccurancesInTitle,
COUNT(CASE WHEN Field = 'URL' THEN Word END) AS OccurancesInURL,
COUNT(CASE WHEN Field = 'Description' THEN Word END) AS OccurancesInDescription
FROM ( SELECT Title AS Word, 'Title' AS Field
FROM Users
UNION ALL
SELECT URL, 'URL' AS Field
FROM Users
UNION ALL
SELECT Description, 'Description' AS Field
FROM Users
) As Fields
GROUP BY Word
HAVING COUNT(Word) > 1
EDIT
I know you have asked about SQL_Server 2005 and 2000, but if you were ever to upgrade to 2008 or later there is a much cleaner solution:
SELECT Word,
COUNT(Word) AS TotalOccurrences,
COUNT(CASE WHEN Field = 'Title' THEN Word END) AS OccurancesInTitle,
COUNT(CASE WHEN Field = 'URL' THEN Word END) AS OccurancesInURL,
COUNT(CASE WHEN Field = 'Description' THEN Word END) AS OccurancesInDescription
FROM Users
CROSS APPLY
( VALUES
(Title, 'Title'),
(URL, 'URL'),
(Description, 'Description')
) AS T (Word, Field)
GROUP BY Word
HAVING COUNT(Word) > 1
EDIT 2
If all your columns are different datatypes you will need to explicitly convert them:
SELECT Word,
COUNT(Word) AS TotalOccurrences,
COUNT(CASE WHEN Field = 'Title' THEN Word END) AS OccurancesInTitle,
COUNT(CASE WHEN Field = 'URL' THEN Word END) AS OccurancesInURL,
COUNT(CASE WHEN Field = 'Description' THEN Word END) AS OccurancesInDescription
FROM ( SELECT CONVERT(NTEXT, Title) AS Word, 'Title' AS Field
FROM Users
UNION ALL
SELECT CONVERT(NTEXT, URL), 'URL' AS Field
FROM Users
UNION ALL
SELECT CONVERT(NTEXT, Description), 'Description' AS Field
FROM Users
) As Fields
GROUP BY Word
HAVING COUNT(Word) > 1
EDIT 3
There is no way around the error you are getting, you cannot group by NTEXT. The best solution I can come up with feels very dirty, and I'm not particularly happy with it...
SELECT COALESCE(Title, URL, Description) AS Word,
COALESCE(Title.Occurances, 0) + COALESCE(URL.Occurances, 0) + COALESCE(Description.Occurances, 0) AS TotalOccurances,
COALESCE(Title.Occurances, 0) AS TitleOccurances,
COALESCE(URL.Occurances, 0) AS URLOccurances,
COALESCE(Description.Occurances, 0) AS DescriptionOccurances
FROM ( SELECT CONVERT(NTEXT, Title) AS Title, COUNT(*) AS Occurances
FROM #tempSearch
GROUP BY Title
) AS Title
FULL JOIN
( SELECT CONVERT(NTEXT, URL) AS URL, COUNT(*) AS Occurances
FROM #tempSearch
GROUP BY URL
) AS URL
ON URL LIKE Title
FULL JOIN
( SELECT Description, 1 AS Occurances
FROM #tempSearch
) AS Description
ON Description LIKE Title
This works, but like I said, it isn't perfect and probably won't perform very well. Strongly consider upgrading to a later version of SQL-Server!
You can count the number of occurrences a word has across three fields using the difference in LEN() to account for the word appearing multiple times in the same field:
SELECT
a.wordname,
SUM(
((LEN(b.title) - LEN(REPLACE(b.title, a.wordname, ''))) / LEN(a.wordname)) +
((LEN(b.url) - LEN(REPLACE(b.url, a.wordname, ''))) / LEN(a.wordname)) +
((LEN(b.description) - LEN(REPLACE(b.description, a.wordname, ''))) / LEN(a.wordname))
) AS occurrence
FROM
(
SELECT 'sqlserver' AS wordname UNION ALL
SELECT 'jquery' AS wordname UNION ALL
SELECT 'ajax' AS wordname
) a
CROSS JOIN
users b
GROUP BY
a.wordname
ORDER BY
occurrence DESC
If you have a table of wordnames, just put that table name in place of the many SELECT ... UNION ALL statements I have put for the sake of example.