SQL mapping between multiple tables - sql

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.

Related

How do I find the name of someone who has multiple entries in a different table

I'm trying to figure out how to get through this type of scenario:
I have a Table A with names of people along with their IDs. I have a Table B with the people's IDs and the type of car that they bought from this dealership. Multiple people can have multiple entries if they buy multiple cars.
Let's say that David with ID 789 has bought multiple cars from this dealership, so he has multiple entries in Table B:
(ID | Car)
(----|----)
(789 | Toyota)
(789 | Ford)
I want to query these tables so that my results show all of the people who have bought a Toyota, but not a Ford.
SELECT name, id
FROM TableA a
JOIN TableB b ON a.id = b.id
WHERE a.id IN (SELECT b.id where Car = 'Toyota') AND
a.id NOT IN (SELECT b.id where Car = 'Ford')
I want to understand why this code does not bring back the ids who have bought a toyota but not a ford, even if they bought multiple cars? What about the logic am I missing?
Thanks in advance.
To start with the why it isn't working, SQL is evaluating the WHERE clause sections in pieces. When it evaluates WHERE a.id IN (SELECT b.id where Car = 'Toyota'), all that remains in the table is records where that ID had Toyota since you are using relative subqueries. So, when it passes to the AND a.id NOT IN (SELECT b.id where Car = 'Ford'), there are no remaining records with Ford in it.
This fiddle should help to illustrate what's happening - https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=085cdad84898f4e498aa9a2e76947d93
The quick fix to this would be to use absolute references to TableB instead of a relative reference in the subquery as shown in this fiddle - https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=f95e3ef0ddab5290ee7b9fa13e7451ab

How to remove line duplicates SQL via compare two same table

How to remove duplicate values a = b and b = a?
with a as(select w.id , w.doc, w.org
, d.name_s, d.name_f, d.name_p, d.spec
, o.name, o.extid
from crm_s_workplaces w
join crm_s_docs d on d.id=w.doc
join crm_s_orgs o on o.id=w.org
where d.active=1 and d.cst='NY' and w.active=1 and w.cst='NY' and o.active=1
and
o.cst='NY')
select a1.doc, a2.doc,
a1.org,a1.name_s,a1.name_f,a1.name_p,a2.name_s,a2.name_f,a2.name_p from a a1
join a a2 on
a1.name_s=a2.name_s and
substr(a1.name_f,1,1)=substr(a2.name_f,1,1) and
substr(a1.name_p,1,1)=substr(a2.name_p,1,1) and
a1.org=a2.org and
a1.spec<>a2.spec
order by a1.name_s `enter code here`
ER model diagram:
Repeat example:
Sometimes comes across a1.spec > a2.spec:
What you are calling "duplicates" are actually not duplicates in your database.
You basically have multiple doc records for what could be the same person or not. See that even their names do not always match. For instace,
doc_id 1131385 has NAME_F = "Gabr" while
doc_id 1447530 has NAME_F = "Gabor"
In your database these are two different entities, and you cannot match them using primary key. You can try to join on the first, middle and last names, but as you can see in the above example with Gabor/Gabr, even that would not work.
Can you change the schema of the db? If so you need to separate the docs in one table - 1 record per doctor. And have the specialization in a separate table with the folloing columns:
spec_id (int, PK)
doc_id (foreign key to Doc table)
specialization
that way, if you have 1 doctor with 3 specs, he/she will show up only once in doc table, and multiple times in spec table.
I just notice something else. You have spec field in table workplaces. why? If you meant to say that Doc Gabor works as admin in hospital 1 but as a Therapist in hospital 2, you can do that. However, you have to remove the spec field from the doc table and only use the spec in workplaces table.

Add new column 'Aid' to B table and link it to the increment id in A

I am new to database design. I am trying to create two tables which are linked by 'name' from A table and 'Aname' from table B. But what i want to do is creating a new increment int column 'id' as a representation for 'name' in A table and then add a new column in B called 'Aid' which is the foreign key.
My idea is to first (SQL SERVER)
SELECT row_number() over(order by A.name) id, A.* from A;
But how can I link this id to 'Aid' in B table?
Or if there is any existing statements for this question?
Thank you!
If you have the columns already defined, you can use join in an update:
update b
set b.a_id = a.a_id
from b join
a
on b.name = a.name;
I think you need to first look up how to add an auto-increment column to an existing dataset, which there are a number of resources for online. You can find one that is specific to your version of SQL Server. Then you can look into joining your B table to your A table on that. Don't add data to a table that is intended to be joined on a ROW_NUMBER result though. This is not good database design.

How to create a query that returns only records in one table without a foreign key

How do I create a query that returns only records in one table that have no foreign key relations with a particular attribute? I'm not even sure how to properly formulate the question, so I'm giving an example below:
I have Table B. It has an ID and other stuff that's not important.
I have Table C. It has an ID, an attribute (call it Available) which is only ever 'Yes' or 'No'.
Each record in Table B may have zero or more records in Table C related to it. They are connected by Table BC_Line, where each record has a FK for B_ID and a FK for C_ID. Hence, if records C_ID=1 and C_ID=2 are related to B_ID=1, then BC_Line has two records: (B_ID_FK=1 C_ID_FK=1) and (B_ID_FK=1 C_ID_FK=2).
I want a query that returns ONLY records in B that have NO associated records in C with C_Available='No'. A record in B might have several related records in C, all with 'Yes', and that would be shown. A record in B might have several related records in C all with 'No', that would NOT be shown. A record in B might also have records in C, some 'Yes' and some 'No', but that record would still not be shown. All related C records must be 'Yes'.
I'm not sure how to do it. I understand how to create queries and how to do Joins, but I do not know how to combine them in such a way as to get what I want. It's possible this is a well known problem, but I haven't been able to find the answer, partly because I have difficulty articulating my problem.
For the record, I am using Oracle.
You could use EXISTS:
SELECT *
FROM TableB b
WHERE NOT EXISTS (SELECT 1
FROM TableC c
WHERE c.C_Available='No'
AND b.B_ID_FK =c.C_ID);
Note! I've assumed that c.C_Available is defined as NOT NULL and it could hold only Yes/No values.
You could also use the left join method.
select b.*
from tableB b left join bc_line on b.b_id = bc.b_id
left join tableC c on bc.c_id = c.c_id
and c.c_available = 'No'
where c.c_id is null

Joining, but not joining (hypothetical q.)

Lets suppose that I have a table A with couple of columns. I work with tables, where there is no index on the entries, since they are 'historical' tables. I use one specific column, though, to sort of identify my things. Lets call this ID.
If you'd make a query like the one below, sometimes you'd get one line back, other cases a few.
SELECT * FROM A WHERE ID = '<something>'
Lets say I have two more tables, B and C. Both have ID columns, like A.
Also, some of the IDs in A, are also in B OR C. IDs existing in B CANNOT exist in C. And ALL IDs in A EXIST in either B OR C.
B and C contain extra information, which I'd like to join to A at the same SELECT.
My problem is, that they would only provide extra information. I do not want extra lines in my output..
To make it more clear: my selection from A returns a hundred lines of output.
When I left/right/inner join B table, I —probably— will have less lines as output. Same thing with joining C.
AND FINALLY, my question is:
Is there a way to join table B only on those IDs, which exist in B and vice versa? (And it I would want it in the same SELECT.... statement.)
If you don't want extra lines in your output, you could do something like this:
select *
from A join
(select B.*
from B
group by B.id
) B
on A.id = B.id;
This would choose arbitrary values from B for each id and join them back to A. Is this what you want?
Well it seems like you should build some left join between A and two "Select MAX"s: one from table B, the other one from table C.
And if you do not want 'duplicate' IDs from table A, a 'group by' on table A should help.