I have a table Books which has many properties. Properties are stored as key and value.
So if Books are:
1 LOTR
2 Harry Potter 1
3 Harry Potter 2
And properties are
id book_id key value
1 1 available 0
2 2 available 10
3 2 author Rowling
4 3 author Rowling
I'd like to get the results as:
1 LOTR
3 Harry Potter 2
since Book id 1 has 0 availability, and 2 has 10, and 3 does not have any availability info.
I know I can work with anti join, but I am not sure how to use it. I'm kind of new to anti joins.
Any help is appreciated.
I'm not 100% sure I'm understanding your question, but assuming you want to return all books that have no availability in the properties table, then here's one option using an outer join:
select b.*
from books b
left join properties p on b.id = p.book_id and p.key = 'available' and p.value > 0
where p.id is null
Depending on your database, you may need to cast the value column in the join.
Try this:
SELECT b.book_id, a.key, a.value
FROM Books AS B INNER JOIN AnotherTable AS A B.book_id = a.book_id
WHERE a.key = 'available' and (a.value = 0 OR a.value is null)
SELECT book_id, title
FROM Books as B
WHERE B.book_id NOT IN (
SELECT P.book_id
FROM properties as P
WHERE P.key = available AND P.value <> 0)
Note that <> means NOT EQUAL
Related
I have 3 tables that contain info about users. I would like to find how many of each type of item in each bucket a person has.
I'm not grasping why this wouldn't work. Does table join order matter or something I not aware of?
A sample of the tables:
PERSONS
ID
NAME
1
John
2
Jane
BUCKETS
ID
LABEL
PERSONID
1
Random
1
2
Vacation
1
THINGS
ID
BUCKETID
TYPE
VALUE
1
1
Image
abc12
2
1
Image
abc13
3
1
Video
abc34
4
1
Image
def12
5
1
Video
def34
SELECT P.NAME, B.LABEL, T.TYPE, COUNT(T.TYPE)
FROM PERSONS P
LEFT JOIN BUCKETS B ON
B.PERSONID = P.ID
LEFT JOIN THING T ON
T.BUCKETID = B.ID
GROUP BY P.NAME, B.LABEL, T.TYPE
I expect it to return:
John, Random, Images, 3
John, Random, Videos, 2
But it returns:
John, Random, Images, 5
John, Random, Videos, 5
I have tried COUNT(*) which results in the same and COUNT(DISTINCT T.TYPE) which of course returns 1 as the count.
This works perfectly in MySQL. Fiddle here: https://www.db-fiddle.com/f/vcb3wiPMSAFBXrWbgYxuMH/8
MSSQL is a different beast all together.
I think you want count(distinct) of some sort. I would speculate:
SELECT P.NAME, B.LABEL, T.TYPE, COUNT(DISTINCT T.BUCKETID)
FROM PERSONS P LEFT JOIN
BUCKETS B
ON B.PERSONID = P.ID LEFT JOIN
THING T
ON T.BUCKETID = B.ID
GROUP BY P.NAME, B.LABEL, T.TYPE;
I want to run an sql query and find all the books that have type="adventure" AND type="drama".
AND does not allow searching at the same time 2 different values of the same column.
My tables are like this
Books
Bid Bname Author
1 Sql loren
2 Monster Mike
3 Minnie Michel
----------
Copies
Cid Bid Type
1 1 Science
2 1 Teaching
3 2 Adventure
4 3 Romance
5 3 Drama
6 3 Adventure
The result I want:
Bid Title
3 Minnie
The tables can't change
There are several ways to do it, here is one using two exists conditions. Bottom line is that you have to check copies table twice.
SELECT * FROM books b
WHERE EXISTS
(
SELECT * FROM copies c1
WHERE b.bid = c1.bid
AND c1.type='adventure'
)
AND EXISTS
(
SELECT * FROM copies c2
WHERE b.bid = c2.bid
AND c2.type='drama'
)
You can achieve with JOIN, here is the DEMO. And you can use EXISTS as per #NenadZivkovic answer.
select
b.Bid,
Bname as Title
from books b
join Copies c
on b.Bid = c.Bid
and c.Type ='Drama'
join Copies c1
on c.Bid = c1.Bid
and c1.Type = 'Adventure'
group by
b.Bid,
Bname
order by
b.Bid
SQL Server 2014, data has been changed to protect the data. I hope the below makes sense.
I have to search a table for all categories with a Moderate risk. Then I need to go into another table (test) and retrieve those Moderate categories where only the Toby test has failed. Categories are unique in that first table.
So in the sample data below, categories 4 and 5 both are Moderate risk, and both also have Toby with a result of Fail in the test table.
However I want to exclude category 4 from my final output because that Category also has the Bill test that failed.
My goal is only to show Category 5 as an output. I can do this with a query and sub-query, where the sub-query returns categories 4 and 5, and the main query filters on that. But can I achieve the same thing with a single query somehow?
Update:
My current query is below. I've had to munge it a bit for this post, I hope it's sufficient. Basically the sub-query pulls in all categories that have any failed test for a Moderate category, and the main query filters out any categories with other failures.
select tt.category, tt.[name]
from test_table mt
where tt.category in (select mt.category
from main_table mt
inner join test_table tt on tt.category = mt.category
where mt.risk= 'Moderate' and tt.result = 'Fail')
and tt.[name] <> 'Toby'
and tt.[result] = 'Fail'
Output:
category risk
----------------------
1 Minimal
2 Critical
3 Elevated
4 Moderate
5 Moderate
category name result
-------------------------------
1 Mark Pass
1 Bill No Result
1 John Pass
1 Toby Pass
2 Mark Pass
2 Bill No Result
2 John Fail
2 Toby Pass
3 Mark Pass
3 Bill No Result
3 John Pass
3 Toby Pass
4 Mark Pass
4 Bill Fail
4 John Pass
4 Toby Fail
5 Mark Pass
5 Bill Pass
5 John Pass
5 Toby Fail
Join the tables, group by category and set the conditions in the HAVING clause:
select c.category
from categories c inner join test t
on c.category = t.category
where c.risk = 'Moderate'
group by c.category
having
sum(case when t.name = 'Toby' and t.result = 'Fail' then 1 else 0 end) > 0
and
sum(case when t.name <> 'Toby' and t.result = 'Fail' then 1 else 0 end) = 0
or:
select c.category
from categories c inner join test t
on c.category = t.category
where c.risk = 'Moderate' and t.result = 'Fail'
group by c.category
having count(distinct t.name) = 1 and max(t.name) = 'Toby'
See the demo.
Results:
> | category |
> | -------: |
> | 5 |
A pretty direct reading of your question suggests exists/not exists:
select c.*
from categories c
where c.risk = 'Moderate' and
exists (select 1
from tests t
where t.category = c.category and
t.name = 'Toby' and
t.result = 'Fail'
) and
not exists (select 1
from tests t
where t.category = c.category and
t.name <> 'Toby' and
t.result = 'Fail'
);
Conditional aggregation is also a viable solution -- and a solution that I prefer when the filter is on only one table. However, the filtering here is on two tables, and this will use of an index on test(category, result, name) and avoid the outer aggregation.
So I have three tables:
authors:
--------
ID Name
1 John
2 Sue
3 Mike
authors_publications:
---------------------
AuthorID PaperID
1 1
1 2
2 2
3 1
3 2
3 3
publications:
-------------
ID year
1 2004
2 2005
3 2004
I'm trying to join them so that I count the number of publications each author has had on 2004. If they didn't publish anything then it should be zero
ideally the result should look like this:
ID Name Publications_2004
1 John 1
2 Sue 0
3 Mike 2
I tried the following:
select a.ID, Name, count(*) as Publications_2004
from authors_publications as ap left join authors as a on ap.AuthorID=a.ID left join publications as p on p.ID=ap.PaperID
where year=2004
group by ap.AuthorID
I don't understand why it's not working. Its completely removing any authors that haven't published in 2004.
Your WHERE statement is taking the result set returned from the JOIN's and them trimming off records where year<>2004.
To get around this you can do a few different things
You can apply a filter to the publications table in the ON statement when joining. This will filter the results before joining
SELECT a.ID,
NAME,
count(*) AS Publications_2004
FROM authors_publications AS ap
LEFT JOIN authors AS a
ON ap.AuthorID = a.ID
LEFT JOIN publications AS p
ON p.ID = ap.PaperID AND
p.year = 2004
GROUP BY ap.AuthorID
You could use a case statement instead of a WHERE:
SELECT a.ID,
NAME,
SUM(CASE WHEN p.year = 2004 THEN 1 ELSE 0) END AS Publications_2004
FROM authors_publications AS ap
LEFT JOIN authors AS a
ON ap.AuthorID = a.ID
LEFT JOIN publications AS p
ON p.ID = ap.PaperID
GROUP BY ap.AuthorID, NAME
You could use a subquery to pre-filter the publications table to only 2004 records, which is just explicitly doing what was implicit in the first option:
SELECT a.ID,
NAME,
count(*) AS Publications_2004
FROM authors_publications AS ap
LEFT JOIN authors AS a
ON ap.AuthorID = a.ID
LEFT JOIN (SELECT * FROM publications WHERE AND year = 2004) AS p
ON p.ID = ap.PaperID
GROUP BY ap.AuthorID, NAME
Also, because you are not aggregating NAME with a formula, you should add that to your GROUP BY otherwise you may get funky results.
I faced an interview and below is the question. The tables are below
Table1: Book (BookId is primary key)
BookId | Book_Title
1 | Book1
2 | Book2
3 | Book3
4 | Book4
5 | Book5
Table2: Book_Copies
BookId | BranchId | No_of_copies
1 1 2
1 2 5
2 1 0
2 2 2
2 3 0
3 1 0
3 2 0
The output should list all the Books with no stock at all as shown below.
Output:
Book_Title
---------
Book3
Book4
Book5
Please note that there are two possibilities. Either the 'No_of_copies' could be 0 or no record for a Book in the "Book_Copies" table.
For e.g.
the total number of copies for "Book3" is 0. The output should
include "Book3"
the total number of copies for "Book1 and "Book2" is 7 and 2
respectively. Both "Book1" and "Book2" shouldn't display
there is no entry for Book4 and Book5 in "Book_Copies", so both
should be included in output
After coming home, I wrote the below query after so many trials :)
select B.Book_Title
from
(
select BC.BookId, sum(BC.No_of_copies) as 'No of copies'
from Book_Copies BC
group by BC.BookId
having sum(BC.No_of_copies) = 0
union
select B.BookId, BC.No_of_copies
from Book B
left outer join Book_Copies BC on B.BookId = BC.BookId
where BC.BookId is null
)
as BookIds_withNoStock
inner join Book B on B.BookId = BookIds_withNoStock.BookId
This query works fine and tested properly.
Is there any way we can improve this query? like complexity, performance etc
If we can improve it, it would be helpful if you can provide the optimized query and reason. Thanks
You want books with no stock. I would approach this with a left outer join, aggregation, and a having clause:
select b.bookid, b.book_title
from book b left join
book_copies bc
on b.bookid = bc.bookid
group by b.bookid, b.book_title
having coalesce(sum(bc.no_of_copies), 0) = 0;
Try This
SELECT B.Book_Title
From Book B LEFT JOIN book_copies BC ON B.Bookid = BC.Bookid
Group By B.Book_Title
Having SUM(ISNULL(BC.no_of_copies,0)) = 0
FIddle Demo
Output:
BOOK_TITLE
Book3
Book4
Book5
The below code worked for me:
SELECT a.Book_Title
FROM #books a
left join(
SELECT BookId,sum(No_of_copies) Cnt
FROM #Book_Copies
GROUP BY BookId) b
ON a.BookId = b.BookId
WHERE b.cnt = 0 OR b.Cnt is null
try this:
select Book_Title from Book left join Book_Copies on Book.BookId = Book_Copies.BookId
group by Book_Title having
SUM(Book_Copies.No_of_copies) IS NULL OR SUM(Book_Copies.No_of_copies)=0
select book_title
from books
where book_id NOT IN
(
select book_id
from book_copies
group by book_id
having sum( no_copies) > 0
)