Conditional UNPIVOT in TSQL - sql

Let's say I have a table with an ID column, and several property columns
MyTable (ID, PI, P2, P3, P4)
ID P1 P2 P3 P4
1 A1 B C1 D1
2 C1 C2 B NULL
3 C2 Z NULL NULL
4 X A1 C1 NULL
So, I need to write a query to find out how many distinct property values out there, no matter in which column they are.
Value Count
A1 2
B 2
C1 3
C2 2
X1 1
...
I think I can get this by using UNPIVOT (correct me, if I am wrong)
Now, how can I get similar count but grouped by a number of non-null values in the row (the count of non-null values per row may, or may not include key columns, doesn't matter), i.e. output like this:
Value NonNullCount Count
A1 3 1
A1 4 1
B 3 1
B 4 1
C1 2 3
C1 4 1
C2 3 1
C2 2 1
...

Here is one method, using cross apply for the unpivot:
select vals.p, t.NonNullCount, count(*)
from (select t.*,
((case when p1 is not null then 1 else 0 end) +
(case when p2 is not null then 1 else 0 end) +
(case when p3 is not null then 1 else 0 end) +
(case when p4 is not null then 1 else 0 end)
) as NonNullCount
from table t
) t cross apply
(values (p1), (p2), (p3), (p4)) vals(p)
where vals.p is not null
group by vals.p, t.NonNullCount;

Related

Oracle count if column exists in other table with conditions

I have this table
COL_A FROM TO
------------------
D1 1 3
D2 3 7
And also this other table
COL_A VALUE
-------------
D1 0
D1 2
D1 5
D2 2
D2 5
D2 6
I want to obtain this. For each row in the first table, count the rows in the second table whose value is less than, between and greater than the FROM and TO columns.
COL_A FROM TO LESS_THAN_FROM BETWEEN_FROM_TO GREATER_THAN_TO
-------------------------------------------------------------------
D1 1 3 1 1 1
D2 3 7 1 2 0
Use join and conditional aggregation:
select t.col_a, t.from, t.to,
sum(case when o.value < t.from then 1 else 0 end) as less_than,
sum(case when o.value between t.from and t.to then 1 else 0 end) as in_between,
sum(case when o.value > t.to then 1 else 0 end) as greater_than
from this_table t join
other_table o
on t.col_a = o.col_a
group by t.col_a, t.from, t.to;

Oracle left join returning no rows

I have a (now thoroughly derived) CTE:
feature_id | function_id | group_id | subgroup_id | type_id
1 1 null 1 null
1 1 null null 14
2 1 null 5 null
2 1 null null 21
3 1 null 7 null
3 1 null null 5
I am trying to collate the rows together using this:
select C1.feature_id, C1.function_Id, C2.Group_ID, C3.Subgroup_ID, C4.Type_id
from CTE C1
left join CTE C2
on C1.feature_id = C2.feature_id
and c1.function_id = c2.function_id
and c2.group_id is not null
left join CTE C3
on C1.feature_id = C3.feature_id
and c1.function_id = c3.function_id
and c3.subgroup_id is not null
left join CTE C4
on C1.feature_id = C4.feature_id
and c1.function_id = c4.function_id
and c4.type_id is not null
This gives me 0 rows...
To validate, I ran:
select *
from CTE C1
236 rows selected
Can anyone help? Surely the rows from C1 should be coming back...
EDIT: Fixed it with the Oracle syntax:
select C1.feature_id, C1.function_Id, C2.Group_ID, C3.Subgroup_ID, C4.Type_id
from CTE C1, CTE C2, CTE C3, CTE C4
where C1.feature_id = C2.feature_id(+)
and c1.function_id = c2.function_id(+)
and C2.group_id(+) is not null
...
(I hate the oracle syntax)
Ok, solved it... Fixed it with the Oracle syntax:
select C1.feature_id, C1.function_Id, C2.Group_ID, C3.Subgroup_ID, C4.Type_id
from CTE C1, CTE C2, CTE C3, CTE C4
where C1.feature_id = C2.feature_id(+)
and c1.function_id = c2.function_id(+)
and C2.group_id(+) is not null
...
(I hate the oracle syntax)

SQL grouping by one value in 2 columns

SQL grouping by one value in 2 columns
Source data table:
P2 P3
----------
1 2
2 1
2 3
4 1
I want a query that counts a's and b's in each column, producing something like:
num conut
-------------
1 3
2 3
3 1
4 1
You can do this using union all and group by:
select num, sum(cnt) as conut
from (select p2 as num, count(*) as cnt from source group by p2
union all
select p3 as num, count(*) as cnt from source group by p3
) p
group by num;

Find missing number from not sequence number

I have a database with 5 columns (A1,A2,A3,A4,A5) which store 5 numbers.
The 5 numbers are "1,2,3,4,5".
A1 A2 A3 A4 A5
-------------------------------
2 4 5 Null Null
I want get the missing number which is "1" and "3".
How do I find the missing number from 5 numbers?
Select Replace(Replace(Replace(Replace(
Replace('12345',(Cast(Coalesce(A5,0) as varchar(1))),''),
(Cast(Coalesce(A4,0) as varchar(1))),''),
(Cast(Coalesce(A3,0) as varchar(1))),''),
(Cast(Coalesce(A2,0) as varchar(1))),''),
(Cast(Coalesce(A1,0) as varchar(1))),'') from Table1
Sql Fiddle Demo
You can do this
WITH sequence AS
(
SELECT 1 n UNION ALL
SELECT n + 1 FROM sequence WHERE n < 5
)
SELECT n
FROM sequence s LEFT JOIN table1 t
ON s.n IN (t.a1, t.a2, t.a3, t.a4, t.a5)
WHERE t.a1 IS NULL
Output:
| N |
|---|
| 1 |
| 3 |
Here is SQLFiddle demo
Depending on the desired output, this might work. This returns the relevant missing number(s) for each row.
SELECT CASE WHEN COALESCE(A1,0)<>1 AND COALESCE(A2,0)<>1 AND COALESCE(A3,0)<>1
AND COALESCE(A4,0)<>1 AND COALESCE(A5,0)<>1 THEN 1 ELSE '' END A
, CASE WHEN COALESCE(A1,0)<>2 AND COALESCE(A2,0)<>2 AND COALESCE(A3,0)<>2
AND COALESCE(A4,0)<>2 AND COALESCE(A5,0)<>2 THEN 2 ELSE '' END B
, CASE WHEN COALESCE(A1,0)<>3 AND COALESCE(A2,0)<>3 AND COALESCE(A3,0)<>3
AND COALESCE(A4,0)<>3 AND COALESCE(A5,0)<>3 THEN 3 ELSE '' END C
, CASE WHEN COALESCE(A1,0)<>4 AND COALESCE(A2,0)<>4 AND COALESCE(A3,0)<>4
AND COALESCE(A4,0)<>4 AND COALESCE(A5,0)<>4 THEN 4 ELSE '' END D
, CASE WHEN COALESCE(A1,0)<>5 AND COALESCE(A2,0)<>5 AND COALESCE(A3,0)<>5
AND COALESCE(A4,0)<>5 AND COALESCE(A5,0)<>5 THEN 5 ELSE '' END E
FROM NumTest
WHERE COALESCE(A1,0)+COALESCE(A2,0)+COALESCE(A3,0)+COALESCE(A4,0)+COALESCE(A5,0)<>15
The results look like:
you'll need a table of integers from 1 to (in this case) 5:
DECLARE #ints table (n int);
INSERT #ints VALUES (1), (2), (3), (4), (5);
second, we get the numbers in the table row into a single comparable set:
SELECT x INTO #all FROM (
SELECT A1 as x FROM myTable WHERE ID = myRow
UNION ALL
SELECT A2 as x FROM myTable WHERE ID = myRow
UNION ALL
SELECT A3 as x FROM myTable WHERE ID = myRow
UNION ALL
SELECT A4 as x FROM myTable WHERE ID = myRow
UNION ALL
SELECT A5 as x FROM myTable WHERE ID = myRow
) y
then you can derive the answer:
SELECT #ints.n
FROM #ints left join #all on #ints.n = #all.x
WHERE #all.x is null
ORDER BY 1

join instances of same row

I have a table h containing data like this (OK, not really, it's just an example):
subj_id q1 q2 q3 q4 q5 q6 num
1 1 0 0 1 0 0 1
1 0 0 0 1 0 0 2
2 1 1 1 1 0 1 1
2 1 0 0 1 0 0 2
2 1 1 1 0 0 1 3
3 0 1 0 0 1 1 1
I would like to sum up the q's for each subj_id resulting in a output like this:
subj_id num1 num2 num3
1 2 1 null
2 5 2 4
3 3 null null
but instead I get the following:
subj_id num1 num2 num3
1 2 1 null
1 2 1 null
2 5 2 4
2 5 2 4
2 5 2 4
3 3 null null
where the summed rows are repeated as many times as the subj_id appears in the table.
My query (postgres) looks like this:
select h.subj_id, n1.sum as num1, n2.sum as num2, n3.sum as num3 from ((( h
left join (select subj_id, q1+q2+q3+q4+q5+q6 as sum from h where num=1) as n1 on h.subj_id=n1.subj_id)
left join (select subj_id, q1+q2+q3+q4+q5+q6 as sum from h where num=2) as n2 on h.subj_id=n2.subj_id)
left join (select subj_id, q1+q2+q3+q4+q5+q6 as sum from h where num=3) as n3 on h.subj_id=n3.subj_id) order by h.subj_id
Left join is obvious not the trick to use here, but what to do to skip the repeating rows?
Thanks in advance!
Your query could be easily modified to this:
with cte as (
select subj_id, q1 + q2 + q3 + q4 + q5 + q6 as q, num
from h
)
select
subj_id,
sum(case when num = 1 then q end) as num1,
sum(case when num = 2 then q end) as num2,
sum(case when num = 3 then q end) as num3
from cte
group by subj_id
order by subj_id
I think plan would be much better - no joins at all.
=> sql fiddle demo
Brief explanation why your query is not working and how you can improve it:
You receive more rows that you want because basically what your query does is selecting each row from table h and then join to it sum from table h with num = 1, 2, 3. You have 6 rows in your initial table and it's logical you'll have 6 rows in your result;
If you ever will make some query like this, I strongly suggest you to use aliases for tables in the inner queries. I'll help you to understand queries. In some cases it'll also help you to avoid incorrect results - see my answer in this topic - SQL IN query produces strange result.
-
select
h.subj_id, n1.sum as num1, n2.sum as num2, n3.sum as num3
from h
left join (
select h1.subj_id, h1.q1+h1.q2+h1.q3+h1.q4+h1.q5+h1.q6 as sum
from h as h1
where h1.num=1
) as n1 on h.subj_id=n1.subj_id
left join (
select h2.subj_id, h2.q1+h2.q2+h2.q3+h2.q4+h2.q5+h2.q6 as sum
from h as h2
where h2.num=2
) as n2 on h.subj_id=n2.subj_id
left join (
select h3.subj_id, h3.q1+h3.q2+h3.q3+h3.q4+h3.q5+h3.q6 as sum
from h as h3
where h3.num=3
) as n3 on h.subj_id=n3.subj_id
order by h.subj_id