SQLite3 and "cascade" SELECTion - sql

I have a parent table and a child table related to the parent table by some REFERENCE.
Suppose I exec a SELECT statement on the child and that it returns the at least one result. Can I arrange for my search to automatically yield all the content of all related parents with this child too?
Or must I always take the reference from the child and put this in a second SELECT statement and exec this myself?

You can use subqueries:
SELECT *
FROM Parent
WHERE Parent.Id IN (SELECT ParentId
FROM Child
WHERE Whatever_was_your_original_query)
Or a good old join:
SELECT Parent.*
FROM Parent INNER JOIN Child ON Parent.Id = Child.ParentId
WHERE Whatever_you_want_to_query

This is the very basic purpose of SQL. You will JOIN the two tables together to create one set of result rows with some or all columns from BOTH tables included.
For more info, see this page.

Related

SQL get leaves of directed acyclic graph

I am quite new to SQL and have a rather basic question. Suppose I'm dealing with the following table structure:
CREATE TABLE nodes (
id INTEGER NOT NULL PRIMARY KEY,
parent INTEGER REFERENCES nodes(id)
);
If we hold an invariant that says, the parent of a node cannot be equivalent to any of its children, then by definition we will not have any loops in our graph. Now we are left with a disjoint directed acyclic graph.
The two questions I have then are:
If we cannot change the structure of the database: What select statement would I have to write to efficiently get all of the leaves in my database? I.e. the ids that don't have any children.
If we can change the structure of the tables: What could we change or add to make this select statement more efficient?
An example of output for the graph with five nodes whose parents where 3->2, 2->1, and 5->4 would output 3 and 5 because they are the only nodes that don't have children.
You can use NOT EXISTS and a correlated subquery that checks for node where the current not is the parent. For leafs no such record can exist.
SELECT *
FROM nodes n1
WHERE NOT EXISTS (SELECT *
FROM nodes n2
WHERE n2.parent = n1.id);
Another option is a left join joining possible children of a node. If there's a null for an id of the "children's side" of the join no child exists for the current node, it's a leaf.
SELECT *
FROM nodes n1
LEFT JOIN nodes n2
ON n2.parent = n1.id
WHERE n2.id IS NULL;
And, leaving denormalization away, I don't think there's much to change in the table's structure. Indexes could help though. One should be on id (but that's already the case because of the primary key constraint) and one on parent (but again such an index already exists because MySQL creates indexes for foreign key tuples).
For more complex graph queries, you may use Common Table Expressions (CTEs), standardized in SQL:99 and supported in MySQL since 8.0.1 (reference)
But as others pointed out, for the query you're interested in, a simple NOT EXISTS subquery or equivalent is enough. Yet another equivalent to those already posted would be using the EXCEPT set operation:
SELECT id FROM nodes
EXCEPT SELECT parent FROM nodes
I would do:
select *
from nodes
where id not in (select parent from nodes where parent is not null)

Second table name after FROM tables

I'm editing some views and came across something that is new to me:
SELECT rn3.create_date
FROM receipt_note rn3
WHERE rn3.receipt_num = receipt_data.receipt_num
I'm just wondering what the rn3 does in the from part of the statement?
As there isn't a comma between them showing its another table and I dont see a table or view in my database called rn3.
It is called an Alias.
You can define another name to use in your queries. Mostly used as shorter name of tables to simplify your queries. Example:
select t.some_column
from very_long_table_name t
Or if you join the same table twice then you need aliases to distinguish between the two. Example:
select child.name, parent.name
from users child
join users parent on child.parent_id = parent.id
And as stated in comments: When using DB engines other than Oracle, you can but don't need to define the as keyword:
select t.*
from some_table_name as t

In SQL, how can I select all parents that have children?

Let's say I have two related tables parents and children with a one-to-many relationship (one parent to many children). Normally when I need to process the information on these tables together, I do a query such as the following (usually with a WHERE clause added in):
SELECT * FROM parents INNER JOIN children ON (parents.id = children.parent_id);
How can I select all parents that have at least one child without wasting time joining all of the children to their parents?
I was thinking of using some sort of OUTER JOIN but I am not sure exactly what to do with it.
(Note that I am asking this question generally, so don't give me an answer that is tied to a specific RDBMS implementation unless there is no general solution.)
As I put earlier in comments:
Solution with LEFT JOIN and GROUP BY:
SELECT p.parents.id FROM parents p
LEFT JOIN children c ON (p.parents.id = c.children.parent_id)
WHERE children.parent_id IS NOT NULL
GROUP BY p.parents_id
The same with DISTINCT:
SELECT DISTINCT p.parents.id FROM parents p
LEFT JOIN children c ON (p.parents.id = c.children.parent_id)
WHERE children.parent_id IS NOT NULL
It should work in most SQL dialects, though some require as when assigning table aliases.
The above is not tested. Hopefully I made no typos.
I think that the simplest solution that avoids a JOIN would be:
SELECT * FROM parents WHERE id IN (SELECT parent_id FROM children);
try this
select parent_id,(select count(1) from children where parent_id = x.parent_id)
from parent x where
(select count(1) from children where parent_id = x.parent_id) > 0

SELECT When child record doesn't exist

I have two tables parent and children. The parent.mopid and children.mopid is the connection between the two tables. How would I write a SELECT that would end result show me just the parent records where there are no children records?
Use the NOT IN function:
SELECT * from parent
where parent.mopid NOT IN (SELECT mopid from children)
This will return all rows from the parent table that do not have a corresponding mopid in the childrens table.
If you have a lot of rows, a LEFT JOIN is often quicker than a NOT IN. But not always - it depends on the data so please try this answer and the one from #aktrazer and see which works best for you.
SELECT parent.*
FROM parent
LEFT JOIN children ON parent.mopid = children.mopid
WHERE children.mopid IS NULL
If there isn't a children row for the mopid, parent.mopid will have a value but child.mopid will be null.
SELECT * from parent p where NOT EXISTS
( select mopid from children c where p.mopid = c.mopid)
This should take care of the nulls as well
This link will explain you the difference between NOT IN and NOT EXISTS
NOT IN vs NOT EXISTS

Delete parent if it's not referenced by any other child

I have an example situation: parent table has a column named id, referenced in child table as a foreign key.
When deleting a child row, how to delete the parent as well if it's not referenced by any other child?
In PostgreSQL 9.1 or later you can do this with a single statement using a data-modifying CTE. This is generally less error prone. It minimizes the time frame between the two DELETEs in which a race conditions could lead to surprising results with concurrent operations:
WITH del_child AS (
DELETE FROM child
WHERE child_id = 1
RETURNING parent_id, child_id
)
DELETE FROM parent p
USING del_child x
WHERE p.parent_id = x.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = x.parent_id
AND c.child_id <> x.child_id -- !
);
db<>fiddle here
Old sqlfiddle
The child is deleted in any case. I quote the manual:
Data-modifying statements in WITH are executed exactly once, and
always to completion, independently of whether the primary query reads
all (or indeed any) of their output. Notice that this is different
from the rule for SELECT in WITH: as stated in the previous section,
execution of a SELECT is carried only as far as the primary query
demands its output.
The parent is only deleted if it has no other children.
Note the last condition. Contrary to what one might expect, this is necessary, since:
The sub-statements in WITH are executed concurrently with each other
and with the main query. Therefore, when using data-modifying
statements in WITH, the order in which the specified updates actually
happen is unpredictable. All the statements are executed with the same
snapshot (see Chapter 13), so they cannot "see" each others' effects
on the target tables.
Bold emphasis mine.
I used the column name parent_id in place of the non-descriptive id.
Eliminate race condition
To eliminate possible race conditions I mentioned above completely, lock the parent row first. Of course, all similar operations must follow the same procedure to make it work.
WITH lock_parent AS (
SELECT p.parent_id, c.child_id
FROM child c
JOIN parent p ON p.parent_id = c.parent_id
WHERE c.child_id = 12 -- provide child_id here once
FOR NO KEY UPDATE -- locks parent row.
)
, del_child AS (
DELETE FROM child c
USING lock_parent l
WHERE c.child_id = l.child_id
)
DELETE FROM parent p
USING lock_parent l
WHERE p.parent_id = l.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = l.parent_id
AND c.child_id <> l.child_id -- !
);
This way only one transaction at a time can lock the same parent. So it cannot happen that multiple transactions delete children of the same parent, still see other children and spare the parent, while all of the children are gone afterwards. (Updates on non-key columns are still allowed with FOR NO KEY UPDATE.)
If such cases never occur or you can live with it (hardly ever) happening - the first query is cheaper. Else, this is the secure path.
FOR NO KEY UPDATE was introduced with Postgres 9.4. Details in the manual. In older versions use the stronger lock FOR UPDATE instead.
delete from child
where parent_id = 1
After deleted in the child do it in the parent:
delete from parent
where
id = 1
and not exists (
select 1 from child where parent_id = 1
)
The not exists condition will make sure it will only be deleted if it does not exist in the child. You can wrap both delete commands in a transaction:
begin;
first_delete;
second_delete;
commit;