Oracle - Order by Alpha numeric - sql

I need to order the rows in my result set by a column that holds varchar2 K-12.
Example:
ID Grade Expense
1 1 500
1 10 500
1 11 500
1 12 500
1 2 500
1 3 500
1 4 500
1 5 500
1 6 500
1 7 500
1 8 500
1 9 500
1 K 500
This is my order by clause which works, but I would like to have the
row with Grade = K as the first row for each ID in my result set.
order by ID, to_number(regexp_substr(grade, '^[[:digit:]]*'))
As it stands, the result set has the row with ID = K is last and not
first. How can i make it the first row for each ID in my result set?
ID Grade Expense
1 K 500
1 1 500
1 2 500
1 3 500
1 4 500
1 5 500
1 6 500
1 7 500
1 8 500
1 9 500
1 10 500
1 11 500
1 12 500
Thanks in advance

Simply use a case statement to set K to something below 1. this has the advantage if you have a Pre-K later, you can modify the case to handle it as well.
With CTE as
(SELECT '1' as grade from dual union
SELECT '2' from dual union
select '10' from dual union
select 'K' from dual)
SELECT * FROM CTE
ORDER BY CASE GRADE when 'K' then -1
else to_number(regexp_substr(grade, '^[[:digit:]]*')) end

This is a bit of a kludge, but since the regex for 'K' returns null, change the order by to:
order by ID, nvl(to_number(regexp_substr(grade, '^[[:digit:]]*')),0)
This will return 0 for 'K' and sort it properly.

You can do the following:
WITH g1 AS (
SELECT 1 AS id, TO_CHAR(level) AS grade, 500 AS expense FROM dual
CONNECT BY level <= 12
UNION
SELECT 1, 'K', 500 FROM dual
UNION
SELECT 1, 'J', 500 FROM dual
)
SELECT g1.*, TO_NUMBER(REGEXP_SUBSTR(grade, '^\d+'))
, DECODE(grade, 'K', -1, TO_NUMBER(REGEXP_SUBSTR(grade, '^\d+')))
FROM g1
ORDER BY DECODE(grade, 'K', -1, TO_NUMBER(REGEXP_SUBSTR(grade, '^\d+'))) NULLS LAST
In this query I'm using CONNECT BY to build your grade table; of course you'll want to ignore that part. Note I added an extra row with a J for the grade level.
In my order by I am using DECODE() so that if grade = 'K', it will give a value of -1. For any grades that can be converted to numeric values (that is, if they start with at least one digit), I use a regex to get as many digits if possible (you can use [:digit:] or [0-9] in place of \d; but \d is nice and short).
I am specifying NULLS LAST so that any rows for which grade cannot be converted to a number, other than K, will be last.
I'm including the extra computed columns just to give a glimpse into what is actually going on and how the values are generated. They aren't needed for the query.
Please see SQL Fiddle demo here.

Only change the ORDER BY clause, this way:
order by ID asc, decode(grade,'K',-1,grade) asc

Related

select case when number is distinct then 1 else 0

I want to get the expected output such that when number happens first time in the account group, then display 1, otherwise if it happens another time, display null or 0. The same logic for other account groups.
The logic I can think is
select *,
case when number happens first time then 1 else null
over (partition by account order by number) from table.
account number expected output
abc 20 1
abc 20 0
abc 30 1
def 20 1
def 30 1
def 30 0
use lag
select *,case when number=lag(number) over(partition by account order by account)
then 0 else 1 end as val
from table_name
You almost had it!
select account, number
case when Row_Number() OVER (partition by account order by number) = 1 THEN 1 END ExpOut
from table
#PaulX's answer is close, but the partitioning isn't quite right. You can do:
-- CTE for sample data
with your_table (account, num) as (
select 'abc', 20 from dual
union all select 'abc', 20 from dual
union all select 'abc', 30 from dual
union all select 'def', 20 from dual
union all select 'def', 30 from dual
union all select 'def', 30 from dual
)
select account, num,
case when row_number() over (partition by account, num order by null) = 1
then 1
else 0
end as output
from your_table;
ACCOUNT NUM OUTPUT
------- ---------- ----------
abc 20 1
abc 20 0
abc 30 1
def 20 1
def 30 1
def 30 0
(adjusted for legal column names; hopefully you don't actually have quoited identifiers...)
If you want nulls rather than zeros then just leave out the else 0 part. And this just assumes by 'first' you mean the first returned in your result set, as otherwise - at least with the columns you showed - there is no obvious alternative. If you actually have other columns, particularly if you're using any others for the ordering of the result set, then you can apply the same ordering inside the partition clause to make it consistent.

Merging rows in SQL (Oracle)

I'm looking to run an UPDATE, and a subsequent DELETE if necessary, query on my table (let's call it MY_TABLE) that would merge all rows in the following way.
Input Table
ID LowerRange UpperRange Attribute
1 10 20 A
2 20 30 A
3 40 50 A
4 15 35 B
Output table
ID LowerRange UpperRange Attribute
1 10 30 A
3 40 50 A
4 15 35 B
Notice how...
Rows 1 & 2 of the Input Table are merged into Row 1 of the Output Table because their ranges overlap and they have the same Attribute.
Row 3 of the Input Table is not merged with Rows 1 & 2 because their ranges don't overlap, despite them having the same Attribute.
Row 4 of the Input Table is not merged with Rows 1 & 2 because they don't have the same Attribute, despite having overlapping ranges.
All rows in TABLE would be merged where their ranges overlap and they have the same Attribute.
Let me know if you have any questions. Any help would be greatly appreciated.
Thanks,
Stephen.
Here's one way (assuming that you could have overlapping rows where the start range of the current row is less than or equal to the end range of the previous row):
with sample_data as (select 1 id, 10 lower_range, 20 upper_range, 'A' attribute from dual union all
select 2 id, 20 lower_range, 30 upper_range, 'A' attribute from dual union all
select 3 id, 40 lower_range, 50 upper_range, 'A' attribute from dual union all
select 4 id, 15 lower_range, 35 upper_range, 'B' attribute from dual union all
select 5 id, 45 lower_range, 55 upper_range, 'A' attribute from dual union all
select 6 id, 16 lower_range, 34 upper_range, 'B' attribute from dual)
select min(id) id,
min(lower_range) lower_range,
max(upper_range) upper_range,
attribute
from (select id,
lower_range,
upper_range,
attribute,
sum(diff) over (partition by attribute order by lower_range, upper_range) grp
from (select id,
lower_range,
upper_range,
attribute,
case when lag(upper_range, 1, lower_range) over (partition by attribute order by lower_range, upper_range) >= lower_range then 0 else 1 end diff
from sample_data))
group by attribute, grp;
ID LOWER_RANGE UPPER_RANGE ATTRIBUTE
---------- ----------- ----------- ---------
1 10 30 A
3 40 55 A
4 15 35 B
If your rows only overlap when the previous upper_range is the same as the current lower_range, then just remove the > from the case statement.
What this does is see if the lower_range of the current row is greater than or equal to the previous row's upper_range. If it is, then we set the result to be 0, otherwise we'll set it to be 1 (which indicates that there is a gap between the two rows).
Next, we then perform a cumulative sum across all the rows per attribute. This then will have the same result for rows that overlap, and will increase by 1 every time it comes across a gap.
Now we can use this along with the attribute column to group the rows and find their min/max ranges along with the min(id).
Check this out: Merge data in two row into one

how to assign a rank for non null values in oracle

I need to assign a rank in such a way that it ignores null value.
select root_cause_desc,
case
when root_cause_desc is null
then null
else rank() over ( order by excess_value desc)
end gap_rank
from table.
where root_cause_desc is not null
gives
ROOT_CAUSE_DESC EXCESS_VALUE TOTAL_EXCESS_VALUE_WK GAP_RANK
advanced shipment 120.9750138 -760356.4054 10
dfdfdfdf222 0 -1696000.946 11
Root Cause -0.0760554 -760356.4054 12
test one more -656.277192 -760356.4054 13
earlier truck -77099.35 720093.3712 14
It ignores the null value and assign it the rank even for null root cause. I want gap_rank as 1,2,3,4. Please let me now how to do this.
The problem is that RANK() is independent of your case statement; it's ordering the entire query by the ORDER BY clause you give it.
Utilise the NULLS LAST keywords to put the NULL values at then end of the order and then your CASE statement will work. For instance:
with the_data as (
select level as a
, nullif(nullif(level, 5), 8) as b
from dual
connect by level <= 10
)
select a
, b
, case when b is null then null
else rank() over ( order by case when b is not null then 1
end nulls last
, a )
end as "rank"
from the_data
order by a;
A B rank
---------- ---------- ----------
1 1 1
2 2 2
3 3 3
4 4 4
5
6 6 5
7 7 6
8
9 9 7
10 10 8
10 rows selected.
SQL Fiddle
I think there is not need to put a check of root_cause_desc is null in the select clause.
The where ,group by order by clause executes first ,then the analytical function is processed.So ,before processing your rank , it will eliminates the null root_cause_desc.
WITH tab
AS (SELECT NULL root_cause, 5 AS val FROM DUAL
UNION ALL
SELECT 'A' root_cause, 1 AS val FROM DUAL
UNION ALL
SELECT NULL root_cause, 4 AS val FROM DUAL
UNION ALL
SELECT 'A' root_cause, 2 AS val FROM DUAL
UNION ALL
SELECT NULL root_cause, 3 AS val FROM DUAL)
SELECT root_cause, val, RANK () OVER (ORDER BY val DESC) rnk
FROM tab
WHERE root_cause IS NOT NULL;
root_casue val rnk
=========================
A 2 1
A 1 2
===========================

one sql instead of 2 for counting

I have read a thread on this but when I tried it I can`t manage to make it work.
I want to count all the male and females from a table like so:
Select
count(case when substr(id,1, 1) in (1,2) then 1 else 0 end) as M,
count(case when substr(id,1, 1) in (3,4) then 1 else 0 end) as F
from users where activated=1
The ideea is that a user having an id starting with 1 or 2 is male
My table has 3 male entries and 2 are activated and it returns (the case statement doesn`t work)
M,F
2,2
Any input would be appreciated
id activated
123 1
234 0
154 1
You should use SUM instead. COUNT will count all non null values.
Select
SUM(case when substr(id,1, 1) in (1,2) then 1 else 0 end) as M,
SUM(case when substr(id,1, 1) in (3,4) then 1 else 0 end) as F
from users where activated=1
COUNT will give you the number of non-null values, whatever they are. Try SUM instead.
If your Oracle version is 10g or later, as an alternative, you can use regexp_count function. I assume that the ID column is of number data type, so in the example it explicitly converted to varchar2 data type using TO_CHAR function. If the data type of the ID column is varchar2 or char then there is no need of any type of data type conversion.
Here is an example:
SQL> create table M_F(id, activated) as(
2 select 123, 1 from dual union all
3 select 234, 0 from dual union all
4 select 434, 1 from dual union all
5 select 154, 1 from dual
6 );
Table created
SQL> select sum(regexp_count(to_char(id), '^[12]')) as M
2 , sum(regexp_count(to_char(id), '^[34]')) as F
3 from M_F
4 where activated = 1
5 ;
M F
---------- ----------
2 1
Demo

How to transpose recordset columns into rows

I have a query whose code looks like this:
SELECT DocumentID, ComplexSubquery1 ... ComplexSubquery5
FROM Document
WHERE ...
ComplexSubquery are all numerical fields that are calculated using, duh, complex subqueries.
I would like to use this query as a subquery to a query that generates a summary like the following one:
Field DocumentCount Total
1 dc1 s1
2 dc2 s2
3 dc3 s3
4 dc4 s4
5 dc5 s5
Where:
dc<n> = SUM(CASE WHEN ComplexSubquery<n> > 0 THEN 1 END)
s <n> = SUM(CASE WHEN Field = n THEN ComplexSubquery<n> END)
How could I do that in SQL Server?
NOTE: I know I could avoid the problem by discarding the original query and using unions:
SELECT '1' AS TypeID,
SUM(CASE WHEN ComplexSubquery1 > 0 THEN 1 END) AS DocumentCount
SUM(ComplexSubquery1) AS Total
FROM (SELECT DocumentID, BLARGH ... AS ComplexSubquery1) T
UNION ALL
SELECT '2' AS TypeID,
SUM(CASE WHEN ComplexSubquery2 > 0 THEN 1 END) AS DocumentCount
SUM(ComplexSubquery2) AS Total
FROM (SELECT DocumentID, BLARGH ... AS ComplexSubquery2) T
UNION ALL
...
But I want to avoid this route, because redundant code makes my eyes bleed. (Besides, there is a real possibility that the number of complex subqueries grow in the future.)
WITH Document(DocumentID, Field) As
(
SELECT 1, 1 union all
SELECT 2, 1 union all
SELECT 3, 2 union all
SELECT 4, 3 union all
SELECT 5, 4 union all
SELECT 6, 5 union all
SELECT 7, 5
), CTE AS
(
SELECT DocumentID,
Field,
(select 10) As ComplexSubquery1,
(select 20) as ComplexSubquery2,
(select 30) As ComplexSubquery3,
(select 40) as ComplexSubquery4,
(select 50) as ComplexSubquery5
FROM Document
)
SELECT Field,
SUM(CASE WHEN RIGHT(Query,1) = Field AND QueryValue > 1 THEN 1 END ) AS DocumentCount,
SUM(CASE WHEN RIGHT(Query,1) = Field THEN QueryValue END ) AS Total
FROM CTE
UNPIVOT (QueryValue FOR Query IN
(ComplexSubquery1, ComplexSubquery2, ComplexSubquery3,
ComplexSubquery4, ComplexSubquery5)
)AS unpvt
GROUP BY Field
Returns
Field DocumentCount Total
----------- ------------- -----------
1 2 20
2 1 20
3 1 30
4 1 40
5 2 100
I'm not 100% positive from your example, but perhaps the PIVOT operator will help you out here? I think if you selected your original query into a temporary table, you could pivot on the document ID and get the sums for the other queries.
I don't have much experience with it though, so I'm not sure how complex you can get with your subqueries - you might have to break it down.