I have a table that looks like this:
COL ID_1 FOR A4
COL ID_2 FOR A4
COL ID_3 FOR A4
SELECT 'A1' ID_1, 'B1' ID_2, 10 NUM, 'X' ID_3 FROM DUAL UNION ALL
SELECT 'A1' ID_1, 'B2' ID_2, 20 NUM, 'Y' ID_3 FROM DUAL UNION ALL
SELECT 'A1' ID_1, 'B3' ID_2, 30 NUM, 'Z' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B1' ID_2, 1 NUM, 'Q' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B2' ID_2, 2 NUM, 'W' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B3' ID_2, 3 NUM, 'E' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B4' ID_2, 4 NUM, 'R' ID_3 FROM DUAL;
ID_1 ID_2 NUM ID_3
---- ---- ---- ----
A1 B1 10 X
A1 B2 20 Y
A1 B3 30 Z
C1 B1 1 Q
C1 B2 2 W
C1 B3 3 E
C1 B4 4 R
7 rows selected.
I want to join rows with minimum and maximum values on the NUM column. Result set grouped by ID_1 column. E.g.:
ID_1 ID_2 NUM ID_3
---- ---- ---- ----
A1 B1 10 X <-- A1: THIS MIN(NUM)
A1 B2 20 Y
A1 B3 30 Z <-- A1: THIS MAX(NUM)
C1 B1 1 Q <-- C1: THIS MIN(NUM)
C1 B2 2 W
C1 B3 3 E
C1 B4 4 R <-- C1: THIS MAX(NUM)
7 rows selected.
The expected result set is
A1 B1 10 X B3 30 Z
C1 B1 1 Q B4 4 R
Please advise how to achieve the desired result.
You can do it without any self-joins using aggregation functions with KEEP (DENSE_RANK [FIRST|LAST] ORDER BY ...):
SELECT id_1,
MIN(id_2) KEEP (DENSE_RANK FIRST ORDER BY num) AS min_id_2,
MIN(num) AS min_num,
MIN(id_3) KEEP (DENSE_RANK FIRST ORDER BY num, id_2) AS min_id_3,
MAX(id_2) KEEP (DENSE_RANK LAST ORDER BY num) AS max_id_2,
MAX(num) AS max_num,
MAX(id_3) KEEP (DENSE_RANK LAST ORDER BY num, id_2) AS max_id_3
FROM table_name
GROUP BY id_1;
Which, for the sample data, outputs:
ID_1
MIN_ID_2
MIN_NUM
MIN_ID_3
MAX_ID_2
MAX_NUM
MAX_ID_3
A1
B1
10
X
B3
30
Z
C1
B1
1
Q
B4
4
R
db<>fiddle here
Something like this:
with cte as (
select
id_1, id_2, num, id_3,
row_number() over (partition by id_1 order by num) as rn1,
row_number() over (partition by id_1 order by num desc) as rn2
from t
)
select
cte1.id_1,
cte1.id_2, cte1.num, cte1.id_3,
cte2.id_2, cte2.num, cte2.id_3
from cte cte1
join cte cte2 on cte1.id_1 = cte2.id_1 and cte2.rn2 = 1
where cte1.rn1 = 1
Related
I have this query that are a number of positions that goes from A1 to A100, then B1 to B100, and so on.
I want to order this query so that the positions appear alphabetically in ascending order, but when it reaches the next letter, it goes descending, and keep this until the end of the table.
Here is an example.
SELECT
POSITION
FROM POSITION_TABLE
-------------------
POSITION
--------
A1
A2
...
A99
A100
B100
B99
B98
...
B2
B1
C1
C2
..
How can I do this ordering inside a query?
I tried ordering by the position using CASE, but I wanted something more generic, in case I needed to increase in size.
You can use:
SELECT position
FROM (
SELECT position,
REGEXP_SUBSTR(position, '^\D+') AS leading_string,
CASE MOD(DENSE_RANK() OVER (ORDER BY REGEXP_SUBSTR(position, '^\D+')), 2)
WHEN 1
THEN ROW_NUMBER() OVER (
PARTITION BY REGEXP_SUBSTR(position, '^\D+')
ORDER BY TO_NUMBER(REGEXP_SUBSTR(position, '\d+$')) ASC
)
ELSE ROW_NUMBER() OVER (
PARTITION BY REGEXP_SUBSTR(position, '^\D+')
ORDER BY TO_NUMBER(REGEXP_SUBSTR(position, '\d+$')) DESC
)
END AS rn
FROM position_table
)
ORDER BY
leading_string,
rn;
Which, for the sample data:
CREATE TABLE position_table (POSITION) AS
SELECT 'A' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10 UNION ALL
SELECT 'B' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10 UNION ALL
SELECT 'C' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10 UNION ALL
SELECT 'D' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10;
Outputs:
POSITION
A1
A2
A3
...
A9
A10
B10
B9
...
B3
B2
B1
C1
C2
C3
...
C9
C10
D10
D9
...
D2
D1
fiddle
I'm trying to count distinct combinations of three columns, order of the columns doesn't matter
sample :
a a a
a a b
a b a
b b a
b a b
the result I'm getting :
a a a 1
a a b 1
a b a 1
b b a 1
b a b 1
desired result
aaa 1
aab 2
bba 2
You can use an ordered array
select v[1], v[2], v[3], count(*) n
from tbl t
cross join lateral (
select array_agg(col order by col) v
from (
values (c1),(c2),(c3)
) t(col)
) s
group by v[1], v[2], v[3];
db<>fiddle
Maybe you can use checksums for getting the required result eg if it is really just combinations 'a' and 'b' that you are dealing with, you could convert the letters to integers (by calling the ASCII() function) and add these up so that you get a checksum.
TABLE
create table t (c1, c2, c3 ) as
select 'a', 'a', 'a' union all
select 'a', 'a', 'b' union all
select 'a', 'b', 'a' union all
select 'b', 'b', 'a' union all
select 'b', 'a', 'b' ;
Checksums
select c1, c2, c3, ascii( c1 ) + ascii( c2 ) + ascii( c3 ) as checksum
from t ;
-- output
c1 c2 c3 checksum
a a a 291
a a b 292
a b a 292
b b a 293
b a b 293
If this works for you, then you can use window functions eg
select c1, c2, c3, rc_ as rowcount
from (
select c1, c2, c3
, count(*) over ( partition by ascii( c1 ) + ascii( c2 ) + ascii( c3 ) order by 1 ) rc_
, row_number() over ( partition by ascii( c1 ) + ascii( c2 ) + ascii( c3 ) order by 1 ) rn_
from t
) sq
where rc_ = rn_ ;
-- output
c1 c2 c3 rowcount
a a a 1
a b a 2
b a b 2
See dbfiddle.
If you are dealing with strings that cannot easily converted to integers, you could create a mapping between the strings and integers, and implement the map_ as a view (so that it is easy to use in subsequent queries) eg
MAP
-- {1} find all distinct elements
-- {2} map each element to an integer
create view map_
as
select val_, rank() over ( order by val_ ) weight_
from (
select distinct val_
from (
select distinct c1 val_ from t union all
select distinct c2 from t union all
select distinct c3 from t
) all_elements
) unique_elements ;
Once you have this map, you can use its values for creating checksums (maybe also in a view) ...
Checksums
create view t_checksums_
as
select c1, c2, c3, c1weight + c2weight + c3weight as checksum
from (
select
c1, ( select weight_ from map_ where c1 = map_.val_ ) c1weight
, c2, ( select weight_ from map_ where c2 = map_.val_ ) c2weight
, c3, ( select weight_ from map_ where c3 = map_.val_ ) c3weight
from t
) valandweight ;
... and then, you can use the same query as before, for obtaining the final result - see dbfiddle.
I have a requirement to remove duplicate values present in a row.
like :
C1 | C2 | C3 | C4 | C5 | C6
----------------------------
1 | 2 | 1 | 2 | 1 | 3
1 | 2 | 1 | 3 | 1 | 4
1 |NULL| 1 |NULL| 1 |NULL
OUTPUT of the query should be:
C1 | C2 | C3 | C4 | C5 | C6
----------------------------
1 | 2 | 1 | 3 |NULL|NULL
1 | 2 | 1 | 3 | 1 | 4
1 |NULL|NULL|NULL|NULL|NULL
As you can see combination of 2 columns should be unique in a row.
in Row 1:
combination of 1/2 is duplicate so its removed and 1/3 is in c5/c6 is moved to c3/c4
in Row 2:
there is no duplicate in the combination of 1/2 , 1/3, 1/4 so no change in the result
in Row 3:
All the 3 combinations are same like 1/NULL is present in all the combinations so c3 to c6 is set to null.
Thanks in advance
Maybe there is a more clever way... but you could convert them to pairs, distinct (union in this case does that), then pivot back.
with pairs as (
select id, c1 as x, c2 as y from mytable
union
select id, c3, c4 from mytable
union
select id, c5, c6 from mytable
)
select id,
max(decode(rn,1,x)) c1,
max(decode(rn,1,y)) c2,
max(decode(rn,2,x)) c3,
max(decode(rn,2,y)) c4,
max(decode(rn,3,x)) c5,
max(decode(rn,3,y)) c6
from (
select id, x, y, row_number() over (partition by id) rn
from pairs
) as foo
group by id
This one works - data included for testing, but might take some time to understand
A tip: un-comment the code snippets under the -- debug lines, copy the script until just these code snippets and paste this part into an SQL prompt to test the intermediate results.
The principle is get a row identifier to "remember" the rows; then to vertically pivot - not 3 columns to one, but 6 columns to 3 pairs of columns; then, use DISTINCT to de-dupe; then get an index within the row identifier of the de-duped intermediate rows; then use that index to pivot horizontally again.
Like so:
WITH
input(c1,c2,c3,c4,c5,c6) AS (
SELECT 1, 2,1, 2,1, 3
UNION ALL SELECT 1, 2,1, 3,1, 4
UNION ALL SELECT 1,NULL::INT,1,NULL::INT,1,NULL::INT
)
,
-- need rowid
input_with_rowid AS (
SELECT ROW_NUMBER() OVER() AS rowid, * FROM input
)
,
-- three groupy of 2 columns, so pivot using 3 indexes
idx3(idx) AS (SELECT 1 UNION SELECT 2 UNION SELECT 3)
,
-- pivot vertically, two columns at a time and de-dupe
pivot_pair AS (
SELECT DISTINCT
rowid
, CASE idx
WHEN 1 THEN c1
WHEN 2 THEN c3
WHEN 3 THEN c5
END AS c1
,
CASE idx
WHEN 1 THEN c2
WHEN 2 THEN c4
WHEN 3 THEN c6
END AS c2
FROM input_with_rowid CROSS JOIN idx3
)
-- debug
-- SELECT * FROM pivot_pair ORDER BY rowid;
,
-- add sequence per rowid
pivot_pair_with_seq AS (
SELECT
rowid
, ROW_NUMBER() OVER(PARTITION BY rowid) AS seq
, c1
, c2
FROM pivot_pair
)
-- debug
-- SELECT * FROM pivot_pair_with_seq;
SELECT
rowid
, MAX(CASE seq WHEN 1 THEN c1 END) AS c1
, MAX(CASE seq WHEN 1 THEN c2 END) AS c2
, MAX(CASE seq WHEN 2 THEN c1 END) AS c3
, MAX(CASE seq WHEN 2 THEN c2 END) AS c4
, MAX(CASE seq WHEN 3 THEN c1 END) AS c5
, MAX(CASE seq WHEN 3 THEN c2 END) AS c6
FROM pivot_pair_with_seq
GROUP BY rowid
ORDER BY rowid
;
rowid|c1|c2|c3|c4|c5|c6
1| 1| 2| 1| 3|- |-
2| 1| 2| 1| 3| 1| 4
3| 1|- |- |- |- |-
Using marcothesane's idea with pivot/unpivot operators. Easier to maintain if more input columns should be deduplicated. This maintains the order of source data (column pairs) - whereas marcothesane's solution might reorder column pairs depening on input data. Also it is a little slower than marcothesane's. It works only in 11R1 and up.
WITH
input(c1,c2,c3,c4,c5,c6) AS (
SELECT 1, 2,1, 2,1, 3 from dual
UNION ALL SELECT 1, 2,1, 3,1, 4 from dual
UNION ALL SELECT 1,NULL ,1,NULL ,1,NULL from dual
)
,
-- need rowid
input_with_rowid AS (
SELECT ROW_NUMBER() OVER (order by 1) AS row_id, input.* FROM input
),
unpivoted_pairs as
(
select row_id, tuple_idx, val1, val2, row_number() over (partition by row_id, val1, val2 order by tuple_idx) as keep_first
from input_with_rowid
UnPivot include nulls(
(val1, val2) --measure
for tuple_idx in ((c1,c2) as 1,
(c3,c4) as 2,
(c5,c6) as 3)
)
)
select row_id,
t1_val1 as c1,
t1_val2 as c2,
t2_val1 as c3,
t2_val2 as c4,
t3_val1 as c5,
t3_val2 as c6
from (
select row_id,
val1, val2, row_number() over (partition by row_id order by tuple_idx) as tuple_order
from unpivoted_pairs
where keep_first = 1
)
pivot (sum(val1) as val1, sum(val2) as val2
for tuple_order in ('1' as t1, '2' as t2, '3' as t3)
)
I know this is possible through some complex techniques, i want to know any simplest way to achieve this patter that every 10 rows should repeat .
for example
select a,b from tablename; (repeating 2 for example)
will give
a1,b1
a2,b2
a1,b1
a2.b2
a3,b3
a4,b4
a3,b3
a4,b4
where if it was 10 it will go like
a1,b1 to a10,b10 again a1,b1 to a10,b10
then
a11,b11 to a20,b20 again a11,b11 to a20,b20
and so on
You want blocks of ten rows repeated twice. So to get:
rows 1 to 10
rows 1 to 10
rows 11 to 20
rows 11 to 20
...
In order to get rows n-fold cross join with a table holding n records. (You get such for instance by querying a big enough table and stop at rowcount n.)
You also need the row number of your original records, so you can get block 1 first, then block 2 and so on. Use integer division to get from row numbers to blocks.
select t.a, t.b
from (select a, b, row_number() over (order by a, b) as rn from tablename) t
cross join (select rownum as repeatno from bigenoughtable where rownum <= 2) r
order by trunc((t.rn -1) / 10), r.repeatno, t.a, t.b;
Use a CTE and union all:
with rows as (
select a, b
from tablename
where rownum <= 2
)
select *
from rows
union all
select *
from rows;
Just some caveats to this. You should use an order by if you want particular rows from the table. This is important, because the same select can return different sets of rows. Actually, considering this, a better way is probably:
with rows as (
select a, b
from tablename
where rownum <= 2
)
select *
from rows cross join
(select 1 as n from dual union all select 2 from dual) n;
I would rather not use UNION so many times. My way would be CONNECT BY ROWNUM <=N. Actually a CARTESIAN JOIN. So, basically you need a ROW GENERATOR to cartesian join with it.,
Update
For example, this will repeat 10 rows 2 times -
SQL> WITH t AS
2 ( SELECT 'a1' A, 'b1' b FROM dual
3 UNION ALL
4 SELECT 'a2' a, 'b2' b FROM dual
5 UNION ALL
6 SELECT 'a3' a, 'b3' b FROM dual
7 UNION ALL
8 SELECT 'a4' A, 'b4' b FROM dual
9 UNION ALL
10 SELECT 'a5' A, 'b5' b FROM dual
11 UNION ALL
12 SELECT 'a6' a, 'b6' b FROM dual
13 UNION ALL
14 SELECT 'a7' A, 'b7' b FROM dual
15 UNION ALL
16 SELECT 'a8' a, 'b8' b FROM dual
17 UNION ALL
18 SELECT 'a9' a, 'b9' b FROM dual
19 UNION ALL
20 SELECT 'a10' a, 'b10' b FROM dual
21 )
22 SELECT A,B FROM t,
23 (SELECT 1 FROM DUAL CONNECT BY ROWNUM <=2
24 )
25 /
A B
--- ---
a1 b1
a2 b2
a3 b3
a4 b4
a5 b5
a6 b6
a7 b7
a8 b8
a9 b9
a10 b10
a1 b1
a2 b2
a3 b3
a4 b4
a5 b5
a6 b6
a7 b7
a8 b8
a9 b9
a10 b10
20 rows selected.
SQL>
So, above CONNECT BY ROWNUM <=10 means repeat the rows 10 times. If you want it to be repeated N times use CONNECT BY ROWNUM <=N.
I have a SQL question. I have the following table:
ID A B C
1 A1 B1 C1
2 A1 B1 C2
3 A2 B2 C3
4 A1 B1 C3
5 A2 B2 C2
6 A3 B1 C1
7 A1 B1 C4
8 A2 B1 C1
I want to select one row from each group where the 'A' and 'B' are the same. For instance rows 1,2,4,and 7 form a group where A = 'A1' & B = 'B1'. Within these groups I want the record with the greatest value in column 'C' that isn't greater than 'C3' so record #4 in the above group.
Here is the result set I'm looking for:
ID A B C
4 A1 B1 C3
3 A2 B2 C3
6 A3 B1 C1
8 A2 B1 C1
You could use a CTE and a ranking function:
WITH CTE AS
(
SELECT ID, A, B, C,
RN = ROW_NUMBER() OVER (PARTITION BY A, B ORDER BY C DESC)
FROM dbo.TableName
WHERE C <= 'C3'
)
SELECT ID, A, B, C
FROM CTE
WHERE RN = 1
This might give you what you want:
Select A, B, Max(C) as maxC
(
Select *
From SomeTable
Where C <= 'C3'
) as nested
Group By A, B
Try this,
SELECT *
FROM (SELECT *,
Row_Number()
OVER (
PARTITION BY A, B
ORDER BY C DESC) AS RN
FROM #TEMP)A
WHERE RN = 1
Try this,
SELECT DISTINCT ID,A,
B,C
FROM (SELECT Row_number()
OVER(
partition BY A, B
ORDER BY id, c DESC)rn,
*
FROM tabl1)p
WHERE rn IN( 1, 3 )
AND id > 1