Append the results of subquery in oracle - database

Suppose I've query like below in oracle:
SELECT author_id, author_name,
(
SELECT book_title FROM books
WHERE author_id = a.author_id
)
FROM author a
Here in subquery for particular author_id can have multiple books written by them. But Oracle won't allow to return multiple rows from subquery. So how can I append the different book_title by comma seperated for same author in oracle in a row.

LISTAGG might help:
select
a.author_id,
a.author_name,
listagg(b.book_title, ',') within group (order by null) list_of_books
from author a join books b on b.author_id = a.author_id
group by a.author_id, a.author_name;

Related

What to use to replace the subquery in the SQL Server IN predicate

Usually we have a subquery for the IN predicate. But I like to store the result from a subquery and use it in serveral IN predicates. I tried a temporary table but got an error - "Error 137: Must declare the scalar variable #xxx".
Say I have a book table Books:
ISBN | Title | Price | InStock(true/false)
DECLARE #SoldBooks TABLE (ISBN int)
INSERT INTO #SoldBooks(ISBN) SELECT DISTINCT ISBN FROM Books WHERE InStock = 0
SELECT Title FROM Books WHERE ISBN IN #SoldBooks
I am just using this script as an example to illustrate my issue. Please don't tell me there are better ways to get titles. Obviously the following code works,
SELECT Title FROM Books WHERE ISBN IN (SELECT DISTINCT ISBN FROM Books WHERE InStock = 0)
But I really like to find a way store the subquery. Thank you for any suggestions.
You can use a table variable just like you describe:
DECLARE #SoldBooks TABLE (ISBN int)
INSERT INTO #SoldBooks(ISBN)
SELECT DISTINCT ISBN
FROM Books
WHERE InStock = 0
Then you have to treat the table variable like a normal table:
SELECT Title
FROM Books
WHERE ISBN IN (SELECT ISBN FROM #SoldBooks)
Use a common table expression:
WITH ExpressionName As (
Your Subquery goes here
)
select * from someTable1 t1
inner join someTable2 t2 ON ...
where t1.SomeValue IN (select * from ExpressionName)
and t2.SomeValue IN (select * from ExpressionName)
Additionally, if you're doing this often, it's typically much better to use a JOIN for these situations:
WITH ExpressionName As (
Your Subquery goes here
)
select *
from tableTable
inner join someExpression ON ....
Finally, if by "several IN predicates" you mean more to re-use this same expression across different queries and reports instead of within the same query, you should create a view:
CreateView SoldBooks As
SELECT DISTINCT ISBN
FROM Books
WHERE InStock = 0
And now you can use this view in SELECT situations as if it were a table. The same advice about JOIN vs IN() still applies.

SQL queries combined into one row

I'm having some difficulty combining the following queries, so that the results display in one row rather than in multiple rows:
SELECT value FROM dbo.parameter WHERE name='xxxxx.name'
SELECT dbo.contest.name AS Event_Name
FROM contest
INNER JOIN open_box on open_box.contest_id = contest.id
GROUP BY dbo.contest.name
SELECT COUNT(*) FROM open_option AS total_people
SELECT SUM(scanned) AS TotalScanned,SUM(number) AS Totalnumber
FROM dbo.open_box
GROUP BY contest_id
SELECT COUNT(*) FROM open AS reff
WHERE refer = 'True'
I would like to display data from the fields in each column similar to what is shown in the image below. Any help is appreciated!
Tab's solution is fine, I just wanted to show an alternative way of doing this. The following statement uses subqueries to get the information in one row:
SELECT
[xxxx.name]=(SELECT value FROM dbo.parameter WHERE name='xxxxx.name'),
[Event Name]=(SELECT dbo.contest.name
FROM contest
INNER JOIN open_box on open_box.contest_id = contest.id
GROUP BY dbo.contest.name),
[Total People]=(SELECT COUNT(*) FROM open_option),
[Total Scanned]=(SELECT SUM(scanned)
FROM dbo.open_box
GROUP BY contest_id),
[Total Number]=(SELECT SUM(number)
FROM dbo.open_box
GROUP BY contest_id),
Ref=(SELECT COUNT(*) FROM open WHERE refer = 'True');
This requires the Total Scanned and Total Number to be queried seperately.
Update: if you then want to INSERT that into another table there are essentially two ways to do that.
Create the table directly from the SELECT statement:
SELECT
-- the fields from the first query
INTO
[database_name].[schema_name].[new_table_name]; -- creates table new_table_name
Insert into a table that already exists from the INSERT
INSERT INTO [database_name].[schema_name].[existing_table_name](
-- the fields in the existing_table_name
)
SELECT
-- the fields from the first query
Just CROSS JOIN the five queries as derived tables:
SELECT * FROM (
Query1
) AS q1
CROSS JOIN (
Query2
) AS q2
CROSS JOIN (...
Assuming that each of your individual queries only returns one row, then this CROSS JOIN should result in only one row.

sql server get several group by results from one query result

Is it possible to to retrieve several group by results from one query?
Currently I've a freetext book-title search system which returns the top X rows:
First it queries the book-titles
SELECT TOP 16 grouped_location, grouped_author, book_title
FROM books
WHERE book_title like '%foo%'
then it queries the location group
SELECT grouped_location, COUNT(*)
FROM books
WHERE book_title like '%foo%'
GROUP BY grouped_location
then it queries the author group: ....
Is it possible to retrieve this information with one search?
I have no problem by sending multiple command to the SQL server, but the goal is that the SQL server only performs one search and not using up all resources by searching three times.
Please keep in mind that a client-side solution, by returning all records to the client and calculate the grouped results, is not an option. It requires to only return the TOP X records due to performance reasons.
This query will give you row détails, with count by grouped_location for each row.
Change the ORDER BY to meet your requirements
select top 16 grouped_location, grouped_author, book_title,
count(*) over (partition by grouped_location) as [count]
FROM books
WHERE book_title like '%foo%'
-- order by grouped_author or some other column
order by [count] desc
To see information only grouped by grouped_location, you could so something like this..
SELECT grouped_location , COUNT(*) Totals
FROM
(
SELECT TOP 16 grouped_location, grouped_author, book_title
FROM books
WHERE book_title like '%foo%'
ORDER BY Some_Column
) Q
GROUP BY grouped_location
To see information grouped by All the columns, you could so something like this..
SELECT grouped_location, grouped_author, book_title, COUNT(*) Totals
FROM
(
SELECT TOP 16 grouped_location, grouped_author, book_title
FROM books
WHERE book_title like '%foo%'
ORDER BY Some_Column
) Q
GROUP BY grouped_location, grouped_author, book_title

Multiple Select against one CTE

I have a CTE query filtering a table Student
Student
(
StudentId PK,
FirstName ,
LastName,
GenderId,
ExperienceId,
NationalityId,
CityId
)
Based on a lot filters (multiple cities, gender, multiple experiences (1, 2, 3), multiple nationalites), I create a CTE by using dynamic sql and joining the student table with a user defined tables (CityTable, NationalityTable,...)
After that I have to retrieve the count of student by each filter like
CityId City Count
NationalityId Nationality Count
Same thing the other filter.
Can I do something like
;With CTE(
Select
FROM Student
Inner JOIN ...
INNER JOIN ....)
SELECT CityId,City,Count(studentId)
FROm CTE
GROUP BY CityId,City
SELECT GenderId,Gender,Count
FROM CTE
GROUP BY GenderId,Gender
I want to something like what LinkedIn is doing with search(people search,job search)
http://www.linkedin.com/search/fpsearch?type=people&keywords=sales+manager&pplSearchOrigin=GLHD&pageKey=member-home
It's so fast and do the same thing.
You can not use multiple select but you can use more than one CTE like this.
WITH CTEA
AS
(
SELECT 'Coulmn1' A,'Coulmn2' B
),
CETB
AS
(
SELECT 'CoulmnX' X,'CoulmnY' Y
)
SELECT * FROM CTEA, CETB
For getting count use RowNumber and CTE some think like this.
ROW_NUMBER() OVER ( ORDER BY COLUMN NAME )AS RowNumber,
Count(1) OVER() AS TotalRecordsFound
Please let me know if you need more information on this.
Sample for your reference.
With CTE AS (
Select StudentId, S.CityId, S.GenderId
FROM Student S
Inner JOIN CITY C
ON S.CityId = C.CityId
INNER JOIN GENDER G
ON S.GenderId = G.GenderId)
,
GENDER
AS
(
SELECT GenderId
FROM CTE
GROUP BY GenderId
)
SELECT * FROM GENDER, CTE
It is not possible to get multiple result sets from a single CTE.
You can however use a table variable to cache some of the information and use it later instead of issuing the same complex query multiple times:
declare #relevantStudent table (StudentID int);
insert into #relevantStudent
select s.StudentID from Students s
join ...
where ...
-- now issue the multiple queries
select s.GenderID, count(*)
from student s
join #relevantStudent r on r.StudentID = s.StudentID
group by s.GenderID
select s.CityID, count(*)
from student s
join #relevantStudent r on r.StudentID = s.StudentID
group by s.CityID
The trick is to store only the minimum required information in the table variable.
As with any query whether this will actually improve performance vs. issuing the queries independently depends on many things (how big the table variable data set is, how complex is the query used to populate it and how complex are the subsequent joins/subselects against the table variable, etc.).
Do a UNION ALL to do multiple SELECT and concatenate the results together into one table.
;WITH CTE AS(
SELECT
FROM Student
INNER JOIN ...
INNER JOIN ....)
SELECT CityId,City,Count(studentId),NULL,NULL
FROM CTE
GROUP BY CityId,City
UNION ALL
SELECT NULL,NULL,NULL,GenderId,Gender,Count
FROM CTE
GROUP BY GenderId,Gender
Note: The NULL values above just allow the two results to have matching columns, so the results can be concatenated.
I know this is a very old question, but here's a solution I just used. I have a stored procedure that returns a PAGE of search results, and I also need it to return the total count matching the query parameters.
WITH results AS (...complicated foo here...)
SELECT results.*,
CASE
WHEN #page=0 THEN (SELECT COUNT(*) FROM results)
ELSE -1
END AS totalCount
FROM results
ORDER BY bar
OFFSET #page * #pageSize ROWS FETCH NEXT #pageSize ROWS ONLY;
With this approach, there's a small "hit" on the first results page to get the count, and for the remaining pages, I pass back "-1" to avoid the hit (I assume the number of results won't change during the user session). Even though totalCount is returned for every row of the first page of results, it's only computed once.
My CTE is doing a bunch of filtering based on stored procedure arguments, so I couldn't just move it to a view and query it twice. This approach allows avoid having to duplicate the CTE's logic just to get a count.

How to create RowNum column in SQL Server?

In Oracle we have "rownum".
What can I do in SQL Server?
In SQL Server 2005 (and 2008) you can use the ROW_NUMBER function, coupled with the OVER clause to determine the order in which the rows should be counted.
Update
Hmm. I don't actually know what the Oracle version does. If it's giving you a unique number per row (across the entire table), then I'm not sure there's a way to do that in SQL Server. SQL Server's ROW_NUMBER() only works for the rows returned in the current query.
If you have an id column, you can do this:
select a.*,
(select count(*) from mytable b where b.id <= a.id) as rownum
from mytable a
order by id;
Of course, this only works where you're able to order rownums in the same (or opposite) order as the order of the ids.
If you're selecting a proper subset of rows, of course you need to apply the same predicate to the whole select and to the subquery:
select a.*,
(select count(*) from table b where b.id <= a.id and b.foo = 'X') as rownum
from table a where a.foo = 'X'
order by id;
Obviously, this is not particularly efficient.
Based on my understanding, you'd need to use ranking functions and/or the TOP clause. The SQL Server features are specific, the Oracle one combines the 2 concepts.
The ranking function is simple: here is why you'd use TOP.
Note: you can't WHERE on ROWNUMBER directly...
'Orable:
select
column_1, column_2
from
table_1, table_2
where
field_3 = 'some value'
and rownum < 5
--MSSQL:
select top 4
column_1, column_2
from
table_1, table_2
where
field_3 = 'some value'

Resources