I need to make a single SQL request like this:
SELECT genres, count(*) FROM books WHERE genres LIKE 'Fiction%'
But I need to use many keyword like 'Nonfiction%', 'Historical' ect. The output should be a table:
Fiction 8654
Nonfiction 6543
Historical 2344
What SQL request I have to use to get this result?
Hopefully, you can just do:
SELECT genres, count(*)
FROM books
GROUP BY genres;
In fact, you could just do this if you had a table called BookGenres with one row per book and per genre. That is the right way to store this data.
In this case you appear to be looking only for the first genre in the list. You can use case:
select (case when genres like 'Fiction%' then 'Fiction'
when genres like 'Nonfiction%' then 'Nonfiction'
when genres like 'Historical%' then 'Historical'
else 'Other'
end) as genre, count(*)
from books
group by (case when genres like 'Fiction%' then 'Fiction'
when genres like 'Nonfiction%' then 'Nonfiction'
when genres like 'Historical%' then 'Historical'
else 'Other'
end);
You could use group by
SELECT genres, count(*)
FROM books
GROUP BY genres
or for a set of values
SELECT genres, count(*)
FROM books
WHERE genres in ( 'Fiction', 'Nonfiction', 'Historical')
GROUP BY genres
Related
For example, I have a simple table books:
author
book
Author-A
Book-A1
Author-A
Book-A2
Author-B
Book-B1
Author-C
Book-C1
Author-C
Book-C2
And I need to count books by each author, so I'll write:
select author, count(*) from books
group by author
# Author-A = 2
# Author-B = 1
# Author-C = 2
But now I need to count books by groups of authors:
groupA = ['Author-A', 'Author-C'],
groupB = ['Author-B']
select authorGroup, count(*) from books
group by {
case author in groupA -> 'groupA'
case author in groupB -> 'groupB'
} as authorGroup
# ['Author-A', 'Author-C'] = 4
# ['Author-B'] = 1
These groups can be different and come from another module.
What's the best way to write this requests? Maybe without union such as:
select author as 'groupA', count(*) from books
where author in { groupA }
union
select author as 'groupB', count(*) from books
where author in { groupB }
because there could be a lot of groups in request (~20-30)
The problem is that these groups can be absolutely dynamic: I can request ['Author-A', 'Author-B'] in one request as one group and ['Author-B', 'Author-C'] in another.
For example, the group is not something like author's country or genre. It can be totally dynamic.
The usual way is to JOIN on to a mapping table, which can be an in-line-view if need be (though I recommend an actual table, which can be indexed).
WITH
author_group AS
(
SELECT 'Author-A' AS author, 'Group-A' AS group_label
UNION ALL
SELECT 'Author-B' AS author, 'Group-B' AS group_label
UNION ALL
SELECT 'Author-C' AS author, 'Group-A' AS group_label
)
SELECT
author_group.group_label,
COUNT(*)
FROM
books
INNER JOIN
author_group
ON author_group.author = books.author
GROUP BY
author_group.group_label
Similar results can be achieved with CASE expressions, but it doesn't scale very well...
WITH
mapped_author AS
(
SELECT
*,
CASE author
WHEN 'Author-A' THEN 'Group-A'
WHEN 'Author-B' THEN 'Group-B'
WHEN 'Author-C' THEN 'Group-A'
END
AS author_group
FROM
books
)
SELECT
author_group,
COUNT(*)
FROM
mapped_author
GROUP BY
author_group
First you need to create a new table that show in what group is the author.
Later you just count
Like this:
select distinct a.group_auth, count(a.book) over (partition by a.group_auth)
from
(select
case when b.Author in [groupA] then 'groupA',
when b.Author in [groupB] then 'groupB'
end case as group_auth,
b.book as book
from books b
) as a
;
I have a table in PostgreSQL that contains demographic data for each province of my country.
Columns are: Province_name, professions, Number_of_people.
As you can see, Province_names are repeated for each profession.
How then can I get the province names not repeated and instead get the professions in separate columns?
It sounds like you want to pivot your table (Really: It is better to show data and expected output in your question!)
demo:db<>fiddle
This is the PostgreSQL way (since 9.4) to do that using the FILTER clause
SELECT
province,
SUM(people) FILTER (WHERE profession = 'teacher') AS teacher,
SUM(people) FILTER (WHERE profession = 'banker') AS banker,
SUM(people) FILTER (WHERE profession = 'supervillian') AS supervillian
FROM mytable
GROUP BY province
If you want to go a more common way, you can use the CASE clause
SELECT
province,
SUM(CASE WHEN profession = 'teacher' THEN people ELSE 0 END) AS teacher,
SUM(CASE WHEN profession = 'banker' THEN people ELSE 0 END) AS banker,
SUM(CASE WHEN profession = 'supervillian' THEN people ELSE 0 END) AS supervillian
FROM mytable
GROUP BY province
What you want to do is a pivot which is a little more complicated in Postgresql then in other rdbms. You can use the crosstab function. Find a introduction here: https://www.vertabelo.com/blog/technical-articles/creating-pivot-tables-in-postgresql-using-the-crosstab-function
for you it would look something like this:
SELECT *
FROM crosstab( 'select Province_name, professions, Number_of_people from table1 order by 1,2')
AS final_result(Province_name TEXT, data_scientist NUMERIC,data_engineer NUMERIC,data_architect NUMERIC,student NUMERIC);
Here is my film table:
FILM (Catalog_Num, Format, Title, Year, Number_Discs, Rating, Timing, Genre)
I want to concatenate the genre column if the year is before 1970 and it cannot be repeated,
for example, if the genre is Romantic --> (Classic Romance) its ok.
but is the genre is already Classic, it should remain Classic rather than (Classic Classic)
after that, I have to list id, title, and genre of all classic film.
Here is what I tried:
select genre|| 'Classic'
from film where (year <1970 and genre not in ('Classic'));
select film_id, title, genre
from inventory, film
where film.catalog_num = inventory.catalog_num and genre like '%Classic%';
But the output only shows all the genre in classic type, instead of romance classics.
Further, I have to finish in one query, but I don't know how to combine them.
Use a subquery to manipulate the data and feed that into your main query:
with films as (
select catalog_num
, title
, case
when (year <1970 and genre not in ('Classic'))
then 'Classic ' || genre
else genre end as genre
from film
)
select inventory.film_id
, films.title
, films.genre
from inventory
join films on films.catalog_num = inventory.catalog_num
where films.genre like '%Classic%';
Your question says you want Romantic --> (Classic Romance) but your posted code has genre||'Classic which is the other way round. I have changed the code to generate 'Classic Romance'.
Note: you haven't aliased the columns in the second query's projection, so I had to guess which columns come from film and which from inventory. You will need to correct any wrong guess.
If you only want your definition of class films, then you don't have to munge the genre. Just do:
select i.film_id, f.title, f.genre
from inventory i join
films f
on f.catalog_num = i.catalog_num
where f.genre like '%Classic%' or f.year < 1970;
If you still want "Classic" in the genre:
select i.film_id, f.title,
(case when f.year < 1970 and f.genre <> 'Classic'
then 'Classic ' || f.genre
else f.genre
end) as genre
from inventory i join
films f
on f.catalog_num = i.catalog_num
where f.genre like '%Classic%' or f.year < 1970;
Your question is a little vague on whether "Classic" can be part of a genre name rather than the entire name. So you might want:
(case when f.year < 1970 and f.genre not like '%Classic%'
then 'Classic ' || f.genre
else f.genre
end) as genre
Note that such comparisons are usually case-sensitive in Oracle, so you might need to take uppercase/lowercase into account as well.
I need a query which will return list of book from book table
but it will show total number of books and books available too ..
And for number of books it will count number of books with simular ISBN n amount count which will count number of books with simular ISBN with status available
Table structure
Bookid booktitle author ISBN edition publisher price supplier volume language status
status contains available,issued
I want result as
BookTitle Author Publisher Location Total available
Please also explain the query because I need to implement it on many tables
Queries i tried this far
select *,count(1) as Quantity from Book group by Book.BookTitle
It successfully gives me book list with total quantity
and
select *,count(1) as Quantity from Book where status='Available' group by Book.BookTitle
It Successfully gives me list of books with how many are available.
but i need combination of both total and available ..
In brevi:
select isbn, booktitle, sum(case when status='available' then 1 else 0 end) as avail, count(*) as total
from Book
group by isbn, booktitle
http://sqlfiddle.com/#!2/80a41/5
i definitely suggest you to read about 3rd normal form
You must group by all items in the SELECT query. You can either count(*) or sum(1). Here is how I would do:
SELECT BookId, Name, Available, count(*) as Quantity
FROM Book GROUP BY Bookid,Name,Available
select d.* from (select BookTitle,Author,Publisher,Location,count(BookTitle) as Total, Status, Count(Status) as StatusQuantity from Book group by BookTitle,Author,Publisher,Location,Status)d where d.Status='Available'
Try this one:
SELECT
BookTitle,
Author,
Publisher,
Location,
SUM(CASE WHEN status='available' THEN 1 ELSE 0 END) Available,
COUNT(*) AS Total
FROM Book
GROUP BY BookTitle,Author,Publisher,Location
Are you have any key constraints??
you must split the table..
Using the bookid or ISBN as a primary key,store the status in a separate table..
it will be easy for further process otherwise the records should be redundant..
The tables which are already created and unmodifiable are Book and Author.
Book (Title, Price, Yeareleased)
Author(AName,btitle,position)
Italized are the keys
and Btitle in Author is a foreign key that references Book(Title).
My SQL query:
select distinct AName
from Author
where position in (2,3) AND position<>1
group by AName
When I run this I get all the authors that have a book in position 2 or 3. Which is what I want but I'm only trying to get those authors which have a position 2 or 3 for all there books.
Essentially returning every author who was in the 2nd or 3rd position in all the books.
Maybe something like this would work:
select distinct AName
from #Author
where position in (2,3)
except
select distinct AName
from #Author
where position not in (2,3)
It makes a set of those authors who are in position 2 and 3 and then removes the ones who has another position.
It is not entirely clear whether someone who co-wrote 2 books and was listed second on one and third on the other should be selected or not. It is simpler to allow it; you can refine the query if you need the more stringent condition.
One way to answer this query makes the key observation that you're interested in authors for whom the count of the books they have written is equal to the count of the books where they are listed as second or third author.
Go for some TDQD — Test-Driven Query Design
Number of books each author wrote
SELECT Aname, COUNT(*) AS BookCount
FROM Author
GROUP BY AName
Number of books each author wrote as second or third author
SELECT Aname, COUNT(*) AS NonLeadAuthorCount
FROM Author
WHERE Position IN (2, 3)
GROUP BY Aname
Join those two where the counts are identical
SELECT X.Aname
FROM (SELECT Aname, COUNT(*) AS BookCount
FROM Author
GROUP BY AName
) AS X
JOIN (SELECT Aname, COUNT(*) AS NonLeadAuthorCount
FROM Author
WHERE Position IN (2, 3)
GROUP BY Aname
) AS Y
ON X.BookCount = Y.NonLeadAuthorCount
An alternative way of looking at is 'the set authors who have written a book in position 2 or 3 minus the set of authors who have written a book where the position is neither 2 nor 3'. For this, see the answer by jpw.
Trying to write standard SQL:
SELECT AName FROM (
SELECT
AName,
COUNT(*) AS count_all,
(SELECT COUNT(*) FROM Author AS aa WHERE aa.AName = a.AName AND position=2) AS count_2,
(SELECT COUNT(*) FROM Author AS aa WHERE aa.AName = a.AName AND position=3) AS count_3,
FROM Author AS a
GROUP BY AName
) AS t
WHERE count_all = count_2
OR count_all = count_3
I hope this work for you.
Try this:
select AName from Author where position=2 OR position=3 group by AName;
Try adding
and AName not in (select AName from Author where position != 2 and position != 3
Or something like that...