How to make SQL Subqueries? - sql

Make a list of employees whose remuneration is available from the average remuneration in their branch and display the average remuneration in the employee's branch. The list should be ordered by branch identifier.
My code looks like this but I can't add it to display the average salary in the employee ward.
SELECT nazwisko, wynagrodzenie, oddzial
FROM pracownicy p
WHERE wynagrodzenie> (SELECT AVG(wynagrodzenie)
FROM pracownicy
WHERE p.oddzial=oddzial);
However, this code displays the average wage but for all employees:
SELECT nazwisko,
(SELECT AVG(wynagrodzenie) FROM pracownicy
WHERE oddzial = p.oddzial) as srednia
FROM pracownicy p LEFT JOIN oddzialy z ON p. oddzial = z.oddzial;

If you want to both filter with and display the average remuneration, then a join is a better option than a subquery.
But here, I would simply recommend window functions; you can do a window average of wynagrodzenie across all rows having the same oddzial, and then use that information for filtering and display as follows:
select nazwisko, wynagrodzenie, oddzial, avg_wynagrodzenie_oddzial
from (
select p.*, avg(wynagrodzenie) over(partition by oddzial) avg_wynagrodzenie_oddzial
from pracownicy p
) t
where wynagrodzenie > avg_wynagrodzenie

You can create a simple join query for that case, so that could reduce implicit (sub query) or explicit (to be displayed) result size that come from persistent table scanning.
SELECT nazwisko, wynagrodzenie, p.oddzial, avg_wynagrodzenie
FROM pracownicy p
INNER JOIN (
SELECT oddzial, AVG(wynagrodzenie) as avg_wynagrodzenie
FROM pracownicy
GROUP BY oddzial
) t
ON p.oddizial = t.oddizial
AND p.wynagrodzenie > t.avg_wynagrodzenie;

Related

How to get the value of max() group when in subquery?

So i woud like to find the department name or department id(dpmid) for the group that has the max average of age among the other group and this is my query:
select
MAX(avg_age) as 'Max average age' FROM (
SELECT
AVG(userage) AS avg_age FROM user_data GROUP BY
(select dpmid from department_branch where
(select dpmbid from user_department_branch where
user_data.userid = user_department_branch.userid)=department_branch.dpmbid)
) AS query1
this code show only the max value of average age and when i try to show the name of the group it will show the wrong group name.
So, How to show the name of max group that has subquery from another table???
You may try this..
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##' ,department_name)) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I've done some change on ipothesys that the department name is placed in a "department" anagraphical table.. so, as it needed put in join a table in plus, then I changed your query, eventually if the department name is placed (but I don't thing so) in the branch_department table you can add the field and its treatment to your query
update
In adjunct to as said, if you wanto to avoid identical average cases you can furtherly make univocal the averages by appending a rownum id in this way:
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##', CONCAT( #rownum:=#rownum+1, CONCAT('##' ,department_name)))) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
,(SELECT #rownum:=0) r
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I took a shot at what I think you are looking for. The following will give you the department branch with the highest average age. I assumed the department_branch table had a department_name field. You may need an additional join to get the department.
SELECT db.department_name, udb.dpmid, AVG(userage) as `Average age`
FROM user_data as ud
JOIN user_department_branch as udb
ON udb.userid = ud.userid
JOIN department_branch as db
ON db.dpmbid = udb.dpmbid
GROUP BY udb.dpmid
ORDER BY `Average age` DESC
LIMIT 1

Returning the Min() of a Count()

I am studying for an SQL test and the previous year has the final question:
Name the student who has studied the least number of papers. How many
papers have they studied?
So far, this is the select query that I have created:
select min(Full_Name), min(Amount)
from (select st.ST_F_Name & ' ' & st.ST_L_Name as Full_Name, count(*) as Amount
from (student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID)
group by st.ST_F_Name & ' ' & st.ST_L_Name)
This works perfectly for returning the result I want but I'm not sure if this is the way I should be doing this query? I feel like calling min() on the Full_Name could potentially backfire on me under certain circumstances. Is there a better way to be doing this? (this is in MS Access for unknown reasons)
If you want only 1 of such students if there are multiple, this is probably the simplest:
select st.ST_F_Name, st.ST_L_Name, count(*) as Amount
from student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
order by Amount ASC LIMIT 1
However, if you want to find all stuch students, you follow a different approach. We use a WITH clause to simplify things, that defines a CTE (Common Table Expression) computing the number of courses per-student. And then we select students where their number equals to the minimum in that CTE:
with per_student as (
select st.ST_F_Name, st.ST_L_Name, count(*) as Amount
from student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
)
select * from per_student
where amount = (select min(amount) from per_student)
But the real trick in that question is that there might be students that didn't take ANY courses. But with approaches presented so far you'll never see them. You want something like this:
with per_student as (
select st.ST_F_Name, st.ST_L_Name, count(sc.SC_ST_ID) as Amount
from student_course as sc
right outer join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
)
select * from per_student
where amount = (select min(amount) from per_student)
You can order by count(*) to get the student with the least # of papers:
i.e.
select * from students where st_id in (
select top 1 sc_st_id
from student_course
group by sc_st_id
order by count(*)
)
if you also need the # of papers studied, then join a derived table containing the min count:
select * from students s
left join (
select top 1 sc_st_id, count(*)
from student_course
group by sc_st_id
order by count(*)
) t on t.sc_st_id = s.st_id

How to retrieve results for top items

I have ran a query to give me the total number of students within each school but now I need to know the name of those students within each school while keeping the top result by total number at the top. How can I add to this query to show me the names of the students?
Here is what I have to show me the total number of students at each school:
SELECT
dbo_Schools.Schools,
Count(dbo_tStudent.Student) AS NumberOfStudents
FROM
dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID=dbo_tSchool.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC;
Its important that I keep the schools in order from top number of students while listing the students
In this case you could use a Sub Query to achieve your resultset.
To use order by inside a subquery, you will also need a top or limit operator.
SELECT sc.schoolname
,st.columns...
FROM dbo_tStudent st
INNER JOIN (
SELECT TOP 1000 dbo_Schools.SchoolID
,min(schoolname) schoolname
,Count(dbo_tStudent.Student) AS NumberOfStudents
FROM dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID = dbo_tSchools.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC
) sc ON st.SchoolID = sc.SchoolID
Assuming that you are using SQL Server, you can use a CTE to join the first aggregate with the details like this:
;WITH cte as (
SELECT TOP 1000 dbo_Schools.SchoolID, Count(dbo_tStudent.Student) AS NumberOfStudents
FROM
dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID = dbo_tSchools.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC
)
SELECT
sc.<your school name column>,
st.<your student columns>
from
dbo_tStudent st
INNER JOIN cte ON st.SchoolID = cte.SchoolID
INNER JOIN dbo_tSchools sc on cte.SchoolID = sc.SchoolID
More generally speaking: you need a derived table (your aggregation containing the group by clause) that is joined with the select statement for the student details. In this example, the CTE basically is a SQL Server feature that facilitates the use of derived tables.

Adding rows to SQL query for each element in WHERE sequence

I have an SQL query like the following:
SELECT store_id, SUM(quantity_sold) AS count
FROM sales_table
WHERE store_id IN ('Store1', 'Store2', 'Store3')
GROUP BY store_id;
This returns a row for each store that has rows in sales_table, but does not return a row for those that do not. What I want is one row per store, with a 0 for count if it has no records.
How can I do this, assuming that I do not have access to a stores table?
with stores (store_id) as (
values ('Store1'), ('Store2'), ('Store3')
)
select st.store_id,
sum(sal.quantity_sold) as cnt
from stores st
left join sales_table sal on sal.store_id = st.store_id
group by st.store_id;
If you do have a stores table, then simply do an outer join to that one instead of "making one up" using the common table expression (with ..).
This can also be written without the CTE (common table expression):
select st.store_id,
sum(sal.quantity_sold) as cnt
from (
values ('Store1'), ('Store2'), ('Store3')
) st
left join sales_table sal on sal.store_id = st.store_id
group by st.store_id;
(But I find the CTE version easier to understand)
You can use unnest() to generate rows from array elements.
SELECT store, sum(sales_table.quantity_sold) AS count
FROM unnest(ARRAY['Store1', 'Store2', 'Store3']) AS store
LEFT JOIN sales_table ON (sales_table.store_id = store)
GROUP BY store;

How can I SELECT a result set of data from a single table with one of the columns coming from the SUM of a column in a different table?

So let's say I want 5 columns total in my result set, for example:
Name, Date, Color, Price, TotalSales
Name, Date, Color, and Price are all stored in a single table so I will just get every row from that table.
SELECT * FROM AwesomeStuff
However, TotalSales needs to be calculated from the SUM of values in another table, and the values that needed to be combined must match up with the ID of the row from the AwesomeStuff table.
SELECT SUM(SalePrice) FROM AwesomeSales
Each row in the AwesomeSales table has an ID that matches a single item in the AwesomeStuff table. So I want to add up all the SalePrice values into a single column and match it up with the correct row in the AwesomeStuff query so I get back the single result set.
How does this work? Along with the query, can you explain what SQL is doing in plain english so I can understand how to write this type of SELECT again in the future?
Thanks.
You can perform a GROUP BY operation to group the results per "AwesomeStuff" item:
SELECT
A.Name,
A.Date,
A.Color,
A.Price,
SUM(S.SalePrice) AS TotalSales
FROM
AwesomeStuff A
INNER JOIN
AwesomeSales S
ON
S.AwesomeStuffId = A.Id
GROUP BY
A.Name,
A.Date,
A.Color,
A.Price
select
t.name,
t.date,
t.color,
t.price,
a.TotalSales
from awesomestuff t
inner join
(
select id, sum(saleprice) as TotalSales
from awesomesales
group by id
)a
on t.id = a.id
What this does is calculates the SUM() based on each id. And then with an inner join it creates the relationship for the TotalSales per id to show in the result set.
The SQL to do what you want is:
select AwesomeStuff.Name,
AwesomeStuff.Date,
AwesomeStuff.Color,
AwesomeStuff.Price,
AwesomeStuff.TotalSales,
TotalSales.Price
from AwesomeStuff
join (select AwesomeSales.id,
sum(AwesomeSales.SalePrice) As Price
from AwesomeSales
group by id) As TotalSales
on TotalSales.id = AwesomeStuff.id;
I will update this post after lunch with an English explanation.
English You aggregate the sale prices, taking the sum, grouped by their ID. This is what the inline view (sub-query) does: we include the ID because we need to use it in the join. That is we join your AwesomeStuff table with the inline view of sales (aliased as TotalSales, as we refer to it later), match them by ID -- as this is the primary key -- and then select all the fields (including the calculated sum, which we have aliased as Price) together.
You could use a subquery if your Database supports it:
SELECT stuff.Name, stuff.Date, stuff.Color, stuff.Price, sales.total
FROM AwesomeStuff AS stuff,
(SELECT STUFF_ID, SUM(SalePrice) AS total
FROM AwesomeSales
GROUP BY STUFF_ID) AS sales
where stuff.ID = sales.STUFF_ID
SELECT
a.Name,
a.Date,
a.Color,
a.Price,
SUM(s.SalesPrice)
FROM AwesomeStuff as a
INNER JOIN AwesomeSales as s on a.ID = s.AwesomestuffID
GROUP BY a.Name, a.Date, a.Color, a.Price
In plain English, what you are doing is saying give me all the unique combinations of name, date, color and price from your stuff table and a sum of all the sales from the sales table. This assumes that each ID in the stuff table represents a unique combination of name, date, color and price.
Try:
SELECT A.Id, -- not required in select for query to work - included for clarity
max(A.Name),
max(A.Date),
max(A.Color),
max(A.Price),
SUM(S.SalePrice) AS TotalSales
FROM AwesomeStuff A
LEFT JOIN AwesomeSales S ON S.AwesomeStuffId = A.Id
GROUP BY A.Id
By joining AwesomeStuff to AwesomeSales on the product's unique ID, you will get a row returned for every sale of every product - since you only want one row per product, you need to group by the product's unique ID. (The SUM function should be self-explanatory.)
Unfortunately, most forms of SQL will not allow you to include unaggregated values in a grouped query, even where these are functionally dependant on one of the values that is being grouped - as in this query. This is why name, date, color and price have all been MAXed.
A left join ensures that records on the left-hand side of the join (here, AwesomeStuff) will be returned even if there are no corresponding records on the right-hand side of the join (AwesomeSales). In other words, this version of the query will include products that have had no sales.