How can I partially transpose a table? - sql

I have a table like this:
id
source
score
1
a
10
1
b
15
2
a
20
2
c
25
In this table, id and source make up a unique key. source can be one of 'a', 'b' or 'c'.
I want to transform it into this:
id
score_a
score_b
score_c
1
10
15
0
2
20
0
25
I don't mind if the 0s are nulls instead, if that would make it easier.
The target table should be the result of the following steps:
for every row in the table:
if source = 'a', add or update a row with id and score_a = score
if source = 'b', add or update a row with id and score_b = score
if source = 'c', add or update a row with id and score_c = score
I tried the following statement:
select id, score as score_a, null as score_b, null as score_c from tbl where source = 'a'
union all
select id, null as score_a, score as score_b, null as score_c from tbl where source = 'b'
union all
select id, null as score_a, null as score_b, score as score_c from tbl where source = 'c'
But it gave me this result instead:
id
score_a
score_b
score_c
1
10
null
null
2
20
null
null
1
null
15
null
2
null
null
25
How can I get a table like the one I want (with distinct id)?

You just need to use CASE to implement your logic and then flatten the result with some aggregation (I used MAX())
SELECT id,
MAX(CASE WHEN source='a' THEN score END) as score_a,
MAX(CASE WHEN source='b' THEN score END) as score_b,
MAX(CASE WHEN source='c' THEN score END) as score_c
FROM Tbl
GROUP BY id
DEMO
You indicated that null was OK, but if you have to have zeroes instead add COALESCE()

Related

Order by statement - two columns

I have a SQL select query from table x. In this query, I get BpName from table x and BpName2 from scalar function. I want to order by BpName2 if BpName is null and by BpName if is not null. Is it possible?
Example:
These are my rows:
Id BpName BpName2
------------------------
1 NULL 'C'
2 'A' NULL
3 NULL 'B'
I want to order them like this:
Id BpName BpName2
------------------------
2 'A' NULL
3 NULL 'B'
1 NULL 'C'
You could order by the coalesced result of the columns:
SELECT *
FROM mytable
ORDER BY COALESCE(BpName, BpName2)

Keep multiple rows during PIVOT in Snowflake

I would like to transpose rows into columns in Snowflake.
Suppose I have the following table BASE
ID
value
type
1
100
'A'
1
200
'B'
1
300
'B'
2
400
'A'
The output should be as follows:
ID
A
B
1
100
200
1
100
300
2
400
NULL
Currently I am pivoting the table with
SELECT ID,
CASE WHEN TYPE = 'A' THEN VALUE ELSE NULL AS A,
CASE WHEN TYPE = 'B' THEN VALUE ELSE NULL AS B
FROM BASE
For now the GROUP BY statement is missing. Typically I would GROUP BY ID, but that does not account for keeping one row per each value on the same TYPE and ID.
Any ideas how to achieve this?
Cheers,
P
You can use conditional aggregation. You can use row_number() to get multiple rows:
SELECT ID,
MAX(CASE WHEN TYPE = 'A' THEN VALUE END) AS A,
MAX(CASE WHEN TYPE = 'B' THEN VALUE END) AS B
FROM (SELECT B.*,
ROW_NUMBER() OVER (PARTITION BY ID, TYPE ORDER BY VALUE) as seqnum
FROM BASE B
) B
GROUP BY ID, seqnum;
This would work, too:
select *
from base_table
pivot(sum(value) for type in ('A','B')) as p
order by id;

Merge multiple columns into one column with multiple rows

In PostgreSQL, how can I merge multiple columns into one column with multiple rows?
The columns are all boolean, so I want to:
Filter for true values only
Replace the true value (1) with the name of the column (A, B or C)
I have this table:
ID | A | B | C
1 0 1 0
2 1 1 0
3 0 0 1
4 1 0 1
5 1 0 0
6 0 1 1
I want to get this table:
ID | Letter
1 B
2 A
2 B
3 C
4 A
4 C
5 A
6 B
6 C
I think you need something like this:
SELECT ID, 'A' as Letter FROM table WHERE A=1
UNION ALL
SELECT ID, 'B' as Letter FROM table WHERE B=1
UNION ALL
SELECT ID, 'C'as Letter FROM table WHERE C=1
ORDER BY ID, Letter
SELECT ID,
(CASE
WHEN TABLE.A = 1 then 'A'
WHEN TABLE.B = 1 then 'B'
WHEN TABLE.C = 1 then 'C'
ELSE NULL END) AS LETTER
from TABLE
You may try this.
insert into t2 select id, 'A' from t1 where A=1;
insert into t2 select id, 'B' from t2 where B=1;
insert into t2 select id, 'C' from t3 where C=1;
If you care about the order, then you can do this.
insert into t3 select id, letter from t2 order by id, letter;
W/o UNION
You can use a single query to get the desired output.Real time example
select id
,regexp_split_to_table((
concat_ws(',', case
when a = 0
then null
else 'a'
end, case
when b = 0
then null
else 'b'
end, case
when c = 0
then null
else 'c'
end)
), ',') l
from c1;
regexp_split_to_table() & concat_ws()

How do I count and replace elements in an sql column

I have a table with a column that contains numbers 1 - 5, it looks something like the following
Column
1
2
4
3
2
1
5
2
How do I count the number of times each number shows up so that my final table looks like this:
Number | Count
one | 2
two | 3
three | 1
four | 1
five | 1
Try this:
select
case
when number = 1 then 'one'
when number = 2 then 'two'
when number = 3 then 'three'
when number = 4 then 'four'
when number = 5 then 'five'
end number,
count
from
(select number,count(*) count
from yourtable
group by number) s
In this case you have only 5 values so case is feasible to do the replacement. However, good practice would be to use a table with the number to name mapping and then join it to your counting query. So, assuming you have table tblMap with 2 columns - number and name, you would do;
select name, count(*)
from
yourtable t
inner join tblMap m on t.number = m.number
group by t.number, name --group by number so that results are ordered by number
SELECT
CASE
WHEN columnx = 1 THEN 'one'
WHEN columnx = 2 THEN 'two'
WHEN columnx = 3 THEN 'three'
WHEN columnx = 4 THEN 'four'
WHEN columnx = 5 THEN 'five'
ELSE 'unexpected' END as Number
, COUNT(*) as count_of
FROM YourTable
GROUP BY
CASE
WHEN columnx = 1 THEN 'one'
WHEN columnx = 2 THEN 'two'
WHEN columnx = 3 THEN 'three'
WHEN columnx = 4 THEN 'four'
WHEN columnx = 5 THEN 'five'
ELSE 'unexpected' END
Which database you need this, if oracle then you can use below query provided your number are between the range of 1 and 5373484
-- works only for values between 1 and 5373484 (i.e julian date)
select to_char(to_date(num_column,'j'),'jsp'),cnt
from
( select num_column,count(1) cnt
From TableA
group by num_column
)
select
case
when [Column] = '1' then 'One'
when [Column] = '2' then 'Two'
when [Column] = '3' then 'Three'
when [Column] = '4' then 'Four'
when [Column] = '5' then 'Five'
end [Number]
, COUNT (*) as [Count] from Table_Name
group by [Column]

Exclude value of a record in a group if another is present

In the example table below, I'm trying to figure out a way to sum amount over id for all marks where mark 'C' doesn't exist within an id. When mark 'C' does exist in an id, I want the sum of amounts over that id, excluding the amount against mark 'A'. As illustration, my desired output is at the bottom. I've considered using partitions and the EXISTS command, but I'm having trouble conceptualizing the solution. If any of you could take a look and point me in the right direction, it would be greatly appreciated :)
sample table:
id mark amount
------------------
1 A 1
2 A 3
2 B 2
3 A 2
4 A 1
4 B 3
5 A 1
5 C 3
6 A 2
6 C 2
desired output:
id sum(amount)
-----------------
1 1
2 5
3 2
4 4
5 3
6 2
select
id,
case
when count(case mark when 'C' then 1 else null end) = 0
then
sum(amount)
else
sum(case when mark <> 'A' then amount else 0 end)
end
from sampletable
group by id
Here is my effort:
select id, sum(amount) from table t where not t.id = 'A' group by id
having id in (select id from table t where mark = 'C')
union
select id, sum(amount) from table t where t.id group by id
having id not in (select id from table t where mark = 'C')
SELECT
id,
sum(amount) AS sum_amount
FROM atable t
WHERE mark <> 'A'
OR NOT EXISTS (
SELECT *
FROM atable
WHERE id = t.id
AND mark = 'C'
)
GROUP BY
id
;