MySQL Query: select all the books a user has completed reviewing - mysql

I have the following 5 tables:
users(user_id)
books(book_id, author_id)
source_phrases(source_phrase_id, book_id, phrase)
synonym_phrases(synonym_phrase_id, source_phrase_id, reader_id, synonym)
synonym_ratings(synonym_ratings_id, synonym_phrase_id, rater_id, rating)
I am trying to get a query that will select all the books a user has completed reviewing.
A user will have completed reviewing a book if they have done the following for each source phrase:
User has suggested a synonym for the source phrase (reader_id in synonym_phrases table is the users id)
OR
The user has rated a synonym for the source phrase (rater_id in synonym_ratings table is the users id)

SELECT b.*
FROM books b
WHERE book_id NOT IN
(
SELECT sp.book_id
FROM source_phrases sp
WHERE source_phrase_id NOT IN
(
SELECT syp.source_phrase_id
FROM synonym_phrases syp
WHERE reader_id = #user_id
)
AND source_phrase_id NOT IN
(
SELECT syp.source_phrase_id
FROM synonym_phrases syp
JOIN synonym_ratings sr
ON sr.synonym_phrase_id = syp.synonym_phrase_id
AND sr.rater_id = #user_id
)
)
AND book_id IN
(
SELECT sp.book_id
FROM source_phrases sp
)

Related

My sql questions about retrieving multiple values from different tables

MySQL tables:
author
(aEmail*
,fName
,lName
,bDate
,city
)
reviewer
(rEmail*
,phoneNumber
,lName
,fName
,city
)
paper
(paperId*
,title
,abstract
,submissionDate
)
author_paper
(authorId*
,paperId*
,isContact
)
paper_review
(paperId*
,reviewerId*
,score
,reviewSubmissionDate
,reviewInvitationDate
)
* = (component of) PRIMARY KEY
How would I find authors that have made more than 3 papers and return their names in a query and that the authors age is above a certain age (aEmail = authorId)
One option uses a correlated subquery for filtering: we can just count how many rows each author has in author_paper.
select a.*
from author a
where (select count(*) from author_paper ap where ap.authorid = a.id) > 3
I am unsure about the correlation clause. Maybe you want to use the author's email instead:
where ap.authorid = a.aemail

MySQL select Table with reviews (even with empty fields)

I need to calculate number of reviews and average grade from client_reviews table. And add these 2 fields to client_impression table. This MySQL reguest is working bu the problem that display fields only with reviews. But I need to display all fields even with no reviews. How to make it?
SELECT `impression`.*, review.count, review.grade FROM `client_impression` AS `impression`
JOIN (SELECT impression_id, count(id) AS `count`, SUM(stars) AS `grade`
FROM `client_reviews` AS rev
WHERE rev.status=1 AND (SELECT city_id FROM client_impression_offer WHERE id=rev.impression_offer_id) = 1
GROUP BY rev.impression_id ) AS review ON review.impression_id = `impression`.`id`
WHERE `impression`.`city_1`=1 AND `impression`.`id` IN (SELECT `impression` FROM `impression_to_rank` WHERE `rank`=6)
Use left join
selct `impression`.*, review.count, review.grade FROM `client_impression` AS `impression`
left join
(
SELECT impression_id, count(id) AS `count`, SUM(stars) AS `grade`
FROM `client_reviews` AS rev WHERE rev.status=1 and
(SELECT city_id FROM client_impression_offer WHERE id=rev.impression_offer_id) = 1
group by impression_id
) review ON review.impression_id = `impression`.`id` and
`impression`.`city_1`=1 AND `impression`.`id` IN (SELECT `impression` FROM `impression_to_rank` WHERE `rank`=6)

SQL Subquery using HAVING COUNT

My question is: How do I show the name of each artist which has recorded at least one title at a studio where the band: "The Bullets" have recorded?
I have formulated the following query:
select ar.artistname, ti.titleid from artists ar
where exists
(
select 0
from titles ti where ti.artistid = ar.artistid
and exists
(
select 0
from studios s
where s.studioid = ti.studioid
and ar.artistname = 'The Bullets'
)
);
However, I need to include HAVING COUNT(ti.titleid) > 0 to satisfy this part, "each artist which has recorded at least one title" in the question.
I also am unsure as to how to match the artistname, "The Bullets" who have recorded at least one studio.
The Artists table resmebles the following:
Artists
-------
ArtistID, ArtistName, City
The Tracks table resmebles the following:
Tracks
------
TitleID, ArtistID, StudioID
The Studios table resmebles the folllowing:
Studios
-------
StudioID, StudioName, Address
I also must specify that I cannot use joins, e.g., a performance preference.
Maybe like this?
select ArtistName from Artists where ArtistID in (
select ArtistID from Tracks where StudioID in (
select StudioID from Tracks where ArtistID in (
select ArtistId from Artists where ArtistName='The Bullets'
)
)
)
I don't see why do you think having is needed.
The studio(s) where the bullets recorded
SELECT StudioID
FROM Sudios S
JOIN Tracks T ON S.StudioID = S.StudioID
JOIN Artists A ON T.ArtistID = A.ArtistID AND A.ArtistName = 'The Bullets'
Every Artist who recorded there
SELECT A1.ArtistName, A1.City
FROM Artist A1
JOIN Tracks T1 ON T1.ArtistID = A2.ArtistID
WHERE T1.SudioID IN
(
SELECT StudioID
FROM Sudios S
JOIN Tracks T ON S.StudioID = S.StudioID
JOIN Artists A ON T.ArtistID = A.ArtistID AND A.ArtistName = 'The Bullets'
) T
These two requirements are quite silly - using HAVING COUNT(*) > 0 and no joins. I've never heard of choosing sub-queries over joins to enhance performance.
However, I think this query fulfills these requirements.
SELECT a.ArtistName FROM Artist a
WHERE EXISTS
(
SELECT t1.ArtistId FROM Track t1
WHERE t1.ArtistId = a.ArtistId
AND EXISTS
(
SELECT * FROM Track t2
WHERE t1.StudioId = t2.StudioID
AND t2.ArtistName = 'The Bullets'
)
GROUP BY t1.ArtistId, t1.StudioId
HAVING COUNT(*) > 0
);

Substitute for MySQL's variables in PostgreSQL?

We often use quick one-off SQL files to insert or update data in an existing database. The SQL is usually written by a developer, tested on the development system, and then imported in the production DB with psql -U dbuser dbname < file.sql.
A (trivial) example might look like this:
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
) VALUES
(
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
'Frodo Baggins',
'Ring bearer',
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
),
(
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
'Samwise Gamgee',
'Rope bearer',
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
),
(
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
'Peregrin Took',
'Ent rider',
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
);
While this works, there's a lot of repetitive code in the subqueries. It would be nice (more efficient and less error prone) to store the relevant values for companies.id and users.id in temporary variables. In this construed example, the performance difference is likely minimal, but in practice we do have more complex queries and updates, and there are often more than three updated/inserted records.
The same example written for MySQL looks like this:
SELECT #company_id := id FROM companies WHERE name = 'Acme Fellowship';
SELECT #admin_id := id FROM users WHERE login = 'admin';
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
) VALUES
(#company_id, 'Frodo Baggins', 'Ring bearer', #admin_id, #admin_id),
(#company_id, 'Samwise Gamgee', 'Rope bearer', #admin_id, #admin_id),
(#company_id, 'Peregrin Took', 'Ent rider', #admin_id, #admin_id);
Is there any way to achieve something similar in PostgreSQL?
What I've looked at:
psql's session variables (with \set): cannot be used to store query results
plpgsql: can only be used in a procedure (we're still running 8.4)
temporary tables: I can't see how to use them without creating ugly and convoluted statements
If there is no direct equivalent for Postgres, what do you think would be the least clumsy way to produce update files of this kind?
Use VALUES() in a SELECT, that should work:
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
)
SELECT
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
name,
position,
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
FROM
(VALUES -- all your new content here
('Frodo Baggins', 'Ring bearer'),
('Samwise Gamgee', 'Rope bearer'),
('Peregrin Took', 'Ent rider')
) content(name, position); -- use some aliases to make it readable
Consider using CTEs or subqueries to query values once and inserted them many times.
This way, you can replace MySQL style variables with standard SQL.
INSERT INTO employees
(company_id, name, position, created_by, last_modified_by)
SELECT c.id , name, position, u.id , u.id
FROM (SELECT id FROM companies WHERE name = 'Acme Fellowship') c
,(SELECT id FROM users WHERE login = 'admin') u
,(VALUES
('Frodo Baggins', 'Ring bearer')
,('Samwise Gamgee', 'Rope bearer')
,('Peregrin Took', 'Ent rider')
) v(name, position)
Assuming that companies.name and users.login are, in fact, unique. Multiple hits would multiply the rows to be inserted.
Read about the INSERT command in the manual.
Here is my test setup with temporary tables in case anyone wants to have a quick look:
CREATE TEMP TABLE companies (id int, name text);
INSERT INTO companies VALUES (17, 'Acme Fellowship');
CREATE TEMP TABLE users (id int, login text);
INSERT INTO users VALUES (9, 'admin');
CREATE TEMP TABLE employees (
company_id int
,name text
,position text
,created_by int
,last_modified_by int);
This is an old question, but I found that using WITH statements made my life easier :)
WITH c AS (
SELECT company_id,
FROM companies
WHERE name = 'Acme Fellowship'
), u AS (
SELECT *
FROM users
WHERE login = 'admin'
), n AS (
SELECT *
FROM
(VALUES -- all your new content here
('Frodo Baggins', 'Ring bearer'),
('Samwise Gamgee', 'Rope bearer'),
('Peregrin Took', 'Ent rider')
) content(name, position)
)
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
)
SELECT c.company_id, n.name, n.position, u.id, u.id
FROM c, u, n

selecting where in multiple join tables

I have the logic worked out, just not sure how to best write this query.
the logic is
we have a deal ID of 1
a deal is linked to multiple regions
a deal is linked to multiple interests
a user is linked to multiple regions
a user is linked to multiple interests
we want all users where....
the user is linked to the same region as a deal
userRegionLink url, dealRegionLink drl
url.regionId is in drl.regionId where drl.dealId = 1
the user is linked to the same interest as a deal
userInterestLink uil, dealInterestLink dil
uil.interestId is in dil.interestId where dil.dealId = 1
this would give us a list of the users
now we need to select distinct from the list so we only end up sending each user a single email
But I have no idea what the best way to write this query would be.
We are dealing with a few tables here
We have
users which has all the user Information in it userId and other columns not important
userInterestLink which has userId and interestId
dealInterestLink which has dealId and interestId
userRegionLink which has userId and regionId
dealRegionLink which has dealId and regionId
so what we are wanting in the end is all the user info which matches.
I take RC's answer and modify it
SELECT u.userId, uil.interestId, url.regionId FROM users u
JOIN userInterestLink uil ON (uil.userId = u.userId)
JOIN userRegionLink url ON (url.userId = u.userId)
WHERE interestId IN (
SELECT DISTINCT interestId FROM dealInterestLink WHERE dealId = 1
) AND regionId IN (
SELECT DISTINCT regionId FROM dealRegionLink WHERE dealId = 1
)
as there is no need for LEFT JOIN if I exclude the NULL rows afterwards.
A more "symmetric" version without subqueries and with USING would be
SELECT u.userId, uil.interestId, url.regionId FROM users u
JOIN userInterestLink uil USING (userId)
JOIN userRegionLink url USING (userId)
JOIN dealInterestLink dil USING (interestId)
JOIN dealRegionLink drl USING (regionId, dealId)
WHERE dealId = 1
Untested as well.
Something like:
SELECT u.userId, uil.interestId, url.regionId FROM users u
LEFT JOIN userInterestLink uil ON (uil.userId = u.userId)
LEFT JOIN userRegionLink url ON (url.userId = u.userId)
WHERE uil.interestId IS NOT NULL AND uil.interestId IN (
SELECT DISTINCT interestId FROM dealInterestLink WHERE dealId = 1
) AND url.regionId IS NOT NULL AND url.regionId IN (
SELECT DISTINCT regionId FROM dealRegionLink WHERE dealId = 1
)
? If result is OK, you can then SELECT DISTINCT u.userId FROM users u -- ...
(not tested)
SELECT `u`.*
FROM `users` AS `u`
JOIN `userRegionLink` `userReg` USING ( `userId` )
JOIN `userInterestLink` `userInt` USING ( `userId` )
JOIN `dealInterestLink` `dealInt` USING ( `interestId` )
JOIN `dealRegionLink` `dealReg` USING ( `regionId` )
JOIN `deal` `d` ON ( `dealInt`.`dealId` && `dealReg`.`dealId` && `d`.`dealId` = 1 )
GROUP BY `u`.`userId`
Tested locally using dummy data and presumed schema. Worked OK.