Unique pairs of columns SQL - sql

I have 4 columns, like below:
COL1 COL1_TIME COL2 COL2_TIME
A 09:20:00 E 09:35:00
A 09:20:00 F 09:36:00
A 09:20:00 G 09:40:00
A 09:20:00 H 09:59:00
B 09:25:00 E 09:35:00
B 09:25:00 F 09:36:00
B 09:25:00 G 09:40:00
B 09:25:00 H 09:59:00
C 09:30:00 E 09:35:00
C 09:30:00 F 09:36:00
C 09:30:00 G 09:40:00
C 09:30:00 H 09:59:00
D 09:50:00 H 09:59:00
I have to select unique pairs of values from columns COL1 and COL2. To find a pair, you should take Closest time to COL1_TIME in COL2_TIME.
So the colsest time for A is E. For B its F - E is taken already etc.
Result should look like this:
A E
B F
C G
D H
Any ideas?

Well, without the recursive WITH Common Table Expression, you need to hard-wire a few things.
And if you have more than 4 values for COL1, it gets even more tedious; and if it is about a very important business issue, consider writing a UDx for that.
But - otherwise - here is one that works - input included in the first Common Table Expression of the WITH clause:
WITH
input(col1,col1_time,col2,col2_time) AS (
SELECT 'A',TIME '09:20:00','E',TIME '09:35:00'
UNION ALL SELECT 'A',TIME '09:20:00','F',TIME '09:36:00'
UNION ALL SELECT 'A',TIME '09:20:00','G',TIME '09:40:00'
UNION ALL SELECT 'A',TIME '09:20:00','H',TIME '09:59:00'
UNION ALL SELECT 'B',TIME '09:25:00','E',TIME '09:35:00'
UNION ALL SELECT 'B',TIME '09:25:00','F',TIME '09:36:00'
UNION ALL SELECT 'B',TIME '09:25:00','G',TIME '09:40:00'
UNION ALL SELECT 'B',TIME '09:25:00','H',TIME '09:59:00'
UNION ALL SELECT 'C',TIME '09:30:00','E',TIME '09:35:00'
UNION ALL SELECT 'C',TIME '09:30:00','F',TIME '09:36:00'
UNION ALL SELECT 'C',TIME '09:30:00','G',TIME '09:40:00'
UNION ALL SELECT 'C',TIME '09:30:00','H',TIME '09:59:00'
UNION ALL SELECT 'D',TIME '09:50:00','H',TIME '09:59:00'
)
,
col1_A AS (
SELECT
col1
, col2
FROM input
WHERE col1='A'
ORDER BY ABS(TIMESTAMPDIFF('SECOND',col1_time::TIMESTAMP,col2_time::TIMESTAMP))
LIMIT 1
)
,
col1_B AS (
SELECT
col1
, col2
FROM input
WHERE col1='B'
AND col2 NOT IN (
SELECT col2 FROM col1_A
)
ORDER BY ABS(TIMESTAMPDIFF('SECOND',col1_time::TIMESTAMP,col2_time::TIMESTAMP))
LIMIT 1
)
,
col1_C AS (
SELECT
col1
, col2
FROM input
WHERE col1='C'
AND col2 NOT IN (
SELECT col2 FROM col1_A
UNION ALL SELECT col2 FROM col1_B
)
ORDER BY ABS(TIMESTAMPDIFF('SECOND',col1_time::TIMESTAMP,col2_time::TIMESTAMP))
LIMIT 1
)
,
col1_D AS (
SELECT
col1
, col2
FROM input
WHERE col1='D'
AND col2 NOT IN (
SELECT col2 FROM col1_A
UNION ALL SELECT col2 FROM col1_B
UNION ALL SELECT col2 FROM col1_C
)
ORDER BY ABS(TIMESTAMPDIFF('SECOND',col1_time::TIMESTAMP,col2_time::TIMESTAMP))
LIMIT 1
)
SELECT * FROM col1_A
UNION ALL SELECT * FROM col1_B
UNION ALL SELECT * FROM col1_C
UNION ALL SELECT * FROM col1_D
;
If it's not what you'd hoped for, I'd not be surprised ...
Happy playing ...
Marco the Sane

If the cardinality of COL1 and COL2 distinct values is always 1-1 and no other special occasions/exeptions exist, you can do the following:
with temp1 as (
select col1
,col1_time
,row_number() over (partition by col1 order by col1 desc) as rownum1
), temp2 as(
select col2
,col2_time
,row_number() over (partition by col2 order by col2 desc) as rownum2
)
select distinct(temp1.col1)
,distinct(temp2.col2)
from temp1,temp2
where temp1.rownum1 = temp2.rownum2

Related

Connect by lead incremental values Oracle

I have this table
COL1 COL2
---------
A 1
B 5
C 12
D 14
And I would like to obtain this other one. This is, until the next col2 for each col1 is reached, a row with the COL1 and incremental values.
COL1 COL2
---------
A 1
A 2
A 3
A 4
B 5
B 6
B 7
B 8
B 9
B 10
B 11
C 12
C 13
D 14
EDIT: this is what I've tried so far. It seems I'm not far away from the solution but struggling to progress further than this.
WITH aux (
col1,
col2
) AS (
SELECT
'A',
1
FROM
dual
UNION ALL
SELECT
'B',
5
FROM
dual
UNION ALL
SELECT
'C',
12
FROM
dual
UNION ALL
SELECT
'D',
14
FROM
dual
), aux1 AS (
SELECT
a.*,
nvl(LEAD(a.col2) OVER(
ORDER BY
a.col2
), a.col2) h
FROM
aux a
)
SELECT
*
FROM
aux1
CONNECT BY level >= col2
AND level <= h;
testseq is the table containing your initial 4 rows. Use lead to find the stop value for col2 for each col1, and recursion to iterate and create the additional rows.
WITH xrows (col1, col2, lastcol2) AS (
SELECT t.*, LEAD(col2) OVER (ORDER BY col1) - 1
FROM testseq t
UNION ALL
SELECT col1, col2+1, lastcol2
FROM xrows t
WHERE col2 < lastcol2
)
SELECT col1, col2
FROM xrows
ORDER BY col1, col2
;
First you need to find the "next" number (whatever ordering you prefer) and then generate such number of rows with recursive subquery:
with a(code, num) as(
select 'A', 1 from dual union all
select 'B', 5 from dual union all
select 'C', 12 from dual union all
select 'D', 14 from dual
)
, b as (
select
a.*
, lead(num - 1, 1, num) over(order by code asc) as next_num
from a
)
select
b.code
, gen.val
from b
cross join lateral(
select num + level - 1 as val
from dual
connect by num + level - 1 <= next_num
) gen
order by 2 asc
Or if you prefer recursive CTE:
with a(code, num) as(
select 'A', 1 from dual union all
select 'B', 5 from dual union all
select 'C', 12 from dual union all
select 'D', 14 from dual
)
, b(code, next_num, val) as (
select
a.code
, lead(num - 1, 1, num) over(order by code asc) as next_num
, num
from a
union all
select
code
, next_num
, val + 1
from b
where val < next_num
)
select
b.code
, val
from b
order by 2 asc
CODE
VAL
A
1
A
2
A
3
A
4
B
5
B
6
B
7
B
8
B
9
B
10
B
11
C
12
C
13
D
14
livesql demo

order columns by their value

I've got a table A with 3 columns that contains the same data, for exemple:
TABLE A
KEY COL1 COL2 COL3
1 A B C
2 B C null
3 A null null
4 D E F
5 null C B
6 B C A
7 D E F
As a result I expect the distinct values of this table and the order doesn't matter. So key 1 and 6 are the same and 2 and 5 also and 4 and 7. The rest is different.
Ofcourse, I can't use a distinct in my select that will only filter 4 and 7.
I could use a very complex case statement, or a select in a select with an order by. But this needs to be used in a conversion, so performance is an issue here.
Does anyone have a good performant way to do this?
The result I expect
COL1 COL2 COL3
A B C
B C null
A null null
D E F
If you can have many columns then you can UNPIVOT then order the values and then PIVOT and take the DISTINCT rows:
Oracle Setup:
CREATE TABLE table_name ( KEY, COL1, COL2, COL3 ) AS
SELECT 1, 'A', 'B', 'C' FROM DUAL UNION ALL
SELECT 2, 'B', 'C', null FROM DUAL UNION ALL
SELECT 3, 'A', null, null FROM DUAL UNION ALL
SELECT 4, 'D', 'E', 'F' FROM DUAL UNION ALL
SELECT 5, null, 'C', 'B' FROM DUAL UNION ALL
SELECT 6, 'B', 'C', 'A' FROM DUAL UNION ALL
SELECT 7, 'D', 'E', 'F' FROM DUAL
Query:
SELECT DISTINCT
COL1, COL2, COL3
FROM (
SELECT key,
value,
ROW_NUMBER() OVER ( PARTITION BY key ORDER BY value ) AS rn
FROM table_name
UNPIVOT ( value FOR name IN ( COL1, COL2, COL3 ) ) u
)
PIVOT ( MAX( value ) FOR rn IN (
1 AS COL1,
2 AS COL2,
3 AS COL3
) )
Output:
COL1 | COL2 | COL3
:--- | :--- | :---
A | B | C
B | C | null
D | E | F
A | null | null
db<>fiddle here
The complicated case expression is going to have the best performance. But the simplest method is going to be conditional aggregation:
select key,
max(case when seqnum = 1 then col end) as col1,
max(case when seqnum = 2 then col end) as col2,
max(case when seqnum = 3 then col end) as col3
from (select key,col,
row_number() over (partition by key order by col asc) as seqnum
from ((select key, col1 as col from t) union all
(select key, col2 as col from t) union all
(select key, col3 as col from t)
) kc
where col is not null
) kc
group by key;

sql counting values in 3 columns

How do I count all values when the possible values could be in anywhere from 0 to 3 columns. I want to add up all the a's, b's, c's, d's
COL1 COL2 COL3
a
b
b a
a c b
a b
c a
d a c
c d
select col, count(*)
from (
select col1 col from tablename
union all
select col2 from tablename
union all
select col3 from tablename) t
group by col
You can use UNPIVOT:
SELECT t.Val, COUNT(*) AS cnt
FROM (
SELECT Col1, Col2, Col3
FROM mytable
UNPIVOT (
Val FOR Col IN ("Col1", "Col2", "Col3"))) t
GROU BY t.Val

Select records where all rows have same value in two columns

Here is my sample table
Col1 Col2
A 1
B 1
A 1
B 2
C 3
I want to be able to select distinct records where all rows have the same value in Col1 and Col2. So my answer should be
Col1 Col2
A 1
C 3
I tried
SELECT Col1, Col2 FROM Table GROUP BY Col1, Col2
This gives me
Col1 Col2
A 1
B 1
B 2
C 3
which is not the result I am looking for. Any tips would be appreciated.
Try this out:
SELECT col1, MAX(col2) aCol2 FROM t
GROUP BY col1
HAVING COUNT(DISTINCT col2) = 1
Output:
| COL1 | ACOL2 |
|------|-------|
| A | 1 |
| C | 3 |
Fiddle here.
Basically, this makes sure that amount the different values for col2 are unique for a given col1.
Try this:
SELECT * FROM MYTABLE
GROUP BY Col1, Col2
HAVING COUNT(*)>1
For example SQLFiddle here
you can try either of the below -
select col1, col2 from
(
select 'A' Col1 , 1 Col2
from dual
union all
select 'B' , 1
from dual
union all
select 'A' ,1
from dual
union all
select 'B' ,2
from dual
)
group by col1, col2
having count(*) >1;
OR
select col1, col2
from
(
select col1, col2, row_number() over (partition by col1, col2 order by col1, col2) cnt
from
(
select 'A' Col1 , 1 Col2
from dual
union all
select 'B' , 1
from dual
union all
select 'A' ,1
from dual
union all
select 'B' ,2
from dual
)
)
where cnt>1;

SQL Server Counting

I have the following query:
select col1, sum( col2 ), count( col3 )
from table1
group by col1
order by col1
which returns something like this
col1
dept1
dept2
dept3
col2
10
20
30
col3
2
3
4
Without a stored procedure, is it possible to get a total column below the results generated by the original query?
i.e.
col1
dept1
dept2
dept3
total
col2
10
20
30
60
col3
2
3
4
9
use ROLLUP:
;with Table1 as (
select 'dept1' as col1, 5 as col2,1 as col3
union all
select 'dept1', 5 as col2, 1 as col3
union all
select 'dept2',10,1
union all
select 'dept2',5,1
union all
select 'dept2',5,1
union all
select 'dept3',10,1
union all
select 'dept3',5,1
union all
select 'dept3',5,1
union all
select 'dept3',10,1
)
select COALESCE(col1,'total'), sum( col2 ), count( col3 )
from table1
group by col1
with rollup
order by COALESCE(col1,'ZZZZZ')
Results:
(No column name) (No column name) (No column name)
dept1 10 2
dept2 20 3
dept3 30 4
total 60 9
Have a look at the keyword WITH ROLLUP on your GROUP BY clause
yep:
select col1, sum(col2), count(col3)
from table1
group by col1
union all
select 'totals', sum(col2), count(1) from table1
order by col1