SQL: Count references to item in same table - sql

I have the following SQL table:
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(200),
parent int(11),
This stores some structured information in a tree:
"id" is the primary (uniq) key of the entries
"name" is some string
"parent" is the "id" of the parent entry (0: root element)
A sample table could be:
id name parent
--+-----------------+----------------
1 root_a 0
2 root_b 0
3 sub_b1 2
4 sub_sub_b1_1 3
5 sub_sub_b1_2 3
This could be a directory with folder ("root_"), sub-folder ("sub_"), sub-sub-folder ("sub_sub_*"), ...
Now I would like to have a SQL query, that returns for each entry how many child entries there are:
SELECT id,name,count(....) as child_count FROM table WHERE ...
For the example table this query shall return:
id name child_count
--+--------------+---------
1 root_a 0
2 root_b 1
3 sub_b1 2
4 sub_sub_b1_1 0
5 sub_sub_b1_2 0
How to perform such a count inside the same table?
Thanks

How about a correlated subquery?
select t.*,
(select count(*)
from t t2
where t2.parent = t.id
) as child_count
from t;

with qry1 as (
select parent,
count(*) as child_count
from table
group by parent
)
select table.id,
table.name,
isnull(qry1.child_count, 0) as child_count
from table
left join qry1
on table.id = qry1.parent
order by table.id
Note: Left Join. Inner join would exclude rows with no children.

Related

How to join two tables in a one-to-many relation, and keep only one-to-one records?

I have two tables : Folders and References. A folder can have 0 to 2 references (usually 1 or 2).
I have an existing query that search Folders that respect certains conditions. The query must have a new condition : Find only Folders with one reference.
Ideally, to limits change to this old application, the new condition should be within the WHERE clause only (This rule out group by x, having y).
Ex :
FolderId Name
0 Folder0
1 Folder1
2 Folder2
RefId FolderId Name
0 1 ref1
1 2 ref2
2 2 ref3
The output should only contain the FolderId 1
Use the query below to create a temporary table or create a table variable:
create table #Temp
(
column_name int
)
insert into #Temp
SELECT column_name
FROM table_name
GROUP BY column_name
HAVING COUNT(column_name) = 1;
Then use the temp table with join to other tables and place the conditions you want.
You probably want this:
select . . .
from folders f join
(select r.*, count(*) over (partition by folderid) as cnt
from references r
) r
on f.folderid = r.folderid and cnt = 1;

How to differentiate between “no child rows exist” and “no parent row exists” in one SELECT query?

Say I have a table C that references rows from tables A and B:
id, a_id, b_id, ...
and a simple query:
SELECT * FROM C WHERE a_id=X AND b_id=Y
I would like to differentiate between the following cases:
No row exists in A where id = X
No row exists in B where id = Y
Both such rows in A and B exist, but no rows in C exist where a_id = X and b_id = Y
The above query will return empty result in all those cases.
In case of one parent table I could do a LEFT JOIN like:
SELECT * FROM A LEFT JOIN C ON a.id = c.a_id WHERE c.a_id = X
and then check if the result is empty (no row in A exists), has one row with NULL c.id (row in A exists, but no rows in C exist) or 1+ rows with non-NULL c.id (row in A exists and at least one row in C exists). A bit messy but it works, but I was wondering if there is a better way of doing this, especially if there is more than one parent table?
For example:
C is "things owned by people", A is "people", B is "types of things". When someone asks "give me a list of games owned by Bill", and there are no such records in C, I would like to return an empty list only if both "Bill" and "games" exist in their corresponding tables, but an error code if either of them doesn't.
So if there are no records matching "Bill" and "games" in table C, I would like to say "I don't know who Bill is" instead of "Bill has no games" if I don't have a record about Bill in table A.
create table a(a_id integer not null primary key);
create table b(b_id integer not null primary key);
create table c(a_id integer not null references a(a_id)
, b_id integer not null references b(b_id)
, primary key (a_id,b_id)
);
insert into a(a_id) values(0),(2),(4),(6);
insert into b(b_id) values(0),(3),(6);
insert into c(a_id,b_id) values(6,6);
PREPARE omg(integer,integer) AS
SELECT EXISTS(SELECT * FROM a where a.a_id = $1) AS a_exists
, EXISTS(SELECT * FROM b where b.b_id = $2) AS b_exists
, EXISTS(SELECT * FROM c where c.a_id = $1 and c.b_id = $2) AS c_exists
;
EXECUTE omg(1,1);
EXECUTE omg(2,1);
EXECUTE omg(1,3);
EXECUTE omg(6,6);
-- with optional payload:
PREPARE omg2(integer,integer) AS
SELECT val.a_id AS va_id
, val.b_id AS vb_id
, EXISTS(SELECT * FROM a WHERE a.a_id = $1) AS a_exists
, EXISTS(SELECT * FROM b WHERE b.b_id = $2) AS b_exists
, EXISTS(select * FROM c WHERE c.ca_id = val.a_id AND c.cb_id = val.b_id ) AS c_exists
, a.*
, b.*
, c.*
FROM (values ($1,$2)) val(a_id,b_id)
LEFT JOIN a ON a.a_id = val.a_id
LEFT JOIN b ON b.b_id = val.b_id
LEFT JOIN c ON c.ca_id = val.a_id AND c.cb_id = val.b_id
;
EXECUTE omg2(1,1);
EXECUTE omg2(2,1);
EXECUTE omg2(1,3);
EXECUTE omg2(6,6);
I think I managed to get a satisfactory solution using the following two features:
Subselect bound to a column, which allows me to check if a row exists and (importantly) get a NULL value otherwise (e.g. SELECT (SELECT id FROM a WHERE id = 1) as a_id))
Common Table Expressions
Initial data:
CREATE TABLE people
(
id integer not null primary key,
name text not null
);
CREATE TABLE thing_types
(
id integer not null primary key,
name text not null
);
CREATE TABLE things
(
id integer not null primary key,
person_id integer not null references people(id),
thing_type_id integer not null references thing_types(id),
name text not null
);
INSERT INTO people VALUES (1, 'Bill');
INSERT INTO thing_types VALUES (1, 'game');
INSERT INTO things VALUES (1, 1, 1, 'Duke Nukem');
INSERT INTO things VALUES (2, 1, 1, 'Warcraft 2');
And the query:
WITH v AS (
SELECT (SELECT id FROM people WHERE id=<person_id_param>) AS person_id,
(SELECT id FROM thing_types WHERE id=<thing_type_param>) AS thing_type_id
)
SELECT v.person_id, v.thing_type_id, things.name
FROM
v LEFT JOIN things
ON v.person_id = things.person_id AND v.thing_type_id = things.thing_type_id
This query will always return at least one row, and I just need to check which, if any, of the three columns of the first row are NULLs.
In case if both parent table ids are valid and there are some records, none of them will be NULL:
person_id thing_type_id name
-------------------------------------
1 1 Duke Nukem
1 1 Warcraft 2
If either person_id or thing_type_id are invalid, I get one row where name is NULL and either person_id or thing_type_id is NULL:
person_id thing_type_id name
-------------------------------------
NULL 1 NULL
If both person_id and thing_type_id are valid but there are no records in things, I get one row where both person_id and thing_type_id are not NULL, but the name is NULL:
person_id thing_type_id name
-------------------------------------
1 1 NULL
Since I have a NOT NULL constraint on things.name, I know that this case can only mean that there are no matching records in things. If NULLs were allowed in things.name, I could include things.id instead and check that for NULLness.
You have 3 cases, the third one is a bit more complex but can be achieved by using cross join between a and b, all three cases in a union could be like this
select a_id, b_id , 'case 1' from c
where not exists (select 1 from a where a.a_id=c.a_id)
union all
select a_id, b_id ,'case 2' from c
where not exists (select 1 from b where b.b_id=c.b_id)
union all
select a_id, b_id, 'case 3' from a cross join b
where exists (select 1 from c where c.a_id=a.a_id)
and exists (select 1 from c where c.b_id=b.b_id)
and not exists (select 1 from c where c.b_id=b.b_id and c.a_id=a.a_id)

Updating order of child record

I've got two tables:
Parent
| id |
Child
| id | owner | ordernr |
owner is a foreign key referencing Parent's id. There is a uniqueness constraint on (owner, ordernr)
Now, there are some gaps in the orders and I'm trying to fix them as follows:
CREATE OR REPLACE VIEW myView AS
(SELECT childid, ordernr, n
FROM (SELECT child.id as childid, ordernr, ROW_NUMBER() OVER ( PARTITION BY parent.id ORDER BY ordernr) AS n
FROM Parent, Child WHERE owner = parent.id)
WHERE ordernr <> n)
UPDATE
(SELECT c.ordernr, n
FROM Child c, myView WHERE childid = c.id) t
SET t.ordernr = t.n
But I get: ORA-01779: cannot modify a column which maps to a non key-preserved table
ORA-01779: cannot modify a column which maps to a non key-preserved table
This error occurs when you try to INSERT or UPDATE columns in a join view which map to a non-key-preserved table.
You could use a MERGE.
For example,
MERGE INTO child c
USING (SELECT n
FROM myview) t
ON (t.childid = c.id)
WHEN matched THEN
UPDATE SET c.ordernr = t.n
/

Select count from different tables with a field in common

So here is my query:
SELECT COUNT( tab1.id_z ) AS Count, tab_tot.name
FROM tab1
INNER JOIN tab_id ON (tab1.id_key = tab_id.id_key)
INNER JOIN tab_tot ON (tab_id.id_z = tab_tot.id_z)
WHERE tab1.id_c = 10888 GROUP BY tab_id.id_z
In table tab_id there are 6 records with the same id_z but i receive 1 in Count.
How can I do?
Edit - this is my schema:
tab1
id_key | id_c
tab_id
id_key |id_z
tab_tot
id_z | description
Though I have more records in tab_id, I "count" ever 1

MySQL SQL Subquery?

Given the following schema / data / output how would I format a SQL query to give the resulting output?
CREATE TABLE report (
id BIGINT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
source VARCHAR(255) NOT NULL UNIQUE,
PRIMARY KEY(id)
) ENGINE = INNODB;
CREATE TABLE field (
id BIGINT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
report_id BIGINT,
PRIMARY KEY(id)
) ENGINE = INNODB;
ALTER TABLE filed ADD FOREIGN KEY (report_id) REFERENCES report(id) ON DELETE CASCADE;
reports:
id, name, source
1 report1 source1
2 report2 source2
3 report3 source3
4 report4 source4
field:
id, name, report_id
1 firstname 3
2 lastname 3
3 age 3
4 state 4
5 age 4
6 rank 4
Expected output for search term "age rank"
report_id, report_name, num_fields_matched
3 report3 1
4 report4 2
Thanks in advance!
This query will return all the reports with words you need.
SELECT *
FROM report r
INNER JOIN field f ON r.id = f.report_id
WHERE name IN ('age','rank')
You have to nest it. So the final query is:
SELECT a.id, a.name, COUNT(*)
FROM
(
SELECT r.id, r.name
FROM report r
INNER JOIN field f ON r.id = f.report_id
WHERE f.name
IN ('age', 'rank')
)a
GROUP BY a.id, a.name