Postgres update with an inner join across 3 tables? - sql

This morning I asked this very similar question, and it was answered beautifully.
However, after reviewing the play, I see that my actual problem is slightly more complicated than what I described in that question. Basically, I have 3 Postgres tables:
[myschema].[animals]
--------------------
animal_id
animal_attrib_type_id (foreign key to [myschema].[animal_attrib_types])
animal_attrib_value_id (foreign key to [myschema].[animal_attrib_values])
[myschema].[animal_attrib_types]
--------------------------------
animal_attrib_type_id
animal_attrib_type_name
[myschema].[animal_attrib_values]
--------------------------------
animal_attrib_value_id
animal_attrib_value_name
So, I might have an animal record like so:
[myschema].[animals]
--------------------
animal_id = 458
animal_attrib_type_id = 38
animal_attrib_value_id = 23
And the corresponding animal_attrib_type (with id = 38) has the following values:
[myschema].[animal_attrib_types]
--------------------------------
animal_attrib_type_id = 38
animal_attrib_type_name = 'animals.should-make-noise'
And the corresponding animal_attrib_value (with id = 23) has the following values:
[myschema].[animal_attrib_values]
--------------------------------
animal_attrib_type_id = 23
animal_attrib_type_name = 'true'
So, the same animal record can have multiple type/value pairs. In this case the animal had an animal_attrib_type_name of "animals.should-make-noise" corresponding to an animal_attrib_value_name of "true".
At runtime, I will only have the animal_id (i.e, 458) and animal_attrib_type_id (i.e, 38). I need to be able to look up the appropriate animal_attrib_value_name corresponding to that given animal_id and animal_attrib_type_id only, and then update its value to some static text ('true' or 'false'); all from within the same UPDATE statement.
The answer in the above-referenced question was correct for the problem I stated, but since the same animal has 0+ type/value combos I actually need a slightly different SQL statement. Thanks in advance!

Make use of the FROM clause in the PostgreSQL UPDATE command. This is usually cleaner and faster.
UPDATE animal_attrib_values av
SET animal_attrib_value_name = 'true'
FROM animals a
WHERE a.animal_id = 458
AND a.animal_attrib_type_id = 38
AND a.animal_attrib_value_id = av.animal_attrib_value_id;
Since we already know the animal_attrib_type_id we don't have to include the third table animal_attrib_types at all. We could join to it additionally if needed ...
Also, do not table-qualify SET items in an UPDATE. That's a syntax error. I quote the manual on said page:
Do not include the table's name in the specification of a target
column — for example, UPDATE tab SET tab.col = 1 is invalid.
Bold emphasis mine.

The below SQL should do what you are asking:
UPDATE animal_attrib_values aav
SET animal_attrib_value_name= 'true'
WHERE aav.animal_attrib_value_id = (
SELECT a.animal_attrib_value_id
FROM animals a
WHERE a.animal_id = 458
AND a.animal_attrib_type_id = 38
)
;

Related

PostGIS Intersect to update a field based on intersected State

I'll admit I'm a little out of my element with PostGIS and spatial geometries in a DB but here's what I'm after: I need to update a field with the determined intersected US State of a geom of an object if it doesn't yet already have one.
The DB structure is as follows:
Accomplishment
id
name
phys_state
poly_point_line_id (fk Accomplishment_Feature)
1
Test Accomp 1
AK
123
2
Test Accomp 2
456
3
Test Accomp 3
789
Accomplishment_Feature (technically not needed in the query AFAIK but included here just in case since it is a join table between the Accomplishment and its geometry types)
id
123
456
789
Accoomplishment_Poly
id (fk to Accomplishment_Feature)
geom
123
[multipolygon geometry]
Accoomplishment_Line
id (fk to Accomplishment_Feature)
geom
123
[multiline geometry]
Accoomplishment_Point
id (fk to Accomplishment_Feature)
geom
123
[multipoint geometry]
I need to determine the intersected US state of each of the geoms for Accomplishments that don't have a value in the physical_state column.
I currently have a table of US State geometries in another schema that I can use.
I currently have the following but it errors out and I'm obviously misunderstanding how to write the queries.
UPDATE accomplishment a
SET a.phys_state = us_state.abbrev
FROM support_gis.state_g us_state
LEFT JOIN accomplishment_poly poly on a.poly_point_line_id = poly.id
WHERE st_intersects(st_centroid(poly.geom), us_state.geom) AND a.phys_state is null
Any guidance or assistance would be greatly appreciated!
How the FROM clause in an UPDATE statement works is slightly different than in a SELECT - go figure. An alternative is to go old school: just put all involved tables in the FROM clause and solve the joins in the WHERE clause instead using JOINs.
UPDATE accomplishment
SET phys_state = us_state.abbrev
FROM support_gis.state_g us_state, accomplishment_poly poly
WHERE
ST_Intersects(ST_Centroid(poly.geom), us_state.geom) AND
phys_state IS NULL AND
poly_point_line_id = poly.id;

Comparing 2 tables in rails 5

I have 2 tables "existing_practices" & "latest_practices", both contain a column "practice_id"
What I want to do is compare latest_practices with the existing_practices to find which practices are on the latest_practices table that I don't have on my existing_practices (in other words I need to find the new practices)
Example:
existing_practices latest_practices
------------------ ------------------
practice_id practice_id
A123 A123
B123 B123
C123 C123
D123
So given the 2 above tables I would need to identify that "D123" is a new practice.
I have tried the following but it doesn't seem to work:
existing_practices = ExistingPractice.select(:practice_id).all
latest_practices = LatestPractice.select(:practice_id).all
new_practices = latest_practices.to_a - existing_practices.to_a
I'm thinking the easiest way is to just write the raw sql but i want to do it the rails way (if there is one).
Can anyone help?
pluck used to fetch column value as array
new_practices = LatestPractice.pluck(:practice_id) - ExistingPractice.pluck(:practice_id)
You can use sql directly for better performance.
new_practices_id = ActiveRecord::Base.connection.execute("SELECT DISTINCT latest_practices.practice_id FROM latest_practices LEFT JOIN existing_practices ON latest_practices.practice_id =
existing_practices.practice_id WHERE existing_practices.practice_id IS NULL")
This will return an array of the practice_id that doesn't exist at the existing_practices table.
here is the fiddle example company two columns or tables
you can do it like this with pluck
new_practices = LatestPractice.pluck(:practice_id) - ExistingPractice.pluck(:practice_id)
If you have a lot of data the solution of plucking practice_id and subtracting them is poorly performing. Instead I suggest:
Okish:
LatestPractice.where.not(practice_id: ExistingPractice.all)
better:
LatestPractice.where.not("EXISTS(SELECT 1 from existing_practices where latest_practices.practice_id = existing_practices.practice_id)")
much better:
LatestPractice.where('practice_id NOT IN(SELECT DISTINCT(practice_id) FROM existing_practices)')

One to many relationship on the same table

Here is the situation:-
I have a table called Users. This contains user data for students and tutors as most of the data required is the same.
Having completed the system I am now told that the client would like to be able to assign students to tutors.
Is there a legitimate/ clean way I can create a one to many relationship within a single table, perhaps via a link table?
I've tried to think this through but whatever solution I come up with seems messy.
I would be grateful for any input.
Thanks
Phill
Have you tried the following approach?
Make a new table, for example TutorStudent (choose a more appropriate name if needed). It should have two columns:
Tutor_ID
Student_ID
Both columns shall be the (composite) primary key, each column will be a foreign key to your Users table User_ID (I assume this is what you have).
So, if you have a tutor named Newton that has two students, Tesla and Edison, your Users table will have something like this:
User_ID, Name
1, Newton
2, Tesla
3, Edison
and your TutorStudent table will have following values:
Tutor_ID, Student_ID
1, 2
1, 3
Relatively simple and doesn't require any modifications to your existing table.
Do take care when deleting users - use the delete cascade feature of your database system or do some maintenance work afterwards so your TutorStudent table doesn't go stale when updating/removing your users.
My ideal for the same situation
Example: one book have many category:
Basic solution:
book table has recorded book information
category table has recored category information ex: 100 documents
book_category_relation table has single book (book_id) has category(category_id) 1 book may be have 100 category_id
Ideal solution:
First calculate total your category: ex 100 document. Each category equal value 1 bit: max 31 bit so 100 category we have ceil floor(100%31) = 4 groups
category_id = 1 : 1 (1%31) <=> 000000001 group 0 = floor(1/31)
category_id = 2 : 2 (2%31)<=> 000000010 group 0 = floor(2/31)
category_id = 3 : 4 (3%31)<=> 000000100 group 0 = floor(3/31)
category_id = 4 : 8(4%31)<=> 000001000 group 0 = floor(4/31)
...........................
category_id = 31: 2^31(31%31) <=>1000..000 group 0 if moduler 31 equal zero so number group = (31/31 -1)=0;
category_id = 32: 1(32%31) <=> 0000000001 group 1 = floor(32/31)
category_id = 33: 2(33%31) <=> 0000000010 group 1 = floor(33/31)
Ok now we add 4 fields in design book table (group_0,group_1,group_2,group_3) with int(11) unsigned and add index that fields
if book has category id = n so we can the following calculate formula:
bit code = (n%31 ==0)?31: (n%31)
number group field = (n%31==0)?(n/31 -1):floor(n/31)
ex: book in category_id = 100 so:
bit code = (100%31) =7 <=>2^7 = 128,
group = floor(100%31) = 3 <=> in group_3
so if you need query all book in category_id = 100, query string is:
SELECT * FROM book WHERE group_3&128
Note: MySQL not index working if bitwise in where.
But you can check in this link:
Bitwise operations and indexes

Postgres update with an inner join across 2 tables?

I have 3 tables in my local Postgres database:
[myschema].[animals]
--------------------
animal_id
animal_attrib_type_id (foreign key to [myschema].[animal_attrib_types])
animal_attrib_value_id (foreign key to [myschema].[animal_attrib_values])
[myschema].[animal_attrib_types]
--------------------------------
animal_attrib_type_id
animal_attrib_type_name
[myschema].[animal_attrib_values]
--------------------------------
animal_attrib_value_id
animal_attrib_value_name
At runtime I will know the animal_id. I need to run SQL to update the animal_attribute_value_name associated with this item, so something like:
UPDATE
animal_attribute_values aav
SET
aav.animal_attribute_value_name = 'Some new value'
WHERE
# Somehow join from the provided animal_id???
I may have to do some kind of nested SELECT or INNER JOIN inside the WHERE clause, but not sure how to do this. Thanks in advance!
Edit:
Let's say I have an animal record with the following values:
[myschema].[animals]
--------------------
animal_id = 458
animal_attrib_type_id = 38
animal_attrib_value_id = 23
And the corresponding animal_attrib_value (with id = 23) has the following values:
[myschema].[animal_attrib_values]
--------------------------------
animal_attrib_value_id = 23
animal_attrib_value_name = 'I am some value that needs to be changed.'
At runtime, I only have the animal_id (458). I need to look up the corresponding animal_attrib_value (23) and change its animal_attrib_value_name to 'Some new value', all inside of a single UPDATE statement.
UPDATE
animal_attribute_values aav
SET
animal_attribute_value_name = 'Some new value'
FROM animals aa
WHERE aa.animal_id = 458
AND aa.animal_attrib_value_id = aav.animal_attrib_value_id
;
are you asking something like this right..?
update animal_attribute_values aav
set aav.animal_attribute_value_name = 'Some new value'
where aav.animal_attrib_value_id in (
select a.animal_attrib_value_id where a.animal_id=458)
try this..

How to make SQL query that will combine rows of result from one table with rows of another table in specific conditions in SQLite

I have aSQLite3 database with three tables. Sample data looks like this:
Original
id aName code
------------------
1 dog DG
2 cat CT
3 bat BT
4 badger BDGR
... ... ...
Translated
id orgID isTranslated langID aName
----------------------------------------------
1 2 1 3 katze
2 1 1 3 hund
3 3 0 3 (NULL)
4 4 1 3 dachs
... ... ... ... ...
Lang
id Langcode
-----------
1 FR
2 CZ
3 DE
4 RU
... ...
I want to select all data from Original and Translated in way that result would consist of all data in Original table, but aName of rows that got translation would be replaced with aName from Translated table, so then I could apply an ORDER BY clause and sort data in the desired way.
All data and table designs are examples just to show the problem. The schema does contain some elements like an isTranslated column or translation and original names in separate tables. These elements are required by application destination/design.
To be more specific this is an example rowset I would like to produce. It's all the data from table Original modified by data from Translated if translation is available for that certain id from Original.
Desired Result
id aName code isTranslated
---------------------------------
1 hund DG 1
2 katze CT 1
3 bat BT 0
4 dachs BDGR 1
... ... ... ...
This is a typcial application for the CASE expression:
SELECT Original.id,
CASE isTranslated
WHEN 1 THEN Translated.aName
ELSE Original.aName
END AS aName,
code,
isTranslated
FROM Original
JOIN Translated ON Original.id = Translated.orgID
WHERE Translated.langID = (SELECT id FROM Lang WHERE Langcode = 'DE')
If not all records in Original have a corresponding record in Translated, use LEFT JOIN instead.
If untranslated names are guaranteed to be NULL, you can just use IFNULL(Translated.aName, Original.aName) instead.
You should probably list the actual results you want, which would help people help you in the future.
In the current case, I'm guessing you want something along these lines:
SELECT Original.id, Original.code, Translated.aName
FROM Original
JOIN Lang
ON Lang.langCode = 'DE'
JOIN Translated
ON Translated.orgId = Original.id
AND Translated.langId = Lang.id
AND Translated.aName IS NOT NULL;
(Check out my example to see if these are the results you want).
In any case, the table set you've got is heading towards a fairly standard 'translation table' setup. However, there are some basic changes I'd make.
Original
Name the table to something specific, like Animal
Don't include a 'default' translation in the table (you can use a view, if necessary).
'code' is fine, although in the case of animals, genus/species probably ought to be used
Lang
'Lanugage' is often a reserved word in RDBMSs, so the name is fine.
Specifically name which 'language code' you're using (and don't abbreviate column names). There's actually (up to) three different ISO codes possible - just grab them all.
(Also, remember that languages have language-specific names, so language also needs it's own 'translation' table)
Translated
Name the table entity-specific, like AnimalNameTranslated, or somesuch.
isTranslated is unnecessary - you can derive it from the existence of the row - don't add a row if the term isn't translated yet.
Put all 'translations' into the table, including the 'default' one. This means all your terms are in one place, so you don't have to go looking elsewhere.