How to Map rows of same table based on child table values? - sql

I need to write SQL that generates a table or view that contains a mapping of one entity to another entity, where the entities belong to the same table, and the mapping conditions are based on their children's values. Say I have the following schema:
I need to be able to creating a mapping between two different Entity IDs (call them A and B) based on the following conditions:
The Category.Name value of the Category linked to the Person linked to Entity A must have the same Category.Name value of the Category linked to the Person linked to Entity B ("The validation names of each entity must match")
The Verification linked to Entity A must have the same VerificationValue as the Verification linked to Entity B ("The Verification values of the two entities must match")
The VerificationType.Name linked to the verification that is linked to Entity A must be the same as the VerificationType.Name that is linked to the Verification that is linked to Entity B ("The verification types of each entity must match")
The end result would be something like this as a TABLE or VIEW:
entity_ID_A | entity_ID_B
--------------------------
1 2
3 4
11 10
Assume for simplicity's sake that we cannot have more than one value mapped to entity_ID_A
In code, this could be expressed simply as:
Entity a, b = //do some stuff to get the two different entities
return a.person.name == b.person.name &&
a.verification.verificationValue == b.verificationValue &&
a.verification.verificationType.name == b.verification.verificationType.Name;
I'm not even sure where to begin expressing this in SQL, much less generate a table of mappings of entity IDs that meet this criteria. Do I join all of the tables together before doing any comparisons? Any pointers in the right direction would be appreciated

Yes. Join all tables at first and then make self-join:
with ents as (
select e.id, c.name cname, v.verificationvalue vval, vt.name vname
from entity e
join person p on e.person_id = p.id
join category c on c.id = p.category_id
join verification v on v.id = e.verification_id
join verificationtype vt on vt.id = v.verificationtype_id)
select a.id id_a, b.id id_b
from ents a
join ents b on a.id < b.id
and a.cname = b.cname
and a.vval = b.vval
and a.vname = b.vname

Related

Problem with JOINs on selection of related data

I have three entity types (let's call them A, B, C) persisted on three tables of my database.
Each entity type has a relation with the other two entities. Relations are persisted in three tables of the DB as well (let's call them AB, AC, BC), where every record is a couple of IDs of the respective entities.
Relations A-B are one-to-many and are mandatory: every A has at least one relation with a B, every B has a relation with an A.
Relations A-C and B-C are many-to-many and are optional: there can be As without relation with Cs, there can be Bs without relations with Cs.
I cannot change this schema.
I need to build a table with all the As and their related data. Every row of the table must contain only related data or NULLs where there are no relations.
I thought I would be fine with something like:
SELECT * -- let's omit columns for simplicity
FROM AB
LEFT JOIN BC ON BC.IdB = AB.IdB
LEFT JOIN AC ON AC.IdA = AB.IdA AND
AC.IdC = BC.IdC
INNER JOIN A ON A.Id = AB.IdA
INNER JOIN B ON B.Id = AB.IdB
LEFT JOIN C ON C.Id = AC.IdC
and then filtering with a WHERE clause. My problem is I don't get how, which makes me think I am approaching the problem in the wrong way.
Any hint would be appreciated, thank you in advance.

Left Join - attempting to identify instances where child records do not exist, or if they do exist, populate unique records meeting specific criteria

I am attempting to search an SQL database for instances where child records do not exist, or if they do exist, they are a specific type (e.g. Historical) and no other types exist (e.g. Current):
SELECT distinct parent.id FROM parenttable
Left Join childtable On childtable.primarykey = parenttable.primarykey
Where childtable.id is null
This populates all of my parent records that do not have any child records without any issues. However, I also want the query to populate instances where the only child records that exist are historical (e.g. childtable.type = 'Historical'). I have not been able to do this thus far.
You can just use not exists:
select p.id
from parenttable p
where not exists (
select 1
from childtable c
where c.primarykey = p.primarykey and c.type <> 'Historical'
)
This phrases as: get all records from the parent table that have no child whose type is other than "Historical" - parents without a child do satisfy this condition too.
Try this please (using operator In):
SELECT id FROM parenttab WHERE primarykey NOT IN
(SELECT primarykey FROM childtable WHERE type<>'Historical'
OR type IS NULL)
It is more fast than to find a match for every row in subquery

SQL set value for case depending on whether or not value exists

I am looking up addresses from one table from a user based on the user's email from another table, then take the resulting list of addresses and check if the address has an attribute that is stored on a third table. The attribute may or may not exist on the third table. If it does, I want the sql to print out, "Attribute Exists" and if it doesn't, print out, "Attribute Doesn't Exist"
The attribute is linked to the address by the address id and the user is linked to the address by the user id.
Here is what I have so far:
select b.street, case
when c.entity_id = b.entity_id and c.attribute_id = 100 then 'Attribute Exists'
else 'Attribute Doesn't Exist'
end as isValue
from customer_entity as a, //Customer Details
customer_address_entity as b, //Address Details
customer_address_entity_int as c //Address Attribute
where a.email = 'customeremail#example.com'
and a.entity_id = b.parent_id
The problem I am having with this particular setup is in table c. If I include it, the 3 addresses I am attempting to get this information from loop around the same number of times as the number of attributes I have stored in table c (in this case, 10 times as there are 10 records in table c, so I get 30 results when I only want 3).
I can't filter the results in table c as there may or may not be a match, but I want to print a result either way. I also need table c for the select case as well. If I get rid of table c, then only the three addresses I want results for show up, but then I can't compare the values in table c.
In short, this is what I need printed out:
street isValue
Street 1 Attribute Exists
Street 2 Attribute Exists
Street 3 Attribute Doesn't Exist
I think your query would be easier to understand written this way:
select distinct
b.street,
case
when c.attribute_id = 100 then 'Attribute Exists'
else 'Attribute Doesn''t Exist'
end as isValue
from customer_entity as a //Customer Details
join customer_address_entity as b //Address Details
on a.entity_id = b.parent_id
left join customer_address_entity_int as c //Address Attribute
on c.entity_id = b.entity_id
where a.email = 'customeremail#example.com'
You can join b with c. If c.attribute=100 it's because the records joined, so if not this field will be always NULL.
I included a distinct because of the left join with c.

How to branch SQL query statement using conditions?

Let's say we have table customer with column subject_type_fk. We need to find name of the customer which can be in table person or enterprise. subject_type_fk defines which of those 2 tables search in. subject_type_fk can be 0 or 1. subject_fk defines the primary key of record from person or enterprise. Both tables have name column we need to retrieve (actually enterprise.enterprise and person.last_name). Customer.customer is given as input parameter. How to write that kind of query for postgreSQL?
UPDATE
select p.last_name
from customer c join person p on c.subject_fk = p.person and c.subject_type_fk = 0
union
select e.full_name
from customer c join enterprise e on c.subject_fk = e.enterprise and c.subject_type_fk = 1
(edited after OP's edit)

SQL mapping between multiple tables

This is a SQL design question. First, the setup. I have three tables:
A, which is automatically populated based on a query against a linked server. The data in this table cannot be changed;
B, which has just a dozen or so rows, containing the names for collections of As;
AtoB, which is the mapping table by which As are organized into named collections, with foreign keys on both columns;
For example, A contains:
Giraffe
Owl
Tiger
And B contains:
Seattle Zoo
San Jose Zoo
And AtoB contains:
1,1 (Giraffe in Seattle)
2,1 (Owl in Seattle)
3,1 (Tiger in Seattle)
2,2 (Owl in San Jose)
Now, the problem:
I've been asked to include in some of these collections items not found in A. So, I create a table, C, with the same identity and Name columns as A, and populate it. In keeping with the earlier example, let's say C contains:
Dragon
The question is, how do I include items from C in AtoB? What if I need to include a Dragon in the Seattle Zoo?
My first instinct, being naive, was to create a view V containing the union of A and C, and modifying AtoB to be VtoB. That's where my naivety paid off: one cannot create a foreign key to a view.
I suspect that there's a standard, correct means of relating one or more A OR C with a B.
To expand on Arthur Thomas's solution here's a union without the WHERE in the subselects so that you can create a universal view:
SELECT A.Name as Animal, B.Name as Zoo FROM A, AtoB, B
WHERE AtoB.A_ID = A.ID && B.ID = AtoB.B_ID
UNION
SELECT C.Name as Animal, B.Name as Zoo FROM C, CtoB, B
WHERE CtoB.C_ID = C.ID && B.ID = CtoB.B_ID
Then, you can perform a query like:
SELECT Animal FROM zoo_animals WHERE Zoo="Seattle Zoo"
If you can't put a Dragon in A then you will need to create another table and another link table. The problem is creating a unique set of data that needs to be stored (another table) that cannot be the same set as A. Since it isn't the same set then you can no longer use the link table (AtoB) which has foreign keys that ensure that the link is a reference from set A. So you could create a tables like this:
imaginary_creatures
id
name
imaginary_creatures_to_b
imaginary_creatures_id (link to imaginary_creatures table)
b_id (link to zoos table)
Later when you want to get all creatures in a zoo you can do a UNION
SELECT A.Name FROM A where A.ID IN
(SELECT AB.A_ID FROM AtoB AB WHERE B_ID =
(SELECT B.ID FROM B WHERE B.Name = 'Zoo Name'))
UNION
SELECT i.name FROM imaginary_creatures i i.id IN
(SELECT ic.imaginary_creatures_id FROM imaginary_creatures_to_c ic
WHERE ic.b_id = (SELECT B.ID FROM B WHERE B.Name = 'Zoo Name'))
There may be a better way of writing that, but it should work for your purposes.
Arthur Thomas has a good solution, the other possible solution is to add a column to the link table indicating which table (A or C) it is related to. Then enforce the relationships through triggers rather than foreign keys. But really Arthur's solution is the preferred way of doing this sort of thing.
What you want to do is put Dragon in A, and if you want to select ALL records from A regardless of if they have a matching record in AtoB, do a LEFT OUTER JOIN. Something like this:
SELECT * FROM A
LEFT OUTER JOIN AtoB
ON A.id = AtoB.A_ID
Edit: This would only work if you could add your new records to A. I missed the fact that you are not able to. I think Arthur Thomas's solution is what you want.
Truncate the table dept_details
Display the structure of the table emp_details
Convert the first letter of emp_name into capitals.