Related
I have a hierarchy in my table contacts. Each contact have column state_id which related with table contact_state (contact_id). There is a column is_top in contact_state table.
I need to write 2 queries:
Select COUNT recursively all children (parent column is parent_id) from contacts where the contact_state is_top is false. So if the child have state_id.contact_state is false, we need to looking for his children while the subchild.state_id.contact_state not be is true.
For this search I use this query:
with recursive hierarchy as (
select contact.id
from contacts contact join contact_state state1 on contact.state_id = state1.id and contact.parent_id = 45 and state1.is_top = false
union all
select recursive_contact.id
from contacts recursive_contact
inner join contact_state recursive_contact_state on recursive_contact.state_id = recursive_contact_state.id and recursive_contact_state.is_top = false inner join hierarchy on hierarchy.id = recursive_contact.parent_id
)
select count(*) from hierarchy;
But I don't understand at all how I can do this:
SELECT all children with state_id.is_top = true + go inside all children with state_id.is_top = false (recursively) until don't get the child with state_id.is_top = true (count only them + first level depth children with state_id.is_top = true)?
Is it possible?
If it will be much easier the option available:
Check only all children with state_id.is_top = false (recursively) until don't get the child with state_id.is_top = true (count only them + first level depth children with state_id.is_top = true)?
The first-level children I can easily add later
UPD:
database scema:
CREATE TABLE contacts (
id INT PRIMARY KEY,
name TEXT,
parent_id INT,
state_id INT
);
CREATE TABLE contact_state (
id INT PRIMARY KEY,
name TEXT,
is_top BOOL
);
demo data:
INSERT INTO contact_state VALUES(1, 'STATE #1', TRUE);
INSERT INTO contact_state VALUES(2, 'STATE #2', FALSE);
INSERT INTO contact_state VALUES(3, 'STATE #3', TRUE);
INSERT INTO contact_state VALUES(4, 'STATE #4', FALSE);
INSERT INTO contacts VALUES(1, 'A1', NULL, 2);
INSERT INTO contacts VALUES(2, 'A2', 1, 1);
INSERT INTO contacts VALUES(3, 'A3', 2, 2);
INSERT INTO contacts VALUES(4, 'A4', 3, 3);
INSERT INTO contacts VALUES(5, 'B2', 1, 4);
INSERT INTO contacts VALUES(6, 'B3', 5, 2);
INSERT INTO contacts VALUES(7, 'B4', 6, 1);
INSERT INTO contacts VALUES(8, 'B5', 7, 3);
INSERT INTO contacts VALUES(9, 'BB3', 5, 1);
INSERT INTO contacts VALUES(10, 'BB4', 9, 1);
INSERT INTO contacts VALUES(11, 'C2', 1, 4);
INSERT INTO contacts VALUES(12, 'C3', 11, 3);
INSERT INTO contacts VALUES(13, 'CC3', 11, 2);
INSERT INTO contacts VALUES(14, 'D2', 1, 2);
INSERT INTO contacts VALUES(15, 'E2', 1, 3);
Expected output:
5 (if use count) or ids: 2,7,9,12,15
Given 3 tables. I need to build SQL query to find two actors who CAST TOGETHER THE MOST and list the titles of those movies. Sort alphabetically
https://www.db-fiddle.com/f/r2Y9CpH8n7MHTeBaqEHe9S/0
The data for reproducing below:
create table film_actor
(
actor_id integer,
film_id integer
)
;
create table film
(
film_id integer,
title varchar
)
;
create table actor
(
actor_id integer,
first_name varchar,
last_name varchar
)
;
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 1);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 2);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 3);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 1);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 2);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 3);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (3, 1);
INSERT INTO public.film (film_id, title) VALUES (1, 'First');
INSERT INTO public.film (film_id, title) VALUES (2, 'Second');
INSERT INTO public.film (film_id, title) VALUES (3, 'Third');
INSERT INTO public.film (film_id, title) VALUES (4, 'Fourth');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (1, 'John', 'Snow');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (2, 'Spider', 'Man');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (3, 'Mike', 'Kameron');
Is this what you are looking for?
with acting_pairs as (
select a1.actor_id as a1_id, a2.actor_id as a2_id
from film_actor a1
join film_actor a2 on a1.film_id = a2.film_id
where a1.actor_id < a2.actor_id
)
select a1_id, a2_id, count(*) as total
from acting_pairs
group by (a1_id, a2_id)
order by total desc
limit 1
Giving us expected output for the example input would be nice.
I have the following 2 tables
CREATE TABLE salesperson_t (
salespersonid numeric(4,0) NOT NULL,
salespersonname character varying(25),
salespersontelephone character varying(50),
salespersonfax character varying(50),
salespersonaddress character varying(30),
salespersoncity character varying(20),
salespersonstate character(2),
salespersonzip character varying(20),
salesterritoryid numeric(4,0),
CONSTRAINT salesperson_pk PRIMARY KEY (salespersonid)
);
INSERT INTO salesperson_t VALUES (1, 'Doug Henny', '8134445555', NULL, NULL, NULL, NULL, NULL, 2);
INSERT INTO salesperson_t VALUES (2, 'Robert Lewis', '8139264006', NULL, '124 Deerfield', 'Lutz', 'FL', '33549', 13);
INSERT INTO salesperson_t VALUES (3, 'William Strong', '3153821212', NULL, '787 Syracuse Lane', 'Syracuse', 'NY', '33240', 3);
INSERT INTO salesperson_t VALUES (4, 'Julie Dawson', '4355346677', NULL, NULL, NULL, NULL, NULL, 4);
INSERT INTO salesperson_t VALUES (5, 'Jacob Winslow', '2238973498', NULL, NULL, NULL, NULL, NULL, 5);
INSERT INTO salesperson_t VALUES (6, 'Pepe Lepue', NULL, NULL, NULL, 'Platsburg', 'NY', NULL, 13);
INSERT INTO salesperson_t VALUES (8, 'Fred Flinstone', NULL, NULL, '1 Rock Lane', 'Bedrock', 'Ca', '99999', 2);
INSERT INTO salesperson_t VALUES (9, 'Mary James', '3035555454', NULL, '9 Red Line', 'Denver', 'CO', '55555', 4);
INSERT INTO salesperson_t VALUES (10, 'Mary Smithson', '4075555555', NULL, '4585 Maple Dr', 'Orlando', 'FL', '32826', 15);
CREATE TABLE territory2_t (
territoryid numeric(4,0),
territoryname character varying(50),
total_sales_person integer,
CONSTRAINT territory2_t_pk PRIMARY KEY (territoryid)
);
INSERT INTO territory2_t VALUES (1, 'SouthEast', NULL);
INSERT INTO territory2_t VALUES (2, 'SouthWest', NULL);
INSERT INTO territory2_t VALUES (3, 'NorthEast', NULL);
INSERT INTO territory2_t VALUES (4, 'NorthWest', NULL);
INSERT INTO territory2_t VALUES (5, 'Central', NULL);
INSERT INTO territory2_t VALUES (6, 'Alaska', NULL);
INSERT INTO territory2_t VALUES (12, 'Hawaii', NULL);
INSERT INTO territory2_t VALUES (13, 'Colorado', NULL);
INSERT INTO territory2_t VALUES (15, 'Arizona', NULL);
I have the following pseudo code:
DO $$
DECLARE
-- currentRow [relevant datatype];
BEGIN
FOR counter IN 1..(SELECT count(*)FROM territory2_t) LOOP -- There are 13 total rows
-- **assign currentRow to counter**
RAISE NOTICE 'Counter: %', counter; -- debugging purposes
UPDATE terriory2_t
SET total_sales_person = ((SELECT count(*)
FROM salesperson_t
WHERE salesterritoryid = currentRow.territoryid)*1) -- *1 is for debuggin puporses
WHERE territoryid = currentRow.territoryid;
-- **increase currentRow by 1**
END LOOP;
END; $$
It's purpose is count how many rows in the table (salesperson) have the 'territoryid' of the the currentRows->'territory2.territoryid', and then assign that quantity to currentRows->territory2.total_sales_person.
You don't need a loop or even a function for this.
What you want to do can be done in a single update statement because the total count per territory can be calculated with a single aggregation:
SELECT salesterritoryid, count(*) as total_count
FROM salesperson_t
group by salesterritoryid
This can then be used as the source to update the territory table:
UPDATE territory2_t
SET total_sales_person = t.total_count
FROM (
SELECT salesterritoryid, count(*) as total_count
FROM salesperson_t
group by salesterritoryid
) t
WHERE territoryid = t.salesterritoryid;
An alternative that might be easier to understand but will be slower for larger tables is an update with a co-related sub-query
UPDATE territory2_t tg
SET total_sales_person = (select count(*)
from salesperson_t sp
where sp.salesterritoryid = tg.territoryid);
There is a slight difference between the first and second update: the second one will update the total_sales_person to 0 (zero) for those territories where there is no salesperson at all. The first one will only update the count for territories that are actually present in the salesperson table.
Unrelated, but: having a "type identifying" prefix or suffix for an identifier is usually useless and doesn't really help at all. See a related discussion on dba.stackexchange
I'm currently going through the growing pains of trying to learn about functions and triggers. I'm trying to do a problem from a book I'm reading , but i dont understand how to do certain parts.
using this table
create table movies (
id integer primary key,
title varchar(255) not null,
year integer
);
insert into movies values (1, 'The Croods', 2013);
insert into movies values (2, 'Now You See Me', 2013);
insert into movies values (3, 'Argo', 2012);
insert into movies values (4, 'Jurassic World', 2015);
create table discs (
id integer primary key,
movie_id integer not null references movies(id),
type_id integer references disc_types(id),
price decimal(10,2),
available boolean
);
insert into discs values (1, 1, 1, 1.59, 't');
insert into discs values (2, 1, 1, 1.59, 'f');
insert into discs values (3, 1, 2, 2.99, 'f');
insert into discs values (4, 2, 1, 1.29, 't');
insert into discs values (5, 2, 1, 1.29, 't');
insert into discs values (6, 2, 2, 2.99, 't');
insert into discs values (7, 3, 2, 2.59, 't');
insert into discs values (8, 3, 2, 2.59, 't');
create table customers (
id integer primary key,
name varchar(255),
email varchar(255)
);
insert into customers values (1, 'John', 'john#hotmail.com');
insert into customers values (2, 'Jane', 'jane#gmail.com');
create table rentals (
id integer primary key,
customer_id integer not null references customers(id),
disc_id integer not null references discs(id),
date_rented date,
date_returned date
);
insert into rentals values (1, 1, 7, '2013-10-01', '2013-10-03');
insert into rentals values (2, 2, 5, '2013-10-05', '2013-10-06');
insert into rentals values (3, 2, 2, '2013-11-02', null);
insert into rentals values (4, 2, 3, '2013-11-02', null);
create table ratings (
customer_id integer not null references customers(id),
movie_id integer not null references movies(id),
rating integer,
primary key (customer_id, movie_id)
);
insert into ratings values (1, 1, 1);
insert into ratings values (1, 2, 4);
insert into ratings values (1, 3, 5);
insert into ratings values (2, 1, 4);
my logic was that i would have the new values of the ratings table that were going to be inserted or updated and use those to compare to whats in the rentals table to see if that customer had rented that movie already, if they did then they could enter a rating. but i cant transfer that logic in this lol. unless there an easier way to do this.
The loop inside the function complicates matters a bit, let's see if we can get rid of it. Your ratings table has a reference to customer and movie so we need a join.
SELECT COUNT(*) INTO rented FROM rentals WHERE disc_id IN
(SELECT id from discs INNER JOIN
rentals ON disc_id = discs.id where movie_id = new.movie_id)
AND customer_id = new.customer_id
Right this should make the logic of your stored procedure a lot easier. I am now leaving you to finish it because this after all is a learning exercise.
You need this sort of a join because it's more efficient and simpler than the loop. The ratings table has a reference to the movie_id but the rentals table only has a disc_id thus to find out if the user has rented a particular movie, you need to join it through the disc table.
You will need to change the return values. ref: http://www.postgresql.org/docs/9.2/static/plpgsql-trigger.html
Row-level triggers fired BEFORE can return null to signal the trigger
manager to skip the rest of the operation for this row (i.e.,
subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does
not occur for this row). If a nonnull value is returned then the
operation proceeds with that row value
And also note that you do not do an INSERT inside your trigger function. You just return a non null value for the insert to proceed.
This is the EXISTS() version. (BTW: the definition for movies is missing)
CREATE OR REPLACE FUNCTION rate_only_rented()
RETURNS TRIGGER AS $func$
BEGIN
IF ( NOT EXISTS (
SELECT *
FROM rentals r
JOIN discs d ON r.disc_id = d.id
WHERE d.movie_id = NEW.movie_id
AND r.customer_id = NEW.customer_id
) ) THEN
RAISE EXCEPTION 'you(%) have not rented this movie(%) before'
, NEW.customer_id ,NEW.movie_id;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$func$ language plpgsql;
And the trigger:
CREATE TRIGGER rate_only_rented
AFTER INSERT OR UPDATE
ON ratings
FOR EACH ROW
EXECUTE PROCEDURE rate_only_rented()
;
I express the relationship between records and searchtags that can be attached to records like so:
TABLE RECORDS
id
name
TABLE SEARCHTAGS
id
recordid
name
I want to be able to SELECT records based on the searchtags that they have. For example, I want to be able to SELECT all records that have searchtags:
(1 OR 2 OR 5) AND (6 OR 7) AND (10)
Using the above data structure, I am uncertain how to structure the SQL to accomplish this.
Any suggestions?
Thanks!
You may want to try the following:
SELECT r.id, r.name
FROM records r
WHERE EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (1, 2, 5)) AND
EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (6, 7)) AND
EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (10));
Test case: Note that only records 1 and 4 will satisfy the query criteria.
CREATE TABLE records (id int, name varchar(10));
CREATE TABLE searchtags (id int, recordid int);
INSERT INTO records VALUES (1, 'a');
INSERT INTO records VALUES (2, 'b');
INSERT INTO records VALUES (3, 'c');
INSERT INTO records VALUES (4, 'd');
INSERT INTO searchtags VALUES (1, 1);
INSERT INTO searchtags VALUES (2, 1);
INSERT INTO searchtags VALUES (6, 1);
INSERT INTO searchtags VALUES (10, 1);
INSERT INTO searchtags VALUES (1, 2);
INSERT INTO searchtags VALUES (2, 2);
INSERT INTO searchtags VALUES (3, 2);
INSERT INTO searchtags VALUES (1, 3);
INSERT INTO searchtags VALUES (10, 3);
INSERT INTO searchtags VALUES (5, 4);
INSERT INTO searchtags VALUES (7, 4);
INSERT INTO searchtags VALUES (10, 4);
Result:
+------+------+
| id | name |
+------+------+
| 1 | a |
| 4 | d |
+------+------+
2 rows in set (0.01 sec)
SELECT
id, name
FROM
records
WHERE
EXISTS (
SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (1, 2, 5)
)
AND EXISTS (
SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (6, 7)
)
AND EXISTS (
SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (10)
)
not sure how to do it in mysql, but in t-sql, you could do something like:
SELECT id, name FROM RECORDS where id in (SELECT recordid from SEARCHTAGS where id in (1,2,5,6,7,10))
I may not be understanding your question entirely... but I gave it my best.
Try:
SELECT R.*
FROM RECORDS R, SEARCHTAGS S
WHERE R.id == S.recordid
AND S.name in (1,2,5,6,7,10);
Don't know if you need S.name or S.id, but this is an example.
select RECORDS.name
from RECORDS join SEARCHTAGS
on RECORDS.id = SEARCHTAGS.recordid
where RECORDS.id in (1,2,...)
I misread the question first time around, and thought it was asking for
(1 AND 2 AND 5) OR (6 AND 7) OR (10)
instead of the correct
(1 OR 2 OR 5) AND (6 OR 7) AND (10)
All the answers so far have concentrated on answering the specific example, rather than addressing the more general question "and suppose I want a different set of criteria next time".
Actual question
I can't do much better than the selected answer for the actual question (Query 1):
SELECT r.id, r.name
FROM Records AS r
WHERE EXISTS(SELECT * FROM SearchTags AS s
WHERE r.id = s.recordid AND s.id IN (1, 2, 5))
AND EXISTS(SELECT * FROM SearchTags AS s
WHERE r.id = s.recordid AND s.id IN (6, 7))
AND EXISTS(SELECT * FROM SearchTags AS s
WHERE r.id = s.recordid AND s.id IN (10));
This could be written as a join to 3 aliases for the SearchTags table.
Alternative question
There are several ways to answer the alternative - I think this is the most nearly neat and extensible. Clearly, the one item (10) is easy (Query 2):
SELECT r.id, r.name
FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
WHERE t.id IN (10) -- or '= 10' but IN is consistent with what follows
The two items (6 or 7) can be done with (Query 3):
SELECT r.id, r.name
FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
WHERE t.id IN (6, 7)
GROUP BY r.id, r.name
HAVING COUNT(*) = 2
The three items (1, 2, 5) can be done with (Query 4):
SELECT r.id, r.name
FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
WHERE t.id IN (1, 2, 5)
GROUP BY r.id, r.name
HAVING COUNT(*) = 3
And the whole collection can be a UNION of the three terms.
Generalizing the solutions
The downside of this solution is that the SQL must be manually crafted for each set of items.
If you want to automate the 'SQL generation', you need the control data - the sets of interesting search tags - in a table:
CREATE TABLE InterestingTags(GroupID INTEGER, TagID INTEGER);
INSERT INTO InterestingTags(1, 1);
INSERT INTO InterestingTags(1, 2);
INSERT INTO InterestingTags(1, 5);
INSERT INTO InterestingTags(2, 6);
INSERT INTO InterestingTags(2, 7);
INSERT INTO InterestingTags(3, 10);
For the query asking for '(1 OR 2 OR 5) AND (...)' (conjunctive normal form), you can write (Query 5):
SELECT r.id, r.name
FROM records AS r JOIN
searchtags AS s ON r.id = s.recordID JOIN
interestingtags AS t ON s.id = t.tagID
GROUP BY r.id, r.name
HAVING COUNT(DISTINCT t.GroupID) = (SELECT COUNT(DISTINCT GroupID)
FROM InterestingTags);
This checks that the number of distinct 'interesting groups of tags' for a given record is equal to the total number of 'interesting groups of tags'.
For the query asking for '(1 AND 2 AND 5) OR (...)' (disjunctive normal form), you can write a join with InterestingTags and check that the Record has as many entries as the group of tags (Query 6):
SELECT i.id, i.name
FROM (SELECT q.id, q.name, c.GroupSize,
COUNT(DISTINCT t.GroupID) AS GroupCount
FROM records AS q JOIN
searchtags AS s ON q.id = s.recordID JOIN
interestingtags AS t ON s.id = t.tagID JOIN
(SELECT GroupID, COUNT(*) AS GroupSize
FROM InterestingTags
GROUP BY GroupID) AS c ON c.GroupID = t.GroupID
GROUP BY q.id, q.name, c.GroupSize
) AS i
WHERE i.GroupCount = i.GroupSize;
Test Data
I took the test data from Daniel Vassalo's answer and augmented it with some extra values:
CREATE TABLE records (id int, name varchar(10));
CREATE TABLE searchtags (id int, recordid int);
INSERT INTO records VALUES (1, 'a');
INSERT INTO records VALUES (2, 'b');
INSERT INTO records VALUES (3, 'c');
INSERT INTO records VALUES (4, 'd');
INSERT INTO records VALUES (11, 'A11');
INSERT INTO records VALUES (21, 'B12');
INSERT INTO records VALUES (31, 'C13');
INSERT INTO records VALUES (41, 'D14');
INSERT INTO records VALUES (51, 'E15');
INSERT INTO records VALUES (61, 'F16');
INSERT INTO searchtags VALUES (1, 1);
INSERT INTO searchtags VALUES (2, 1);
INSERT INTO searchtags VALUES (6, 1);
INSERT INTO searchtags VALUES (10, 1);
INSERT INTO searchtags VALUES (1, 2);
INSERT INTO searchtags VALUES (2, 2);
INSERT INTO searchtags VALUES (3, 2);
INSERT INTO searchtags VALUES (1, 3);
INSERT INTO searchtags VALUES (10, 3);
INSERT INTO searchtags VALUES (5, 4);
INSERT INTO searchtags VALUES (7, 4);
INSERT INTO searchtags VALUES (10, 4);
INSERT INTO searchtags VALUES (1, 11);
INSERT INTO searchtags VALUES (2, 11);
INSERT INTO searchtags VALUES (5, 11);
INSERT INTO searchtags VALUES (6, 21);
INSERT INTO searchtags VALUES (7, 21);
INSERT INTO searchtags VALUES (10, 31);
INSERT INTO searchtags VALUES (1, 41);
INSERT INTO searchtags VALUES (6, 41);
INSERT INTO searchtags VALUES (10, 41);
INSERT INTO searchtags VALUES (2, 51);
INSERT INTO searchtags VALUES (5, 51);
INSERT INTO searchtags VALUES (10, 51);
INSERT INTO searchtags VALUES (7, 61);
INSERT INTO searchtags VALUES (2, 61);
INSERT INTO searchtags VALUES (1, 61);
CREATE TABLE InterestingTags(GroupID INTEGER, TagID INTEGER);
INSERT INTO InterestingTags VALUES(1, 1);
INSERT INTO InterestingTags VALUES(1, 2);
INSERT INTO InterestingTags VALUES(1, 5);
INSERT INTO InterestingTags VALUES(2, 6);
INSERT INTO InterestingTags VALUES(2, 7);
INSERT INTO InterestingTags VALUES(3, 10);
Test results
The outputs that I got were:
Query 1
1 a
4 d
41 D14
Query 2
1 a
3 c
4 d
31 C13
41 D14
51 E15
Query 3
21 B12
Query 4
11 A11
Query 5
1 a
41 D14
4 d
Query 6
4 d
31 C13
3 c
1 a
41 D14
51 E15
Clearly, if I wanted the output in a specific order, I would add an ORDER BY clause to the queries.