How to separate column values by condition (pivot) to fill one row - sql

I have two tables that I'd like do a full outer join where the resulting view separates the values table into two separate columns with one row for each name_id. I have made one approach with a CASE expression to select by type and then use it with pandas to fill in the values and return distinct name_ids.
Name Table
name_id
name
1
foo
2
bar
3
doo
4
sue
Values Table
name_id
value
type
1
90
red
2
95
blue
3
33
red
3
35
blue
4
60
blue
4
20
red
This is a condensed version. In my full table, I need to do this twice with two separate value tables sorted by type, red/blue and control/placebo.
Simple Join
SELECT names_table.name_id, name, value, type
FULL OUTER JOIN values_table
ON names_table.name_id = values_table.name_id
WHERE type in ('red', 'blue')
name_id
name
value
type
1
foo
90
red
2
bar
95
blue
3
doo
33
red
3
doo
35
blue
4
sue
60
blue
4
sue
20
red
Current work around result which I then fix with python and pandas
SELECT names_table.name_id, name, value, type
CASE
WHEN type = 'red' THEN value END red,
CASE
WHEN type = 'blue' THEN value END blue
FROM names_table
FULL OUTER JOIN values_table
ON names_table.name_id = values_table.name_id
name_id
name
blue
red
1
foo
Null
90
2
bar
95
Null
3
doo
35
Null
3
doo
Null
33
4
sue
60
Null
4
sue
Null
20
This is my desired output below, where I would have the types as columns and just rows for unique name_ids but with value tables 1 and 2.
Desired Output
name_id
name
blue
red
1
foo
Null
90
2
bar
95
Null
3
doo
35
33
4
sue
60
20

I have two tables that I'd like do a full outer join ...
Why would you? Better explain what you actually want to do instead of the assumed tool to implement it.
Simple pivoting with the aggregate FILTER clause. See:
Aggregate columns with additional (distinct) filters
SELECT name_id, n.name, v.blue, v.red
FROM (
SELECT name_id
, min(value) FILTER (WHERE type = 'blue') AS blue
, min(value) FILTER (WHERE type = 'red') AS red
FROM values_table
GROUP BY 1
) v
LEFT JOIN names_table n USING (name_id);
Produces your desired result.
db<>fiddle here
The LEFT JOIN includes result rows even if no name is found.
A FULL [OUTER] JOIN would add names in the result that have no values at all. I think you really want a LEFT [OUTER] JOIN or even a plain [INNER] JOIN.
You can just switch the JOIN type to adapt to your actual requirements. The identical column name "name_id" allows to join with a USING clause. The unqualified name_id in the outer SELECT works for any join type.
Note how I aggregate first and join later. Typically substantially faster. See:
Query with LEFT JOIN not returning rows for count of 0
If there can be duplicate values for "red" or "blue", you'll have to define how to deal with those.
For more involved queries consider crosstab(). See:
PostgreSQL Crosstab Query

Related

Generating a row if there is not a matching id in another table as a placeholder row

I have 2 tables. Table 1 has a foreign key of factor_id in Table 2.
Table 1 - Financial Data
factor_id trans_id profit(in K)
1 476 24
2 476 22
3 476 19
3 515 41
Table 2 - Financial Factors
factor_id model_id factor_title
1 43 domestic
2 43 foreign
3 43 3rd party
4 43 licensed
What I need is for Table 1 to return all 3 rows for model_id 43 and trans_id 476 AND IN ADDITION a placeholder row for the one that is missing with factor_id 4 since it doesn't exist in Table 1
Expected output would be:
factor_id trans_id profit(in K)
1 476 24
2 476 22
3 476 19
4 476 0 <---Created output Row that doesn't exist in Table 1
You could use an OUTER JOIN, either LEFT or RIGHT. This type of join will return all records from one table (the FROM table for a LEFT or the JOIN table for a RIGHT) even if there are not no matches in the other table, a null value will be returned in all columns referenced from the table that has no matches but are returned in the results.
To return a zero or other value instead of a NULL we can use the ISNULL() function.
The following two queries are equivalent, Showing a RIGHT join first, (the term OUTER is optional and omitted from this example) as it matches the FROM statement that you may already have, the LEFT join example swaps the tables around in the statement. In general the FROM table should be the one that the most significant filtering will be performed on, for this limited example neither is superior to the other.
SELECT ff.factor_id, IsNull(f.trans_id,476) AS trans_id, IsNull(f.profit,0) AS profit
FROM financial_data f
RIGHT JOIN financial_data ff on ff.factor_id = f.factor_id
LEFT variant:
SELECT ff.factor_id, IsNull(f.trans_id,476) AS trans_id, IsNull(f.profit,0) AS profit
FROM financial_factors ff
LEFT JOIN financial_data f on ff.factor_id = f.factor_id
Results:
factor_id
trans_id
profit
1
476
24
2
476
22
3
476
19
4
476
0
Defaulting a column to zero is pretty safe, however defaulting to a specific value really should only be done in conjunction with filtering, so I assume the default of trans_id=476 is because this is the filter you are applying to the tables, in which case the following parameterised query might be more useful:
DECLARE #transId INT = 476
SELECT ff.factor_id
, IsNull(f.trans_id, #transId) AS trans_id
, IsNull(f.profit, 0) AS profit
FROM financial_data f
RIGHT JOIN financial_data ff on ff.factor_id = f.factor_id
WHERE f.trans_id = #transId;
You could also use a UNION between two separate queries, if you can construct a query to represent the missing rows, in this case however an OUTER JOIN is likely to be the simpler approach.
I think you just want left join:
select ff.factor_id, v.trans_id, coalesce(f.profit, 0) as profit
from financial_factors ff cross join
(values (476)) v(trans_id) left join
financial f
on f.factor_id = ff.factor_id and
f.trans_id = v.trans_id;

Mapping columns in oracle-sql

Table:
column 1 column 2 column 3
2 two 3
5 five 8
3 three 10
8 eight 11
12 one 15
I want to create a new column column 4like below:
column 1 column 2 column 3 column 4
2 two 3 three
5 five 8 eight
3 three 10
8 eight 11
12 one 15
I want to map column 3 and column 1 and if there's a match column 4 takes values of column 2.
Example: Value 3 in column 3 is present in column 1, so column 4 will take corresponding column 3 value three.
Thanks!
This looks like a left join:
select t.*, tt.column2 as column4
from table1 t left join
table1 tt
on t.column3 = tt.column1;
EDIT:
If you want to set the value, you can use update:
update table1 t
set column4 = (select tt.column2 from table1 tt where t.column3 = tt.column1)
where exists (select 1 from table1 tt where t.column3 = tt.column1);
However, this seems silly. You can easily get the value in the table using an explicit join or hiding the logic in a view.

SQL: How to add values according to index columns

I have an sql table which looks like the following:
|value| |position| |relates_to_position| |type|
100 | 2 | NULL | 1
50 | 6 | NULL | 2
20 | 7 | 6 | 3
From this I need to create the resulting table, which adds all the lines with a |relates_to_position| field to the line which has |position| = |relates_to_position|.
For the above table, this would be
|value| |position| |relates_to_position| |type|
100 2 NULL 1
70 6 NULL 2
I am quite a newbie in SQL, so I would be glad for help. The database I use is Oracle XE 11. There will only be a single level of relates_to_position, meaning, that if relates_to_position is set, no other line will reference to this line.
If we only assume 1 level of hierarchy. If multiple level's of hierarchy this gets more interesting.
SELECT A.Value+coalesce(B.Value,0) as Value
, A.Position
, A.Relates_to_Position
, A.Type
FROM Table A
LEFT JOIN Table B
on B.Relates_To_Position = A.Position
WHERE A. Relate_to_Position is null
What this does is a self join so it puts related records on the same row. it then eliminate all those records with a value in relate_to_position as they will be added to a parent row.
we use a LEFT join because not all records will have a related value and we use coalesce to ensure null's are not attempted to be added. (coalesce takes the first non-null value)
Not sure why you need relates_To_Position returned as it will ALWAYS be null..
If you can have more than one level of hierarchy and they all need to sum up to the root position, then the following ought to do the trick:
WITH sample_data AS (SELECT 100 VALUE, 2 position, NULL relates_to_position, 1 TYPE FROM dual UNION ALL
SELECT 50 VALUE, 6 position, NULL relates_to_position, 2 TYPE FROM dual UNION ALL
SELECT 20 VALUE, 7 position, 6 relates_to_position, 3 TYPE FROM dual UNION ALL
SELECT 10 VALUE, 8 position, 7 relates_to_position, 3 TYPE FROM dual)
SELECT SUM(VALUE) VALUE,
root_position position,
root_type TYPE
FROM (SELECT value,
position,
TYPE,
connect_by_root(position) root_position,
connect_by_root(TYPE) root_type
FROM sample_data
CONNECT BY PRIOR position = relates_to_position
START WITH relates_to_position IS NULL)
GROUP BY root_position,
root_type;
VALUE POSITION TYPE
---------- ---------- ----------
100 2 1
80 6 2

Joining multiple values from one table to a shared value on another table

I apologize if this problem has been posted, but I don't really know how to describe it properly except to use an example.
To simplify my problem, I've created the following tables.
Essentially, I would like to link
FRUIT.COLOUR_ID to COLOURSHAPES.VALUE
FRUIT.SHAPE_ID to CLOURSHAPES.VALUE
However, what I want to display is COLOURSHAPES.VALUE as 2 separate columns.
In addition, I would like to have each fruit displayed as many times as their availabilities exist.
FRUITNAME COLOUR SHAPE AVAILABILITY
ORANGE ORANGE ROUND METRO
ORANGE ORANGE ROUND LOBLAWS
TABLE #1: FRUIT
FRUIT_ID FRUITNAME COLOUR_ID SHAPE_ID
1 ORANGE 10 20
2 BANANA 11 21
3 APPLE 12 20
4 PEAR 13 20
TABLE #2: COLOURSHAPES
VALUE DESCRIPTION
10 ORANGE
11 YELLOW
12 RED
13 BROWN
20 ROUND
21 LONG
TABLE #3: AVAILABILITY
FRUIT_ID STORE
1 METRO
1 LOBLAWS
2 FRESHCO
3 METRO
4 FRESHCO
You can join to Table2 twice, once to get colour and again to get shape.
select T3.FRUIT_ID, T1.FRUIT_NAME, T3.STORE, T2A.DESCRIPTION, T2B.DESCRIPTION
from TABLE3 T3
join TABLE1 T1
left join TABLE2 T2A on T1.COLOUR_ID=T2A.VALUE
left join TABLE2 T2B on T1.SHAPE_ID=T2B.VALUE

Multiple columns from a table into one, large column?

I don't know what in the world is the best way to go about this. I have a very large array of columns, each one with 1-25 rows associated with it. I need to be able to combine all into one large column, skipping blanks if at all possible. Is this something that Access can do?
a b c d e f g h
3 0 1 1 1 1 1 5
3 5 6 8 8 3 5
1 1 2 2 1 5
4 4 2 1 1 5
1 5
there are no blanks within each column, but each column has a different number of numbers in it. they need to be added from left to right so a,b, c, d, e, f. And the 0 from be needs to be in the first blank cell after the second 3 in A. And the first 5 in H needs to be directly after the 1 in g, with no blanks.
So you want a result like:
3
3
0
5
1
4
1
6
1
4
etc?
Here is how I would approach the problem. Insert your array into a work table with an autonumber column (important to retain the order the data is in, databases do not guarnatee an order unless you can give them something to sort on) called id
as well as the array columns.
Create a final table with an autonumber column (see above note on why you need an automnumber) and the column you want as you final table.
Run a separate insert statment for each column in your work table and run them in the order you want the data.
so the inserts would look something like:
insert table2 (colA)
select columnA from table1 order by id
insert table2 (colA)
select columnB from table1 order by id
insert table2 (colA)
select columnC from table1 order by id
Now when you do select columnA from table2 order by id you should have the results you need.