Maintaining the order by in union of two ordered by queries - sql

I am trying to run the below query but looks like I am doing something wrong.
(Just modified the sample query for clear understanding)
SELECT name,total,rate
FROM business b
WHERE rate > 100
ORDER BY total DESC
UNION
SELECT name,total,rate
FROM business b
WHERE rate <= 100
ORDER BY rate ASC
Now I want to union of these two queries and in the resultant output in first row the output should come from first query and then output from second query in the same sorted order however the single actual query was giving.
Let me know if it is still unclear. I will try to explain in some more deep level.

It's really simple: Use UNION ALL instead of UNION.
SELECT * FROM (
SELECT name,total,rate
FROM business b
WHERE rate > 100
ORDER BY total DESC) x
UNION ALL
SELECT * FROM (
SELECT name,total,rate
FROM business b
WHERE rate <= 100
ORDER BY rate ASC) y
UNION ALL preserves order as coded.
UNION removes duplicates and does not guarantee order. Most databases actually sort the output (to make duplicate detection easier).

Try this:
select name,total,rate
from (
SELECT name,total,rate, row_number() over(order by total desc) rn, 1 ord
FROM business b
WHERE rate > 100
UNION
SELECT name,total,rate, row_number() over(ORDER BY rate ASC) rn, 2 ord
FROM business b
WHERE rate <= 100
)
order by ord, rn

Add a branch flag in each of the inner branches, and sort by that first:
SELECT * FROM
(
SELECT * FROM
(
SELECT 'd' AS char1 , 2 AS sortcol, 1 AS branch FROM dual
UNION
SELECT 'c' AS char1 , 1 AS sortcol, 1 AS branch FROM dual
ORDER BY sortcol
) Inner1
UNION
SELECT * FROM
(
SELECT 'a' AS char1 , 1 AS sortcol, 2 as branch FROM dual
UNION
SELECT 'b' AS char1 , 2 AS sortcol, 2 as branch FROM dual
ORDER BY sortcol
) Inner2
) Outer
ORDER BY Outer.branch, Outer.SORTCOL;
CHAR1 SORTCOL BRANCH
----- ---------- ----------
c 1 1
d 2 1
a 1 2
b 2 2
Obviously you can replace * in the outer query to only get the columns you want in the result set, excluding both branch and sortcol.
SQL Fiddle with both queries for comparison.

EDIT: Try conditional OR clause as here. This will also avoid unions.
SELECT NAME, TOTAL, RATE FROM BUSINESS B
ORDER BY
CASE
WHEN rate > 100 THEN total
WHEN rate <= 100 THEN rate
END
Instead of using multiple layers of select query, Just include an additional field called TAB_ID in your select query and sort using that field as below:
SELECT CHAR1 FROM
(
SELECT 'd' AS char1 , 2 AS sortcol, '1' AS TAB_ID FROM dual
UNION
SELECT 'c' AS char1 , 1 AS sortcol, '1' AS TAB_ID FROM dual
UNION
SELECT 'a' AS char1 , 1 AS sortcol, '2' AS TAB_ID FROM dual
UNION
SELECT 'b' AS char1 , 2 AS sortcol, '2' AS TAB_ID FROM dual
) ORDER BY TAB_ID, SORTCOL;
Check the result out from Working SQL Fiddle Here
To correct your original query and avoid the missing paranthesis error, use below query, but it does not seem to provide the right output:
select * from
(
SELECT * FROM
(
SELECT 'd' AS char1 , 2 AS sortcol FROM dual
UNION
SELECT 'c' AS char1 , 1 AS sortcol FROM dual
) Inner1
ORDER BY Inner1.sortcol
)
UNION
select * from
(
SELECT * FROM
(
SELECT 'a' AS char1 , 1 AS sortcol FROM dual
UNION
SELECT 'b' AS char1 , 2 AS sortcol FROM dual
) Inner2
ORDER BY Inner2.sortcol
) ;

Related

Running count but reset on some column value in select query

I want to achieve a running value, but condition is reset on some specific column value.
Here is my select statement:
with tbl(emp,salary,ord) as
(
select 'A',1000,1 from dual union all
select 'B',1000,2 from dual union all
select 'K',1000,3 from dual union all
select 'A',1000,4 from dual union all
select 'B',1000,5 from dual union all
select 'D',1000,6 from dual union all
select 'B',1000,7 from dual
)
select * from tbl
I want to reset count on emp B if the column value is B, then count is reset to 0 and started again increment by 1:
emp salary ord running_count
A 1000 1 0
B 1000 2 1
K 1000 3 0
A 1000 4 1
B 1000 5 2
D 1000 6 0
B 1000 7 1
Here order column is ord.
I want to achieve the whole thing by select statement, not using the cursor.
You want to define groups were the counting takes place. Within a group, the solution is row_number().
You can define the group by doing a cumulative sum of B values. Because B ends the group, you want to count the number of B after each record.
This results in:
select t.*,
row_number() over (partition by grp order by ord) - 1 as running_count
from (select t.*,
sum(case when emp = 'B' then 1 else 0 end) over (order by ord desc) as grp
from tbl t
) t;

select rows between two character values of a column

I have a table which shows as below:
S.No | Action
1 | New
2 | Dependent
3 | Dependent
4 | Dependent
5 | New
6 | Dependent
7 | Dependent
8 | New
9 | Dependent
10 | Dependent
I here want to select the rows between the first two 'New' values in the Action column, including the first row with the 'New' action. Like [New,New)
For example:
In this case, I want to select rows 1,2,3,4.
Please let me know how to do this.
Hmmm. Let's count up the cumulative number of times that New appears as a value and use that:
select t.*
from (select t.*,
sum(case when action = 'New' then 1 else 0 end) over (order by s_no) as cume_new
from t
) t
where cume_new = 1;
you can do some magic with analytic functions
1 select group of NEW actions, to get min and max s_no
2 select lead of 2 rows
3 select get between 2 sno (min and max)
with t as (
select 1 sno, 'New' action from dual union
select 2,'Dependent' from dual union
select 3,'Dependent' from dual union
select 4,'Dependent' from dual union
select 5,'New' from dual union
select 6,'Dependent' from dual union
select 7,'Dependent' from dual union
select 8,'New' from dual union
select 9,'Dependent' from dual union
select 10,'Dependent' from dual
)
select *
from (select *
from (select sno, lead(sno) over (order by sno) a
from ( select row_number() over (partition by action order by Sno) t,
t.sno
from t
where t.action = 'New'
) a
where t <=2 )
where a is not null) a, t
where t.sno >= a.sno and t.sno < a.a

SQL Grouping by Ranges

I have a data set that has timestamped entries over various sets of groups.
Timestamp -- Group -- Value
---------------------------
1 -- A -- 10
2 -- A -- 20
3 -- B -- 15
4 -- B -- 25
5 -- C -- 5
6 -- A -- 5
7 -- A -- 10
I want to sum these values by the Group field, but parsed as it appears in the data. For example, the above data would result in the following output:
Group -- Sum
A -- 30
B -- 40
C -- 5
A -- 15
I do not want this, which is all I've been able to come up with on my own so far:
Group -- Sum
A -- 45
B -- 40
C -- 5
Using Oracle 11g, this is what I've hobbled togther so far. I know that this is wrong, by I'm hoping I'm at least on the right track with RANK(). In the real data, entries with the same group could be 2 timestamps apart, or 100; there could be one entry in a group, or 100 consecutive. It does not matter, I need them separated.
WITH SUB_Q AS
(SELECT K_ID
, GRP
, VAL
-- GET THE RANK FROM TIMESTAMP TO SEPARATE GROUPS WITH SAME NAME
, RANK() OVER(PARTITION BY K_ID ORDER BY TMSTAMP) AS RNK
FROM MY_TABLE
WHERE K_ID = 123)
SELECT T1.K_ID
, T1.GRP
, SUM(CASE
WHEN T1.GRP = T2.GRP THEN
T1.VAL
ELSE
0
END) AS TOTAL_VALUE
FROM SUB_Q T1 -- MAIN VALUE
INNER JOIN SUB_Q T2 -- TIMSTAMP AFTER
ON T1.K_ID = T2.K_ID
AND T1.RNK = T2.RNK - 1
GROUP BY T1.K_ID
, T1.GRP
Is it possible to group in this way? How would I go about doing this?
I approach this problem by defining a group which is the different of two row_number():
select group, sum(value)
from (select t.*,
(row_number() over (order by timestamp) -
row_number() over (partition by group order by timestamp)
) as grp
from my_table t
) t
group by group, grp
order by min(timestamp);
The difference of two row numbers is constant for adjacent values.
A solution using LAG and windowed analytic functions:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( "Timestamp", "Group", Value ) AS
SELECT 1, 'A', 10 FROM DUAL
UNION ALL SELECT 2, 'A', 20 FROM DUAL
UNION ALL SELECT 3, 'B', 15 FROM DUAL
UNION ALL SELECT 4, 'B', 25 FROM DUAL
UNION ALL SELECT 5, 'C', 5 FROM DUAL
UNION ALL SELECT 6, 'A', 5 FROM DUAL
UNION ALL SELECT 7, 'A', 10 FROM DUAL;
Query 1:
WITH changes AS (
SELECT t.*,
CASE WHEN LAG( "Group" ) OVER ( ORDER BY "Timestamp" ) = "Group" THEN 0 ELSE 1 END AS hasChangedGroup
FROM TEST t
),
groups AS (
SELECT "Group",
VALUE,
SUM( hasChangedGroup ) OVER ( ORDER BY "Timestamp" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS grp
FROM changes
)
SELECT "Group",
SUM( VALUE )
FROM Groups
GROUP BY "Group", grp
ORDER BY grp
Results:
| Group | SUM(VALUE) |
|-------|------------|
| A | 30 |
| B | 40 |
| C | 5 |
| A | 15 |
This is typical "star_of_group" problem (see here: https://timurakhmadeev.wordpress.com/2013/07/21/start_of_group/)
In your case, it would be as follows:
with t as (
select 1 timestamp, 'A' grp, 10 value from dual union all
select 2, 'A', 20 from dual union all
select 3, 'B', 15 from dual union all
select 4, 'B', 25 from dual union all
select 5, 'C', 5 from dual union all
select 6, 'A', 5 from dual union all
select 7, 'A', 10 from dual
)
select min(timestamp), grp, sum(value) sum_value
from (
select t.*
, sum(start_of_group) over (order by timestamp) grp_id
from (
select t.*
, case when grp = lag(grp) over (order by timestamp) then 0 else 1 end
start_of_group
from t
) t
)
group by grp_id, grp
order by min(timestamp)
;

Oracle SUM returns false summary with identicals values returing from an SELECT UNION

I am facing a problem with SUM statement.
This query returns MY_ID = 1 and QTY = 7
select my_id, sum(qty) qty
from
(
select 1 my_id ,2 qty from dual
union
select 1 my_id, 5 qty from dual
)
group by my_id;
But this one returns MY_ID = 1 and QTY = 5 instead of QTY = 10.
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union
select 1 my_id, 5 qty from dual
)
group by my_id;
How can I summary the two quantity in case of the two values are the same?
Use union all:
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union all
select 1 my_id, 5 qty from dual
)
group by my_id;
Try using union all:
The below works:
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union all
select 1 my_id, 5 qty from dual
)
group by my_id;
this is because 5 union 5 is always 5. if you do union all it includes everything irrespective of it being the same!
In the second query, the two rows in the union are identical.
There are two forms of UNION: UNION ALL and UNION DISTINCT. Which one is the default varies, but it looks like you're getting a UNION DISTINCT, which since the two (1, 5) rows are the same is only returning one of them. Change it to:
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union ALL
select 1 my_id, 5 qty from dual
)
group by my_id;
That should give you what you want: (1, 10).
EDIT: Briefly I had union DISTINCT in the query which was wrong! Now corrected....

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.