ORACLE: Sorting a String field containing numbers - sql

I want to display page number in ascending order. But, since the field PAGE is of String datatype, normal 'ORDER BY' considers 10 < 2. I have to make the field PAGE as String because there can be inputs like '3-4'. Can anyone please suggest a way out. I've attached screenshot for reference.
Kindly help.Screenshot
select id
,F_NL
,page
,title
from newsletter_content
where F_NL = '29'
order by page asc;

select page from p
order by to_number(nvl(substr(page, 1, instr(page, '-')-1), page))
rextester demo

You can check for the presence of a - character and extract the preceding number:
ORDER BY CASE
WHEN INSTR( page, '-' ) > 0
THEN TO_NUMBER( SUBSTR( page, 1, INSTR( page, '-' ) - 1 ) )
ELSE TO_NUMBER( page )
END;

Related

What is the rule of order by with special character?

I sort my data with select pk_customer_no from customer order by pk_customer_no
The code with '-', didn't group together and sort by letter, It seems sql just ignore it and sort by the third letter.
How can I sort by the '-'?
The '-' character is ignored in sorting.
You can use order by replace '-' with '0' (zero), if you want to put the words with '-' in front.
select t.pk_customer_no as rep from (
values ('YH'), ('YHC'), ('Z-CH'), ('Z-CHE'), ('ZCM'), ('Z-CP'), ('Z1'), ('ZHT'), ('ZLA'), ('Z-JP'), ('ZLENO')
) as t (pk_customer_no)
order by replace(t.pk_customer_no, '-', '0')
You can use order by replace '-' with 'Z' if you want to put the words with '-' at the end.
select t.pk_customer_no as rep from (
values ('YH'), ('YHC'), ('Z-CH'), ('Z-CHE'), ('ZCM'), ('Z-CP'), ('Z1'), ('ZHT'), ('ZLA'), ('Z-JP'), ('ZLENO')
) as t (pk_customer_no)
order by replace(t.pk_customer_no, '-', 'Z')

SQL How to perform multiple look-ups from a list, in one query

We have a weird database table (wt) for which I can construct a query that can return a single row with these fields:
wt.thing_a_id = 5, wt.thing_b_id = 12, wt.thing_c_id = 9
Then, there's another lookup table (dt) that holds descriptions for these numbers, you could imagine it like this:
id desc
5 "flour"
12 "cups"
9 "barley"
what I need to end up with is numbers from wt, along with its description from dt.
I can do 3 simple queries, one to look up each of my three thing_ values (select desc from dt where id = ) but I was hoping to do it all in one query.
Is there a way to do this?
Even better, is there way to do my query to get my single row of thing id's and combine them with their descriptions? I think the fundamental problem/challenge is that my thing id's are not one per row, but that they come back as fields in just one row. This makes it really hard to join against them, for example.
Michael
You seem to want conditional aggregation:
select
max(case when id = 3 then descr end) descr_3,
max(case when id = 12 then descr end) descr_12,
max(case when id = 9 then descr end) descr_9
from dt
where id in (3, 12, 9)
Note that desc is a SQL keyword, hence a poor choice for a column name. I renamed it descr in the query.
You will need multiple joins to the dt table to get the description of each of the "things" you want in a single row:
SELECT thing_a_id, dta.desc AS thing_a_desc,
thing_b_id, dtb.desc AS thing_b_desc,
thing_c_id, dtc.desc AS thing_c_desc
FROM wt
JOIN dt dta ON dta.id = wt.thing_a_id
JOIN dt dtb ON dtb.id = wt.thing_b_id
JOIN dt dtc ON dtc.id = wt.thing_c_id
I love to play with common table expressions (CTE), this is an ideal candidate for one.
In the example below, decriptions and dataset are substitutes for the actual tables you use. I am just building them in memory rather than an actual table.
In the "breakdown" CTE I am splitting up the CSV value from dataset into multiple rows.
In the last part of the select I am converting everything after the = sign to a number, and then matching that on id from the descriptions CTE. The resulting dataset is I believe what you requested.
WITH
descriptions AS
(SELECT 5 AS id, 'flour' AS description FROM DUAL
UNION ALL
SELECT 12 AS id, 'cups' AS description FROM DUAL
UNION ALL
SELECT 9 AS id, 'barley' AS description FROM DUAL),
dataset AS
(SELECT 'wt.thing_a_id = 5, wt.thing_b_id = 12, wt.thing_c_id = 9' AS result FROM DUAL),
breakdown ( result, REMAINDER ) AS
(SELECT TRIM( SUBSTR( result
, 1
, INSTR( result || ',', ',' ) - 1 ) ) AS result
, TRIM( SUBSTR( result, INSTR( result || ',', ',' ) + 1 ) || ',' ) AS REMAINDER
FROM dataset
UNION ALL
SELECT TRIM( SUBSTR( REMAINDER
, 1
, INSTR( REMAINDER, ',' ) - 1 ) )
, SUBSTR( REMAINDER, INSTR( REMAINDER || ',', ',' ) + 1 ) AS REMAINDER
FROM breakdown
WHERE REMAINDER IS NOT NULL)
SELECT result, TO_NUMBER( TRIM( SUBSTR( result, INSTR( result, '=' ) + 1 ) ) ) AS id, description
FROM breakdown
LEFT OUTER JOIN descriptions
ON TO_NUMBER( TRIM( SUBSTR( breakdown.result, INSTR( breakdown.result, '=' ) + 1 ) ) ) =
descriptions.id
Results:
Result ID DESCRIPTION
wt.thing_a_id = 5 5 flour
wt.thing_b_id = 12 12 cups
wt.thing_c_id = 9 9 barley

REGEXP_REPLACE to replace emails in a list except a specific domain

I am novice to regular expressions. I am trying to remove emails from a list which do not belong to a specific domain.
for e.g. I have a below list of emails:
John#yahoo.co.in , Jacob#gmail.com, Bob#rediff.com,
Lisa#abc.com, sam#gmail.com , rita#yahoo.com
I need to get only the gmail ids:
Jacob#gmail.com, sam#gmail.com
Please note we may have spaces before the comma delimiters.
Appreciate any help!
This could be a start for you.
SELECT *
FROM ( SELECT REGEXP_SUBSTR (str,
'[[:alnum:]\.\+]+#gmail.com',
1,
LEVEL)
AS SUBSTR
FROM (SELECT ' John#yahoo.co.in , Jacob.foo#gmail.com, Bob#rediff.com,Lisa#abc.com, sam#gmail.com , sam.bar+stackoverflow#gmail.com, rita#yahoo.com, foobar '
AS str
FROM DUAL)
CONNECT BY LEVEL <= LENGTH (REGEXP_REPLACE (str, '[^,]+')) + 1)
WHERE SUBSTR IS NOT NULL ;
Put in a few more examples, but an email checker should comply to the respective RFCs, look at wikipedia for further knowledge about them https://en.wikipedia.org/wiki/Email_address
Inspiration from https://stackoverflow.com/a/17597049/869069
Rather than suppress the emails not matching a particular domain (in your example, gmail.com), you might try getting only those emails that match the domain:
WITH a1 AS (
SELECT 'John#yahoo.co.in , Jacob#gmail.com, Bob#rediff.com,Lisa#abc.com, sam#gmail.com , rita#yahoo.com' AS email_list FROM dual
)
SELECT LISTAGG(TRIM(email), ',') WITHIN GROUP ( ORDER BY priority )
FROM (
SELECT REGEXP_SUBSTR(email_list, '[^,]+#gmail.com', 1, LEVEL, 'i') AS email
, LEVEL AS priority
FROM a1
CONNECT BY LEVEL <= REGEXP_COUNT(email_list, '[^,]+#gmail.com', 1, 'i')
);
That said, Oracle is probably not the best tool for this (do you have these email addresses stored as a list in a table somewhere? If so then #GordonLinoff's comment is apt - fix your data model if you can).
Here's a method using a CTE just for a different take on the problem. First step is to make a CTE "table" that contains the parsed list elements. Then select from that. The CTE regex handles NULL list elements.
with main_tbl(email) as (
select ' John#yahoo.co.in , Jacob.foo#gmail.com, Bob#rediff.com,Lisa#abc.com, sam#gmail.com , sam.bar+stackoverflow#gmail.com, rita#yahoo.com, foobar '
from dual
),
email_list(email_addr) as (
select trim(regexp_substr(email, '(.*?)(,|$)', 1, level, NULL, 1))
from main_tbl
connect by level <= regexp_count(email, ',')+1
)
-- select * from email_list;
select LISTAGG(TRIM(email_addr), ', ') WITHIN GROUP ( ORDER BY email_addr )
from email_list
where lower(email_addr) like '%gmail.com';

Find the most frequent value ignoring everything after '(' within it

I am trying to find the most frequent string ignoring everything after ( within it.
So, how it should work. If I've got the strings:
England (88)
Iceland (100)
Iceland (77)
England (88)
Denmark (15)
Iceland (18)
It should return
Iceland
because it's the most frequent country here and no matter that as a string England (88) is going to pretend.
Unfortunately, my query returns
England(88)
SQLfiddle
I've been thinking to do it by 2 steps:
truncate every country string
do script that I already written.
But I failed on the first step.
SQL Fiddle is acting up, so can't test, but I'd think you could use SUBSTR() and INSTR() to isolate the portion left of the first (:
SELECT SUBSTR(X,1,INSTR(X,'(')-1) AS HUS
FROM tt
GROUP BY SUBSTR(X,1,INSTR(X,'(')-1)
ORDER BY COUNT(*) DESC
LIMIT 1;
Edit: Tested on https://sqliteonline.com/ and it returns Iceland as expected: Fiddle.
This is a bunch of string manipulation, which is rather cumbersome in SQLite. Here is one approach:
select trim(substr(str, 1, instr(str, '(') - 1)) as country,
sum(cast(replace(substr(str + 1, instr(str, '('), ')', '') as int))
from t
group by trim(substr(str, 1, instr(str, '(') - 1));
This would be a bullet-proof, whether you have '(' in your text or not
select rtrim(substr(mycolumn,1,instr(mycolumn || '(','(')-1))
from mytable
group by rtrim(substr(mycolumn,1,instr(mycolumn || '(','(')-1))
order by count(*) desc
limit 1
Please try the following solutions based on replace, rtrim and potentially replicate
select rtrim(substr(replace(mycolumn,'(',replicate(' ',50)),1,50))
from mytable
group by rtrim(substr(replace(mycolumn,'(',replicate(' ',50)),1,50))
order by count(*) desc
limit 1
;
select rtrim(substr(replace(mycolumn,'(',' '),1,50))
from mytable
group by rtrim(substr(replace(mycolumn,'(',' '),1,50))
order by count(*) desc
limit 1
;

Oracle SQL regexp sum within repeating sequence grouped by

First time here, hope someone can help
What's the Oracle SQL select stmt for the XML below stored in an Oracle CLOB field so the following is returned (there will be as many as 96 intvColl blocks per day).
Basically the numeric values in the intvColl blocks between the 2nd and 3rd commas need to be summed and grouped by the date before the first comma and also by the varchar after the 3rd comma.
I'm guessing regexp_substr / but can't quite get there.
The first record is the sum of the 1st and 2nd intvColl blocks
The second record is the sum of the 3rd intvColl block
The third record is the sum of the 4th and 5th
MeterChannelID Date Sum Quality Count_of_records
6103044759-40011200-Q1 14/03/2016 1,387 A 2
6103044759-40011200-Q1 14/03/2016 694 S 1
6103044759-40011200-Q1 15/03/2016 1,433 A 2
<uploadRegData>
<intervalDataBlock>
<setDateTime>16/03/2016-19:30:01</setDateTime>
<intervalMinute>15</intervalMinute>
<meterChannelID>6103044759-40011200-Q1</meterChannelID>
<intvColl><intvData>14/03/2016,1,700,A</intvData></intvColl>
<intvColl><intvData>14/03/2016,2,687,A</intvData></intvColl>
<intvColl><intvData>14/03/2016,3,694,S</intvData></intvColl>
<intvColl><intvData>15/03/2016,4,724,A</intvData></intvColl>
<intvColl><intvData>15/03/2016,5,709,A</intvData></intvColl>
</intervalDataBlock>
</uploadRegData>
SELECT MeterChannelID,
"Date",
SUM( value ) AS "Sum",
Quality,
COUNT(1) AS Count_of_Records
FROM (
SELECT MeterChannelID,
TO_DATE( SUBSTR( data, 1, 10 ), 'DD/MM/YYYY' ) AS "Date",
TO_NUMBER( SUBSTR(
data,
INSTR( data, ',', 1, 2 ),
LENGTH( data ) - INSTR( data, ',', 1, 2 ) - 2
) ) AS value,
SUBSTR( data, -1 ) AS Quality
FROM (
SELECT EXTRACTVALUE( xml, '/uploadRegData/intervalDataBlock/meterChannelId' )
AS MeterChannelID,
EXTRACTVALUE( d.COLUMN_VALUE, '/intvData' ) AS data
FROM ( SELECT XMLType( column_name ) AS xml FROM table_name ) x,
TABLE(
XMLSequence(
EXTRACT(
x.xml,
'/uploadRegData/intervalDataBlock/intvCol1/intvData'
)
)
) d
)
)
GROUP BY MeterChannelID, "Date", Quality
ORDER BY MeterChannelID, "Date", Quality;