Helo,
I have 2 simple tables and I must make one query which group row "GRO" from PROD and return "gro" which is used most often by coid. I try use subquery but i have error, I dont know how to transform this query to work.
Please help me :)
I use Oracle SQL .
TABLE CO
ID |name|param|
---+----+-----+
1 |AA | X |
2 |BB | X |
3 |CC | X |
4 |DD | X |
5 |EE | |
Table PROD
id| coid |gro
--+------+------
1 | 1 | a
2 | 1 | a
3 | 1 | b
4 | 1 | c
5 | 1 | d
6 | 2 | b
7 | 2 | c
8 | 2 | c
9 | 3 | a
10| 3 | a
11| 4 | b
12| 4 | b
13| 4 | b
14| 4 | a
15| 1 | a
Result
ID |name|best_gro|
---+----+--------+
1 |AA | a |
2 |BB | c |
3 |CC | a |
4 |DD | b |
My Query
select c.id, c.name,
(
select gro from(
SELECT GRO, count(GRO) as m FROM HR.PROD pro
where coid = c.id --<------ ERROR
group by gro order by m desc
) where rownum <=1
) as best_gro
from HR.co c
where c.param = 'X'
You can't refer to your c column more than one level of subquery below where it is defined.
You can rewrite this with a join, using the analytic row_number() function, something like:
select id, name, gro
from (
select id, name, gro,
row_number() over (partition by id, name order by cnt desc) rnk
from (
select c.id, c.name, p.gro, count(*) as cnt
from HR.co c
join HR.prod p on p.coid = c.id
where c.param = 'X'
group by c.id, c.name, p.gro
)
)
where rnk = 1
Depending on what you want to do with ties - if two or more gro values have the same count for a compary - you need the order by to specify how to choose which to use; or use rank or dense_rank instead of row_number to get all tied values.
Or you can use the keep dense rank approach which will be shorter.
Thanks Alex,
i modified a little and i got correct result !
select id, name, gro from(
select id, name, gro,
rank() over (partition by coid order by cnt desc) rnk
from (
select c.id, c.name, p.gro, p.coid, count(*) as cnt
from HR.co c
join HR.prod p on p.coid = c.id
where c.param = 'X'
group by c.id, c.name, p.gro, p.coid
)
)
where rnk = 1
Related
Given the below data set I want to run a query to highlight any 'pairs' that do not consist of a 'left' and 'right'.
+---------+-----------+---------------+----------------------+
| Pair_Id | Pair_Name | Individual_Id | Individual_Direction |
+---------+-----------+---------------+----------------------+
| 1 | A | A1 | Left |
| 1 | A | A2 | Right |
| 2 | B | B1 | Right |
| 2 | B | B2 | Left |
| 3 | C | C1 | Left |
| 3 | C | C2 | Left |
| 4 | D | D1 | Right |
| 4 | D | D2 | Left |
| 5 | E | E1 | Left |
| 5 | E | E2 | Right |
+---------+-----------+---------------+----------------------+
In this instance Pair 3 'C' has two lefts. Therefore, I would look to display the following:
+---------+-----------+---------------+----------------------+
| Pair_Id | Pair_Name | Individual_Id | Individual_Direction |
+---------+-----------+---------------+----------------------+
| 3 | C | C1 | Left |
| 3 | C | C2 | Left |
+---------+-----------+---------------+----------------------+
You can simply use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t2.pair_id = t.pair_id and
t2.Individual_Direction <> t.Individual_Direction
) ;
With an index on (pair_id, Individual_Direction), this should not only be the most concise solution but also the fastest.
If you want to be sure that there are pairs (the above returns singletons):
select t.*
from t
where not exists (select 1
from t t2
where t2.pair_id = t.pair_id and
t2.Individual_Direction <> t.Individual_Direction
) and
exists (select 1
from t t2
where t2.pair_id = t.pair_id and
t2.Individual_ID <> t.Individual_ID
);
You can also do this using window functions:
select t.*
from (select t.*,
count(*) over (partition by pair_id) as cnt,
min(status) over (partition by pair_id) as min_status,
max(status) over (partition by pair_id) as max_status
from t
) t
where cnt > 1 and min_status <> max_status;
One option uses aggregation:
WITH cte AS (
SELECT Pair_Name
FROM yourTable
WHERE Individual_Direction IN ('Left', 'Right')
GROUP BY Pair_Name
HAVING MIN(Individual_Direction) = MAX(Individual_Direction)
)
SELECT *
FROM yourTable
WHERE Pair_Name IN (SELECT Pair_Name FROM cte);
The HAVING clause used above asserts that a matching pair has both a minimum and maximum direction which are the same. This implies that such a pair only has one direction.
As is the case with Gordon's answer, an index on (Pair_Name, Individual_Direction) might help performance:
CREATE INDEX idx ON yourTable (Pair_Name, Individual_Direction);
There should be an elegant way of using window function than what I wrote:
WITH ranked AS
(
SELECT *, RANK() OVER(ORDER BY Pair_Id, Pair_Name, Individual_Direction) AS r
FROM pairs
),
counted AS
(
SELECT Pair_Id, Pair_Name, Individual_Direction,r, COUNT(r) as times FROM ranked
GROUP BY Pair_Id, Pair_Name, Individual_Direction, r
HAVING COUNT(r) > 1
)
SELECT ranked.Pair_Id, ranked.Pair_Name, ranked.Individual_Id, ranked.Individual_Direction FROM ranked
RIGHT JOIN counted
ON ranked.Pair_Id=counted.Pair_Id
AND ranked.Pair_Name=counted.Pair_Name
AND ranked.Individual_Direction=counted.Individual_Direction
Given the following example table:
+-----------+
| Id | Name |
+----+------+
| 1 | A |
| 2 | B |
| 3 | B |
| 4 | C |
| 5 | A |
| 6 | B |
| 7 | B |
| 8 | B |
| 9 | B |
| 10 | X |
+----+------+
I would like a query to get the following result:
+----+------+
| 6 | B |
| 7 | B |
| 8 | B |
| 9 | B |
+----+------+
The best query I could do was:
SELECT * FROM
(SELECT id, name, LEAD(id) OVER (ORDER BY id) t
FROM test WHERE name = 'B' ORDER BY id)
WHERE ID <> t-1;
sqlfiddle here
If you want the length and where it starts:
select min(id), max(id)
from (select t.*,
row_number() over (order by id) as seqnum,
row_number() over (partition by name order by id) as seqnum_1
from test t
) t
where name = 'B'
group by (seqnum - seqnum_1)
order by min(id) desc
fetch first 1 row only;
You can join back to the table to get the original rows.
Another method using window functions to count the number of non-Bs after a given row . . . and then choose the first:
select t.*
from (select t.*,
dense_rank() over (order by nonbs_after asc) as grp
from (select t.*,
sum(case when name <> 'B' then 1 else 0 end) over (order by id desc) as nonbs_after
from test t
) t
where name = 'B'
) t
where grp = 1;
Here is a db<>fiddle.
I'm using ROW_NUMBER and I'm trying to compare arr in rn 1 to arr in rn 2,3,4,etc
to see if they overlap. I can do this with a subquery / simple join. Is there a way that AVOIDS a join?
rn | id | job | arr |desired_result
---+----+-----+--------+---------
1 | 1 | 100 | {1,2} | {1,2}
2 | 1 | 101 | {2,3} | {1,2}
3 | 1 | 102 | {5,6,8}| {1,2}
4 | 1 | 103 | {2,7} | {1,2}
I made a dbfiddle
--USING JOIN
WITH a AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY id ORDER by job) as rn
,*
FROM a_table
)
SELECT *
FROM (
SELECT id,arr
FROM a
WHERE rn = 1
) x
JOIN a
ON a.id=x.id
You can use first_value():
SELECT a.*, first_value(arr) over (partition by id order by job)
FROM a_table a;
row_number() does not seem necessary.
I have a table A as below
id| Name|Subject
--|-----|-------
1 |Mano |Science
2 |Pavi |Maths
3 |Mano |Social
1 |Kalai|Maths
4 |Kalai|Science
I want distinct values for each column.
So My output be like
id|Name | Subject
--|-----|--------
1 |Mano |Science
2 |Pavi |Maths
3 |Kalai|Social
4 | |
I have tried using cursors. But I didn't get what I needed.
Anybody help me in getting this
You seem to just want a list of the distinct values, without regards to what appears together. This isn't very SQL'ish, but can be done:
select row_number() over (order by n.seqnum) as firstcol, n.name, s.subject
from (select name, row_number() over (order by name) as seqnum
from t
group by name
) n full outer join
(select subject, row_number() over (order by subject) as seqnum
from t
group by subject
) s
on s.seqnum = n.seqnum;
select *
from (select col,val,dense_rank () over (partition by col order by val) as dr
from mytable unpivot (val for col in (name,subject)) u
) pivot (min(val) for col in ('NAME','SUBJECT'))
order by dr
+----+-------+---------+
| DR | NAME | SUBJECT |
+----+-------+---------+
| 1 | Kalai | Maths |
| 2 | Mano | Science |
| 3 | Pavi | Social |
+----+-------+---------+
Say I have this table, and I want to select the IDs where all D is < 4. In this case it would only select ID 1 because 2's D>4, and 3 has a D>4
+----+---+------+
| ID | D | U-ID |
+----+---+------+
| 1 | 1 | a |
+----+---+------+
| 1 | 2 | b |
+----+---+------+
| 2 | 5 | c |
+----+---+------+
| 3 | 5 | d |
+----+---+------+
| 3 | 2 | e |
+----+---+------+
| 3 | 3 | f |
+----+---+------+
I really don't even know where to start making a query for this, and my sql isn't good enough yet to know what to google, so I'm sorry if this has been asked before.
I would simply do:
select id
from table
group by id
having max(d) < 4;
If you happened to want all the original rows, I would use a window function:
select t.*
from (select t.*, max(d) over (partition by id) as maxd
from t
) t
where maxd < 4;
Here's one option using conditional aggregation:
select id
from yourtable
group by id
having count(case when d >= 4 then 1 end) = 0
SQL Fiddle Demo
If you need all the data from the corresponding rows/columns, you can either join back to the table using the above, or alternatively you could use not exists:
select *
from yourtable t
where not exists (
select 1
from yourtable t2
where t.id = t2.id and
t2.d >= 4
)
use this query.
select ID from yourtablename where D < 4;