I have such tables :
Author(name,surname,id_author)
Author_book(id_author, id_book)
Book_theme(id_book,id_theme)
Theme(id_theme, description)
I need to find for each author theme which was used in his every book which was written by himself and wasn't used in any book where he was co-author. Sorry for my poor english.
I agree with David, it's easier to start off with the basics than continue to add to the query to get the answer your looking for. If I understand the question, your looking for all books where the theme is not part of a book that they co-authored...
select name, Author_book.id_book, Theme.id_theme, description
from Author
join Author_book on (Author.id_author = Author_book.id_author)
join Book_theme on (Author_book.id_book = Book_theme.id_book)
join Theme on (Book_theme.id_theme = Theme.id_theme)
where name = 'Bob'
and Book_theme.id_theme not in(select c.id_theme
from Author_book b
join Book_theme c on (b.id_book = c.id_book)
where
Author_book.id_book = b.id_book
and Author.id_author <> b.id_author)
SQL Fiddle Example
I can understand why you might not know where to begin. I find in such cases that it's best to start small and work your way out using subqueries.
To find the books where a given author is not the sole author:
SELECT id_author, id_book
FROM (
SELECT id_author, id_book, COUNT(*) OVER ( PARTITION BY id_book ) AS author_cnt
FROM author_book
) WHERE author_cnt >= 2;
To get the themes from the above books:
SELECT ab2.id_author, bt.id_theme
FROM (
SELECT id_author, id_book, COUNT(*) OVER ( PARTITION BY id_book ) AS author_cnt
FROM author_book
) ab2, book_theme bt
WHERE ab2.author_cnt >= 2
AND ab2.id_book = bt.id_book;
You can do the same for books where the author is the sole author:
SELECT ab1.id_author, bt.id_theme
FROM (
SELECT id_author, id_book, COUNT(*) OVER ( PARTITION BY id_book ) AS author_cnt
FROM author_book
) ab1, book_theme bt
WHERE ab1.author_cnt = 1
AND ab1.id_book = bt.id_book;
Then you can use MINUS to get the set of themes where the author is the sole author of a book, but not the ones where he is co-author:
SELECT ab1.id_author, bt.id_theme
FROM (
SELECT id_author, id_book, COUNT(*) OVER ( PARTITION BY id_book ) AS author_cnt
FROM author_book
) ab1, book_theme bt
WHERE ab1.author_cnt = 1
AND ab1.id_book = bt.id_book
MINUS
SELECT ab2.id_author, bt.id_theme
FROM (
SELECT id_author, id_book, COUNT(*) OVER ( PARTITION BY id_book ) AS author_cnt
FROM author_book
) ab2, book_theme bt
WHERE ab2.author_cnt >= 2
AND ab2.id_book = bt.id_book;
Related
I have created a search SQL query, I'm almost finished I only need to match the WHERE clause with an id and it's parent ids. Currently I only match with an id and not it's parents. I'm not sure how to solve this.
This is my query, the code where I need the change is where the comment "This part needs to match the tileid and it's parent tileids" is. Based on the [TileId] I need to get the [TileId] and it's parents.
WITH [TileSearch_CTE] ([TileId], [TypeId], [TypeName], [Title], [Info]) AS
(
SELECT TOP 10 [TileId], [TypeId], [TypeName], [Title], [Info]
FROM [Priox].[TileFullTextSearchNL]
INNER JOIN CONTAINSTABLE([Priox].[TileFullTextSearchNL], *, '"searchText*"') AS [CT]
ON [Priox].[TileFullTextSearchNL].[TileId] = [CT].[Key]
WHERE
(
NOT EXISTS
(
SELECT [intPortalTileFilterId]
FROM [Priox].[tblPortalTileFilter]
WHERE [intPortalTileIdFk] = [TileId] -- This part needs to match the tileid and it's parent tileids
)
OR EXISTS
(
SELECT [intPortalTileFilterId]
FROM [Priox].[tblPortalTileFilter]
WHERE [intPortalTileIdFk] = [TileId] -- This part needs to match the tileid and it's parent tileids
AND [intPortalFilterIdFk] IN (56)
)
)
ORDER BY [CT].[Rank] DESC
)
SELECT [TileId], [TypeName], [Title], [Info], [TP].[strName] AS [ParamName], [TPV].[strParamValue] AS [ParamValue]
FROM [TileSearch_CTE]
LEFT JOIN [Priox].[tblPortalTileParam] AS [TP] ON [TP].[intTileType] = [TileSearch_CTE].[TypeId]
LEFT JOIN [Priox].[tblPortalTileParamValue] AS [TPV] ON [TPV].[intTileParamIdFk] = [TP].[intPortalTileParamId] AND [TileSearch_CTE].[TileId] = [TPV].[intTileIdFk]
This is the database structure of the tiles.
So the query should do something like this.
SELECT [intPortalTileFilterId]
FROM [Priox].[tblPortalTileFilter]
WHERE [intPortalTileIdFk] IN (438,1317)
I fixed it myself, created a function that returns the parents and the child.
CREATE FUNCTION [Priox].[GetTileIdHierarchy]
(
#tileId int
)
RETURNS TABLE
AS
RETURN
(
WITH parents AS
(
SELECT [intPortalTileId], [intPortalTileId] AS [intParentIdFk]
FROM [Priox].[tblPortalTile] WHERE [intPortalTileId] = #tileId
UNION ALL
SELECT p.[intPortalTileId], [Priox].[tblPortalTile].[intParentIdFk]
FROM parents p
INNER JOIN [Priox].[tblPortalTile] on p.[intParentIdFk] = [Priox].[tblPortalTile].[intPortalTileId]
AND [Priox].[tblPortalTile].[intParentIdFk] IS NOT NULL
AND [Priox].[tblPortalTile].[intPortalTileId] <> [Priox].[tblPortalTile].[intParentIdFk]
)
SELECT [intPortalTileId], [intParentIdFk]
FROM parents
WHERE [intPortalTileId] = #tileId
)
GO
And changed my search query accordingly.
WITH [TileSearch_CTE] ([TileId], [TypeId], [TypeName], [Title], [Info], [Rank]) AS
(
SELECT TOP 10 [TileId], [TypeId], [TypeName], [Title], [Info],[CT].[Rank] AS [Rank]
FROM [Priox].[TileFullTextSearchNL]
INNER JOIN CONTAINSTABLE([Priox].[TileFullTextSearchNL], *, '"searchTerm*"') AS [CT]
ON [Priox].[TileFullTextSearchNL].[TileId] = [CT].[Key]
WHERE
(
NOT EXISTS
(
SELECT [intPortalTileFilterId]
FROM [Priox].[tblPortalTileFilter]
WHERE [intPortalTileIdFk] IN
(
SELECT [intParentIdFk] FROM [Priox].[GetTileIdHierarchy] ([TileId])
)
)
OR
EXISTS
(
SELECT [intPortalTileFilterId]
FROM [Priox].[tblPortalTileFilter]
WHERE [intPortalTileIdFk] IN
(
SELECT [intParentIdFk] FROM [Priox].[GetTileIdHierarchy] ([TileId])
)
AND [intPortalFilterIdFk] IN (51)
)
)
ORDER BY [CT].[Rank] DESC
)
SELECT [TileId], [TypeName], [Title], [Info], [TP].[strName] AS [ParamName], [TPV].[strParamValue] AS [ParamValue] , [Rank]
FROM [TileSearch_CTE]
LEFT JOIN [Priox].[tblPortalTileParam] AS [TP] ON [TP].[intTileType] = [TileSearch_CTE].[TypeId]
LEFT JOIN [Priox].[tblPortalTileParamValue] AS [TPV] ON [TPV].[intTileParamIdFk] = [TP].[intPortalTileParamId] AND [TileSearch_CTE].[TileId] = [TPV].[intTileIdFk]
I edited the starting post with the solution.
CREATE TABLE WRITTEN_BY
( Re_Id CHAR(15) NOT NULL,
Pub_Number INT NOT NULL,
PRIMARY KEY(Re_Id, Pub_Number),
FOREIGN KEY(Re_Id) REFERENCES RESEARCHER(Re_Id),
FOREIGN KEY(Pub_Number) REFERENCES PUBLICATION(Pub_Number));
CREATE TABLE WORKING_ON
( Re_Id CHAR(15) NOT NULL,
Pro_Code CHAR(15) NOT NULL,
PRIMARY KEY(Re_Id, Pro_Code, Subpro_Code)
FOREIGN KEY(Re_Id) REFERENCES RESEARCHER(Re_Id));
Re_Id stands for ID of a researcher
Pub_Number stands for ID of a publication
Pro_Code stands for ID of a project
Written_by table stores information about a Publication's ID and it's author
Working_on table stores information about a Project's ID and who is working on it
Now, I have this query :
For each project, find the researcher who wrote the most number of publications .
This is what i've done so far :
SELECT Pro_Code,WORK.Re_Id
FROM WORKING_ON AS WORK , WRITTEN_BY AS WRITE
WHERE WORK.Re_Id = WRITE.Re.Id
so I got a table which contains personal ID and project's ID of a researcher who has at least 1 publication. But what's next ? How to solve this problem?
You haven't said which platform you're on but try this. It handles the case where there are ties as well.
select g.Pro_Code, g.Re_Id, g.numpublished
from
(
SELECT work.Pro_Code, WORK.Re_Id, count(WRITE.pub_number) as numpublished
FROM WORKING_ON WORK JOIN WRITTEN_BY AS WRITE ON WORK.Re_Id = WRITE.Re_Id
GROUP BY work.Pro_Code, WORK.Re_Id
) g
inner join
(
select Pro_code, max(numpublished) as maxpublished
from (
SELECT work.Pro_Code, WORK.Re_Id, count(WRITE.pub_number) numpublished
FROM WORKING_ON WORK JOIN WRITTEN_BY AS WRITE ON WORK.Re_Id = WRITE.Re_Id
GROUP BY work.Pro_Code, WORK.Re_Id
) g2
group by Pro_code
) m
on m.Pro_code = g.Pro_Code and m.maxpublished = g.numpublished
Some platforms will allow you to write it this way:
with g as (
SELECT work.Pro_Code, WORK.Re_Id, count(WRITE.pub_number) as numpublished
FROM WORKING_ON WORK JOIN WRITTEN_BY AS WRITE ON WORK.Re_Id = WRITE.Re_Id
GROUP BY work.Pro_Code, WORK.Re_Id
)
select g.Pro_Code, g.Re_Id, g.numpublished
from g
inner join
(
select Pro_code, max(numpublished) as maxpublished
from g
group by Pro_code
) m
on m.Pro_code = g.Pro_Code and m.maxpublished = g.numpublished
I think that you are looking for something like the following :
select
tm.pro_code as pro_code,
tm.re_id as re_id,
max(total) as max_pub
from (
select *
from (
select
wo.pro_code as pro_code
wr.re_id as re_id,
count(wr.pub_number) as total
from
written_by wr,
working_on wo
where
wr.re_id = wo.re_id
group by wr.re_id,wo.pro_code
)
) tm
group by pro_code
If you are using MS SQL, this should work:
With cte as (
select a.Re_Id, Pub_Number,Pro_Code, COUNT(distinct Pub_Number) as pubs
from WRITTEN_BY a
inner join WORKING_ON b
on a.Re_Id = b.Re_Id)
SELECT Re_Id,pubs from cte
HAVING pubs = MAX(pubs)
GROUP BY Re_Id
I am new to PostgreSQL and I have a problem with the following query:
WITH relevant_einsatz AS (
SELECT einsatz.fahrzeug,einsatz.mannschaft
FROM einsatz
INNER JOIN bergefahrzeug ON einsatz.fahrzeug = bergefahrzeug.id
),
relevant_mannschaften AS (
SELECT DISTINCT relevant_einsatz.mannschaft
FROM relevant_einsatz
WHERE relevant_einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,relevant_mannschaften WHERE mannschaft.leiter = person.id AND relevant_mannschaften.mannschaft=mannschaft.id;
This query is working basically - but in "relevant_mannschaften" I am currently selecting each mannschaft, which has been to an relevant_einsatz with at least 1 bergefahrzeug.
Instead of this, I want to select into "relevant_mannschaften" each mannschaft, which has been to an relevant_einsatz WITH EACH from bergefahrzeug.
Does anybody know how to formulate this change?
The information you provide is rather rudimentary. But tuning into my mentalist skills, going out on a limb, I would guess this untangled version of the query does the job much faster:
SELECT m.id, m.rufname, p.id, p.nachname
FROM person p
JOIN mannschaft m ON m.leiter = p.id
JOIN (
SELECT e.mannschaft
FROM einsatz e
JOIN bergefahrzeug b ON b.id = e.fahrzeug -- may be redundant
GROUP BY e.mannschaft
HAVING count(DISTINCT e.fahrzeug)
= (SELECT count(*) FROM bergefahrzeug)
) e ON e.mannschaft = m.id
Explain:
In the subquery e I count how many DISTINCT mountain-vehicles (bergfahrzeug) have been used by a team (mannschaft) in all their deployments (einsatz): count(DISTINCT e.fahrzeug)
If that number matches the count in table bergfahrzeug: (SELECT count(*) FROM bergefahrzeug) - the team qualifies according to your description.
The rest of the query just fetches details from matching rows in mannschaft and person.
You don't need this line at all, if there are no other vehicles in play than bergfahrzeuge:
JOIN bergefahrzeug b ON b.id = e.fahrzeug
Basically, this is a special application of relational division. A lot more on the topic under this related question:
How to filter SQL results in a has-many-through relation
Do not know how to explain it, but here is an example how I solved this problem, just in case somebody has the some question one day.
WITH dfz AS (
SELECT DISTINCT fahrzeug,mannschaft FROM einsatz WHERE einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
), abc AS (
SELECT DISTINCT mannschaft FROM dfz
), einsatzmannschaften AS (
SELECT abc.mannschaft FROM abc WHERE (SELECT sum(dfz.fahrzeug) FROM dfz WHERE dfz.mannschaft = abc.mannschaft) = (SELECT sum(bergefahrzeug.id) FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,einsatzmannschaften WHERE mannschaft.leiter = person.id AND einsatzmannschaften.mannschaft=mannschaft.id;
I have two tables Forum and ForumCateory.
ForumCateoryId is the mapping field.
The Forum table contains the fields such as:
ForumId, Title,Description and ForumCategoryId
The ForumCategory table contains
ForumCategoryId,ForumCategory
I need to display all records from ForumCategory and top 1 Title and Description From Forum.
Try ti use top 1 in subquery
select *
from ForumCategory FC
join (select top 1 ForumId, Title,Description, ForumCategoryId
from Forum
where F.ForumCategoryId = FC.ForumCategoryId
) F
or try to use aggregate function (min for example) in subquery:
select *
from ForumCategory FC
join Forum F on F.ForumCategoryId = FC.ForumCategoryId
and F.ForumId = ( select min(F2.ForumId)
from Forum F2
where F2.ForumCategoryId = F.ForumCategoryId
)
I'm assuming you need last Forum for each Forum Category, so i ordered forum by ForumId descending.
select
FC.ForumCategoryId,
FC.ForumCategory,
F.Title as ForumTitle,
F.Description as ForumDescription
from ForumCategory as FC
outer apply
(
select top 1 TT.*
from Forum as TT
where TT.ForumCategoryId = FC.ForumCategoryId
order by TT.ForumId desc
) as F
you can also try something like this
select top 1 with ties
FC.ForumCategoryId,
FC.ForumCategory,
F.Title as ForumTitle,
F.Description as ForumDescription
from #ForumCategory as FC
left outer join #Forum as F on F.ForumCategoryId = FC.ForumCategoryId
order by
row_number() over (partition by FC.ForumCategoryId order by F.ForumId desc)
I have three tables: videos, videos_categories, and categories.
The tables look like this:
videos: video_id, title, etc...
videos_categories: video_id, category_id
categories: category_id, name, etc...
In my app, I allow a user to multiselect categories. When they do so, I need to return all videos that are in every selected category.
I ended up with this:
SELECT * FROM videos WHERE video_id IN (
SELECT c1.video_id FROM videos_categories AS c1
JOIN c2.videos_categories AS c2
ON c1.video_id = c2.video_id
WHERE c1.category_id = 1 AND c2.category_id = 2
)
But for every category I add to the multiselect, I have to add a join to my inner select:
SELECT * FROM videos WHERE video_id IN (
SELECT c1.video_id FROM videos_categories AS c1
JOIN videos_categories AS c2
ON c1.video_id = c2.video_id
JOIN videos_categories AS c3
ON c2.video_id = c3.video_id
WHERE c1.category_id = 1 AND c2.category_id = 2 AND c3.category_id = 3
)
I can't help but feel this is the really wrong way to do this, but I'm blocked trying to see the proper way to go about it.
if this is a primary key:
videos_categories: video_id, category_id
then a GROUP BY and HAVING should work, try this:
SELECT
*
FROM videos
WHERE video_id IN (SELECT
video_id
FROM videos_categories
WHERE category_id IN (1,2,3)
GROUP BY video_id
HAVING COUNT(video_id)=3
)
Sounds similar to SQL searching for rows that contain multiple criteria
To avoid having to another join for each category (and hence changing the structure of the query), you can put the categories into a temp table and then join against that.
CREATE TEMPORARY TABLE query_categories(category_id int);
INSERT INTO query_categories(category_id) VALUES(1);
INSERT INTO query_categories(category_id) VALUES(2);
INSERT INTO query_categories(category_id) VALUES(3);
SELECT * FROM videos v WHERE video_id IN (
SELECT video_id FROM video_categories vc JOIN query_categories q ON vc.category_id = qc.category_id
GROUP BY video_id
HAVING COUNT(*) = 3
)
Although this is ugly in its own way, of course. You may want to skip the temp table and just say 'category_id IN (...)' in the subquery.
Here's a FOR XML PATH solution:
--Sample data
CREATE TABLE Video
(
VideoID int,
VideoName varchar(50)
)
CREATE TABLE Videos_Categories
(
VideoID int,
CategoryID int
)
INSERT Video(VideoID, VideoName)
SELECT 1, 'Indiana Jones'
UNION ALL
SELECT 2, 'Star Trek'
INSERT Videos_Categories(VideoID, CategoryID)
SELECT 1, 1
UNION ALL
SELECT 1, 2
UNION ALL
SELECT 1, 3
UNION ALL
SELECT 2, 1
GO
--The query
;WITH GroupedVideos
AS
(
SELECT v.*,
SUBSTRING(
(SELECT (', ') + CAST(vc.CategoryID AS varchar(20))
FROM Videos_Categories AS vc
WHERE vc.VideoID = v.VideoID
AND vc.CategoryID IN (1,2)
ORDER BY vc.CategoryID
FOR XML PATH('')), 3, 2000) AS CatList
FROM Video AS v
)
SELECT *
FROM GroupedVideos
WHERE CatList = '1, 2'
(Ignore everything below - I misread the question)
Try
WHERE c1.category_id IN (1,2,3)
or
...
FROM videos v
JOIN Vedeos_categories vc ON v.video_id = vc.video_id
WHERE vc.category_id IN (1,2,3)
Multiple joins aren't at all necessary.
Edit: to put the solutions in context (I realize it's not obvious):
SELECT *
FROM videos
WHERE video_id IN
( SELECT c1.video_id
FROM videos_categories AS c1
WHERE c1.category_id = IN (1,2,3))
or
SELECT *
FROM videos v
JOIN Vedeos_categories vc ON v.video_id = vc.video_id
WHERE vc.category_id IN (1,2,3)