Combining tables where some keys are in common and some are missing - sql

I have a table of landcover values in my postgres db (version 9.3). The key values are "huc12code", which represent a spatial area. This is the first entry of the landcover table (it has 1700+ rows):
huc12code | open_water | developed | bare_earth | managed | heritage
030402080205 | 0.107027 | 0.0215444 | 0.406911 | |
The area of managed and heritage land is empty in this table but are stored in separate tables. They also have huc12code as the first column:
From the temp_heritage table:
huc12code | heritage
-------------+-------------
030101020801 | 0.0402684
From temp_managed table:
huc12code | managed
-------------+-------------
030101020802 | 0.000385026
I'd like to incorporate the managed & heritage fields into my landcover table, but I see two problems:
Most of the huc12code fields in the landcover table are found in temp_managed and temp_heritage, but some are not. This is because the "temp" tables don't include places where the heritage and managed fields equal 0.
I do want the landcover table to include a 0 value where the heritage and managed fields are 0, but since these values aren't actually present in the "temp" tables, I need some logic that inserts a zero for the heritage field for each huc12code that is found in landcover but not in temp_heritage (and the same for the manage field).

Assuming huc12code to be unique in all tables.
Only update rows where values are available
To do it all in a single UPDATE join the two source tables with a FULL [OUTER] JOIN to preserve all rows. Then use this in the FROM clause of the UPDATE command:
UPDATE landcover l
SET managed = COALESCE(t.managed, 0)
, heritage = COALESCE(t.heritage, 0)
FROM (
SELECT huc12code, m.managed, h.heritage
FROM temp_managed m
FULL JOIN temp_heritage h USING (huc12code)
) t
WHERE l.huc12code = t.huc12code;
Every row in landcover is updated exactly one time if there is a (combined) row in the source. The rest is not touched at all.
Update all rows
Use two LEFT JOIN instead:
UPDATE landcover l
SET managed = COALESCE(t.managed, 0)
, heritage = COALESCE(t.heritage, 0)
FROM (
SELECT l.huc12code, m.managed, h.heritage
FROM landcover l
LEFT JOIN temp_managed m USING (huc12code)
LEFT JOIN temp_heritage h USING (huc12code)
) t
WHERE l.huc12code = t.huc12code;
Columns without a source or source NULL value are set to 0.

Related

Do a specific query for each row of a table, one by one

Let's say I have a table:
| key1 | key2 | value |
+------+------+-------+
| 1 | 1 | 1337 |
| 1 | 2 | 6545 |
| 2 | 1 | 213 |
| 3 | 1 | 131 |
What I would like to do is traverse this table row by row, then using the key two values in further queries (all other tables contain the unique combination of these two keys + other data)
How do I do this kind of thing in SQL?
EDIT: I would want to extract key1, key2 from row 1 (1,1) then do a query on it, which would result in a number.
Then I would move to the second row, an identical query which would again result in a number.
All of these numbers would be then inserted into a pre-prepared view.
EDIT2: I need to traverse it because the specific use of my database.
It is a database of planets which contains sectors (the keys are the IDs of these two). All of these sectors contain resources, turrets and walls.
The table I have in my post is an example of table of sectors, with the value being enemy force.
Table of resources, turrets etc. contain these two keys so they are linked to and only to a specific sector.
I need to go row by row so I can use this keys to select only specific resources/turrets/walls from my tables, aggregate them and then subtract them from the value in my sector table. Resulting number would then be inserted into a pre-prepared view (again, into the row which matches the combination of my two keys)
This sounds like a correlated subquery or lateral join. You don't have that much explanation, but something like this:
select t1.*, t2.*
from table1 t1 cross join lateral
(select . . .
from table2 t2 . . .
where t2.key1 = t1.key1 and t2.key2 = t1.key2
) t2
You are not clear on what the second query looks like. The where clause is called a correlation clause. It connects the subquery to the outer query. A correlation clause is not strictly needed for this to work.
The columns from the outer query can be used elsewhere in the subquery. I am just assuming that an equality condition connects the two (lacking other information).

Left join - two tables with the same data

Lets say that that I have two simple tables with the following columns and data:
Table 1 Table 2
year month year month
2017 01 2017 01
2016 12 2016 12
The primary key is a composite key that consists of the year and the month.
So a classical left join, gives me all the data in the left table with the matching rows in the right table.
If I do a left join like this:
select
t1.year, t2.month
from
table1 t1
left join table 2 t2 on (t1.year = t2.year and t1.month = t2.month)
Why do I get only two rows?? Shouldn't I get 4 rows??
Tnx,
Tom
A classical left join will give you the number of rows in the "Left Table" (the one in from) multiplied by the number of matches in the "Right Table" (the one in LEFT JOIN in this case), plus all the rows in the LEFT Table that have no match in the first table.
Number of rows in LEFT Table = 2
Number of matches in Right Table = 1
Number of rows in LEFT Table withouth matches = 0
2 x 1 + 0 = 2
Edit: Actually the multiplication is given for each row. Would be something like
Sum (row_i x matches_i) + unmatched
Where row_i is means each row, and matches_i to the matches for the i row in the first table. The difference with this is that each row could have different number of matches (the previous formula is only adapted to your case)
This will result in
1 (row1) x 1 (matches for row 1) + 1 (row2) x 1 (matches for row 2) +
0 (unmatched rows in table 1) = result
1x1 + 1x1 + 0 = result
1 + 1 = 2 = result
If you expected 4 rows maybe you wanted to get a Cartesian Product. As the comment stated, you can use Cross Join in that case
When you join tables together, you're essentially asking the database to combine data from two different tables and display it as a single record. When you perform a left join, you are saying:
Give me all the rows from Table1, as well as any associated data from
Table2 (if it exists).
In this sense, the data from Table2 doesn't represent separate or additional records to Table1 (even though they are stored as separate records in a separate table), it represents associated data. You are linking the data between the tables, not appending rows from each table.
Imagine that Table1 stored people, and Table2 stored phone numbers.
Table1 Table2
+------+-------+--------+ +------+-------+-------------+
| Year | Month | Person | | Year | Month | Phone |
+------+-------+--------+ +------+-------+-------------+
| 2017 | 12 | Bob | | 2017 | 12 | 555-123-4567|
| 2016 | 01 | Frank | | 2016 | 01 | 555-234-5678|
+------+-------+-------+ +------+-------+--------------+
You could join them together to get a list of people and their corresponding phone numbers. But you wouldn't expect to get a combination of rows from each table (two rows of people and two rows of phone numbers).
You will get two rows as both the columns have 2 rows that match exactly the sam and its a composite key.
It will make the same way if you had 4 rows in each you will only get 4 rows in total.
The Left Join takes Table1 (t1) as the Left table.
It searches for and retrieves all values from the Right ie:- from Table 2 (t2) matching the criteria T1.Year&Month = T2.Year&Month (alias GOD/s) as well as the additional join condition T1.Month=T2.Month. The result is that only 2 rows from T1 match the join criteria as well as the additional join criteria
Another takeaway : The AND T1.Month=T2.Month condition on the left join is redundant as the composite GOD key takes care of it explicitly.
cross join returns every row you can make by combining a row from each argument. (inner) join on returns the rows from cross join that satisfy its condition. Ie (inner) join on returns every row you can make that combines a row from each argument and that satisfies its condition.
left join on returns the rows from (inner) join on plus the rows you can make by extending unjoined left argument rows by null for columns of the right argument.
Notice that this is regardless of primary keys, unique column sets, foreign keys or any other constraints.
Here there are 2 rows in each argument so there are 2 X 2 = 4 rows in the cross join. But only 2 meet the condition--the ones where a row is combined with itself.
(If you left join a table with itself where the condition is the conjunction of one or more equalities of the left and right versions of a column and there are no nulls in those columns then every left argument row gets joined with at least itself from the right argument. So there are no unjoined left argument rows. So only the rows of the (inner) join on are returned.)

Select data from three different tables with null data

I am new in Sql. My question is how to get data from three different tables with null values.
I have tried a query as below:
SELECT *
FROM [USER]
JOIN [Location] ON ([Location].UserId = [USER].Id)
JOIN [ParentChild] ON ([ParentChild].UserId = [USER].Id) WHERE ParentId=7
which I find from this link.
Its working fine but, it not fetches all and each data associated with the ParentId
Something like it only fetches data which are available in all tables, but also omits some data which not available in Location tables but it comes under the given ParentId.
For example:
+----------+-------------+
| UserId | ParentId |
+----------+-------------+
| 1 | 7 |
+----------+-------------+
| 8 | 7 |
+----------+-------------+
For userId 8, there is data available in Location table,so it fetches all data. But there is no data for userId 1 available in Location table, so the query didn't work for this.
But I want all and every data.
If there is no data for userId then it can return only null columns.
Is it possible ??
hope everyone can understand my problem.
If you always want to return a list of users, but some may not have locations then you want to change the type of join from an "Inner Join" (or as you have used the short hand "JOIN") to a "Left Join".
SELECT *
FROM [USER]
INNER JOIN [ParentChild] ON ([ParentChild].UserId = [USER].Id)
LEFT JOIN [Location] ON ([Location].UserId = [USER].Id)
WHERE ParentId=7
This doesn't account for users that do not have a parent. If you still want to return users who do not have a parent then you would then need to change the JOIN type to your ParentChild table to a LEFT join also.

Update a single table based on data from multiple tables SQL Server 2005,2008

I need to update table one using data from table two. Table one and two are not related by any common column(s). Table three is related to table two.
Ex : table one(reg_det table)
reg_det_id | reg_id | results
101 | 11 | 344
table two :(temp table)
venue | results
Anheim convention center | 355
Table three (regmaster-tbl)
reg_id| venue
11 | Anaheim convention center
I need to update results column in table one using data from table two. But table one and two are not related. Table two and three and table one and three are related as you can see above. Can anyone please suggest any ideas! I need the results value to be 355 in table one and this data is coming from table 2, but these two are unrelated, and they can be related using table three. Sorry if it is confusing!
Fairly straight forward:
UPDATE T1
SET result = t2.results
FROM [table one] T1
INNER JOIN [table three] t3
on t1.reg_id = t3.reg_id
INNER JOIN [table two] T2
on t2.venue = t3.venue
Almost a question instead of an answer. :)
Couldn't you use an implied inner join?
UPDATE rd
SET rd.results = tt.results
FROM reg_det rd, regmaster rm, temptable tt
WHERE rm.reg_id = rd.reg_id
AND rm.venue = tt.venue;
I find it easier to read, and this syntax works in a SELECT statement, with the same meaning as an explicit inner join.
Try this:
UPDATE rd
SET rd.results = t.results
FROM reg_det rd
JOIN regmaster rm ON rm.reg_id = rd.reg_id
JOIN temptable t ON t.venue = rm.venue
WHERE t.results = 355
I added a WHERE clause because otherwise it will update all reg_det records that have matches in regmaster and temptable.

What kind of SQL join do I need to compress a One to Many relationship into the same view row?

Edit: this isn't to be a dynamic output, the output view structure is fixed.
I am trying to create a SQL Server view that shows a single fixed column row for each user, and flattens out an associated one to many table into that row.
Although the associated table has a one to many relationship, the output table structure is limited to 4 elememts form that table.
My table structure is like so:
User (Id, FirstName, LastName)
Assessment (Id, Date, Location, User_Id)
Topics (Id, Topic, Assessment_Id)
Where the Assessment is joined to the User by the User_Id (One 2 One), and the Topics are joined to the Assessment by the Assessment_Id.
So, if I have three topics for an assessment, I'd want the view to look something like:
User_Id | FirstName | LastName | Date | Location | Topic1 | Topic2 | Topic3 | Topic4 |
1 | dave | toby | 2/2/11 | In situ | apples | pears | lemons | NULL |
My current SQL looks like this:
SELECT User.Id, User.FirstName, User.LastName, Assessment.Date, Assessment.Location, Topic.Topic
FROM User LEFT OUTER JOIN
Assessment INNER JOIN
Topic ON Assessment.Id = Topic.Assessment_Id ON
User.Id = Assessment.User_Id
But this returns a row for each concern - it doesn't compress them to one line. I've played with a few different joins, but haven't been able to get the behaviour I want.
Is it possible to do this in a view?
What do I need to do to make it happen??
Thanks!
There is no such JOIN. SQL has a fixed column output: so you can't add arbritrary numbers of columns. It doesn't matter if it's a view, direct or in a stored procedure.
There are 2 main options
concatenate the many rows into one column which is a popular questions here on SO. One random solution using XML PATH
use dynamic SQL to add a column per row in a stored procedure.
Note: PIVOT is fixed column output too
Edit: for a maximum of 4 child rows
SELECT
P.col1, P.col2,
C1.col1 AS Topic1,
C2.col1 AS Topic2,
C3.col1 AS Topic2,
C4.col1 AS Topic4
FROM
Parent P
LEFT JOIN
Child C1 ON P.Key = C1.FKey AND C1.ID = 1
LEFT JOIN
Child C2 ON P.Key = C2.FKey AND C2.ID = 2
LEFT JOIN
Child C3 ON P.Key = C3.FKey AND C3.ID = 3
LEFT JOIN
Child C4 ON P.Key = C4.FKey AND C4.ID = 4
You can use PIVOT too but I prefer the simpler self joins.
Take a look at PIVOT table functionality - e.g. http://www.help-sql.info/27/9/610208.html and http://blog.sqlauthority.com/2008/05/22/sql-server-pivot-table-example/
Although you will need to know the AssessmentId's before you can write the PIVOT