count rows and give values of the rows - sql

Probably really simple but I can't quite figure it out.
Here's my SQL
select ag.product_id, COUNT(reviewer) as review_number
from PRODUCT ag
left join RATINGANDREVIEW rr on rr.product_id = ag.product_id
where ag.product_id = 123
group by ag.product_id
This is giving me total number of reviews made for one product as follows:
Product_id: 123
review_number: 3
but I now want the reviewers names as well like so:
Product_id: 123
review_number: 3
reviewer: Bob Smith
Product_id: 123
review_number: 3
reviewer: Peter Jones
Product_id: 123
review_number: 3
reviewer: Jane Green
How do I do this?

you could do something like this:
select
ag.product_id,
reviewer,
(select COUNT(reviewer)
from PRODUCT ag
left join RATINGANDREVIEW rr on rr.product_id = ag.product_id
where ag.product_id = 123) as review_number
from PRODUCT ag
left join RATINGANDREVIEW rr on rr.product_id = ag.product_id
where ag.product_id = 123

Try like this
select ag.product_id, COUNT(reviewer) as review_number, rr.reviewer_name
from PRODUCT ag
left join RATINGANDREVIEW rr on rr.product_id = ag.product_id
where ag.product_id = 123
group by ag.product_id,rr.reviewer_name

Related

SQL to return one row from many

I have the following data
PersonId
City
Type
UpdateDate
123
Boston
P
01/01/2021
123
Boston
M
02/01/2021
130
Detroit
P
01/01/2021
130
Detroit
M
03/01/2021
140
Dallas
M
02/01/2021
140
Dallas
M
03/01/2021
I want a query that returns one row per PersonId. If the Type is "P" return that row otherwise return the row with the minimum UpdateDate. So the query would return:
PersonId
City
Type
UpdateDate
123
Boston
P
01/01/2021
130
Detroit
P
01/01/2021
140
Dallas
M
02/01/2021
In the past I would write a query like
select * from person, address
where person.PersonId = address.PersonId
group by PersonId
having (Type = 'P') or (UpdateDate = min(UpdateDate))
but this is not allowed anymore.
What should my SQL query be in SQL Server?
Presumably you want the most recent address per person. If so, outer apply is very well suited to this problem:
select p.* a.*
from person p outer apply
(select top (1) a.*
from address a
where a.PersonId = p.PersonId
order by (case when a.type = 'P' then 1 else 2 end),
a.updatedate desc
) a;
No aggregation is called for.

Using SQLite and Inner Joins to Find Unique Pairs and Number of Pair Matches

If we have the following two tables, "teachers" and "classes", how do we use an INNER JOIN statement to produce the final table where
"teacher1_id" and "teacher2_id" represent all the unique combinations of teachers who have taught the same class at least 2 times
"pair_count" represents the number of same classes "teacher1_id" and "teacher2_id" have taught and
"average_teacher_rating" represents the average rating of those same classes taught by "teacher1_id" and "teacher2_id"?
"teachers" table
teachers
class_id teacher_id teacher_name class_count
1111 1234 Smith 2
1111 2345 Jones 4
2222 1234 Smith 2
2222 2345 Jones 4
1111 5678 Taylor 2
3333 2345 Jones 4
4444 5678 Taylor 2
4444 2345 Jones 4
"classes" table
classes
id class_name rating
1111 Math 60
2222 Biology 70
3333 Psychology 50
4444 Physics 80
5555 Chemistry 30
6666 Economics 60
7777 Computing 70
8888 Statistics 90
And we want the final table to look like:
teacher1_id teacher2_id pair_count average_teacher_rating
1234 2345 2 65
2345 5678 2 70
I tried the following code in sqlite but it did not give the desired result (as found in the final table above).
SELECT
t.teacher1_id
, t.teacher2_id
, COUNT(t.teacher1_id = t.teacher2_id)
, (c.rating1 + c.rating2) / COUNT(t.teacher1_id = t.teacher2_id)
FROM teachers t
INNER JOIN classes c on c.id = t.class_id
WHERE COUNT(t.teacher1_id = t.teacher2_id) > 1;
Try:
select
t1.teacher_id as t1
, t2.teacher_id as t2
, count()
, avg(c.rating)
from teachers t1 inner join teachers as t2
on t1.class_id = t2.class_id and t1.teacher_id < t2.teacher_id
left join classes c on c.id = t1.class_id
group by t1, t2
having count()>1;
see sqlfiddle.
I believe the following may be along the lines of what you want :-
WITH cte1 AS
(
SELECT
t1.teacher_id AS teacher1_id,
t1.teacher_name AS t1name,
t2.teacher_id AS teacher2_id,
t2.teacher_name AS t2name,
c.class_name,
avg(c.rating) AS average_teacher_rating,
count(c.class_name) AS pair_count
FROM teachers AS t1
LEFT JOIN classes AS c ON t1.class_id = c.id
INNER JOIN teachers AS t2 ON t1.class_id = t2.class_id AND t1.teacher_id < t2.teacher_id
GROUP BY teacher1_id,teacher2_id
HAVING pair_count > 1
)
SELECT teacher1_id, teacher2_id, pair_count, average_teacher_rating FROM cte1
;
This results in :-
The above could be simplified as the subquery isn't required. However, the additional values may prove to be useful.
i.e. It could be :-
SELECT
t1.teacher_id AS teacher1_id,
t2.teacher_id AS teacher2_id,
avg(c.rating) AS average_teacher_rating,
count(c.class_name) AS pair_count
FROM teachers AS t1
JOIN teachers AS t2 ON t1.class_id = t2.class_id AND t1.teacher_id < t2.teacher_id
JOIN classes AS c ON t1.class_id = c.id
GROUP BY teacher1_id,teacher2_id
HAVING pair_count > 1
;

How to join 2 tables with select and count in single query

I need to join 2 tables (Person and PersonLine). The result should contain id and name column from Person table and count of personlineid column from PersonLine Table for each id. But sql query returns count of all personlineid. Can anyone help to form the sql.
Person:
ID NAME AGE
100 John 25
101 James 30
102 Janet 35
PersonLine:
ID NAME PERSONLINEID
100 John 1
100 John 2
100 John 3
101 James 1
101 James 2
102 Janet 1
SQL:
SELECT P.ID, CNT.COUNT_PERSONLINE, P.NAME
FROM PERSON P
LEFT JOIN PERSONLINE PL
ON P.ID = PL.ID,
(SELECT count(PL.PERSONLINEID) cnt FROM PERSON P LEFT JOIN PERSONLINE PL ON P.ID = PL.ID WHERE
P.ID = PL.ID) cnt
JOIN Table (Expected):
ID COUNT_PERSONLINE NAME
100 3 John
101 2 James
102 1 Janet
JOIN Table (Actual):
ID COUNT_PERSONLINE NAME
100 6 John
101 6 James
102 6 Janet
With your sample data, you don't even need the Person table -- because you seem to have redundant table in the two tables. You should probably fix this, but:
select pl.id, pl.name, count(*)
from personline pl
group by pl.id, pl.name;
Your count is just counting all the rows from the join of the tables -- that would be all the rows. A simple aggregation should suffice, even if you decide that the join is still necessary.
EDIT:
You have several choices with lots of columns in persons. One method is to put them in the group by:
select pl.id, pl.name, p.col1, p.col2, count(*)
from persons p join
personline pl
on p.id = pl.id
group by pl.id, pl.name, p.col1, p.col2
Another method is to do the aggregation before the join:
select p.*, pl.cnt
from person p join
(select pl.id, pl.name, count(*) as cnt
from personline pl
group by pl.id, pl.name
) pl
on p.id = pl.id;
Or, a correlated subquery:
select p.*, (select count(*) from personline pl where p.id = pl.id)
from person;

Create view by joining three tables in SQL

I have three tables STUDENTS, SUBJECTS, RANK ,with data as -
1) STUDENTS [NAME(Primary)]
NAME
--------
Alex
Greg
2) SUBJECTS [ID(Primary)]:
ID
--------
100
101
102
3) RANK [SEQ(Primary), NAME, ID, RANK]
SEQ NAME ID RANK
------ ------- ------ ------
1 Alex 100 A
2 Greg 100 A
3 Greg 101 B
I want to create a view that should display data as
NAME ID RANK
------- ------ ------
Alex 100 A
Alex 101 Z
Alex 102 Z
Greg 100 A
Greg 101 B
Greg 102 Z
So, for every student and for every subject, the View should display the RANK if present in RANK table, else replace the NULL with 'Z'.
I'm a newbie to SQL. So any help in forming the query would be deeply appreciated!
cross join student and subject then left outer join the result with rank to get ranks for all (student, subject) combination. selecting column with NVL OR COALESCE will replace NULL with 'z'.
SELECT st.name,
su.id,
NVL(ra.rank,'Z') Rank, --COALESCE(ra.rank,'Z') Rank
FROM student st
CROSS JOIN subject su
LEFT OUTER JOIN rank ra
ON ra.name = st.name
AND ra.id = su.id
ORDER BY st.name,su.id
Note : ORDER BY can be removed from above query if you don't need.
fiddle
SELECT r.NAME, r.ID, NVL(r.RANK, 'Z')
FROM RANK r, studendts st, SUBJECTS su
WHERE st. NAME = r. NAME
AND su.ID = r.ID
ORDER BY 1,2,3

SQL Server : take 1 to many record set and make 1 record per id

I need some help. I need to take the data from these 3 tables and create an output that looks like below. The plan_name_x and pending_tallyx columns are derived to make one line per claim id. Each claim id can be associated to up to 3 plans and I want to show each plan and tally amounts in one record. What is the best way to do this?
Thanks for any ideas. :)
Output result set needed:
claim_id ac_name plan_name_1 pending_tally1 plan_name_2 Pending_tally2 plan_name_3 pending_tally3
-------- ------- ----------- -------------- ----------- -------------- ----------- --------------
1234 abc cooks delux_prime 22 prime_express 23 standard_prime 2
2341 zzz bakers delpux_prime 22 standard_prime 2 NULL NULL
3412 azb pasta's prime_express 23 NULL NULL NULL NULL
SQL Server 2005 table to use for the above result set:
company_claims
claim_id ac_name
1234 abc cooks
2341 zzz bakers
3412 azb pasta's
claim_plans
claim_id plan_id plan_name
1234 101 delux_prime
1234 102 Prime_express
1234 103 standard_prime
2341 101 delux_prime
2341 103 standard_prime
3412 102 Prime_express
Pending_amounts
claim_id plan_id Pending_tally
1234 101 22
1234 102 23
1234 103 2
2341 101 22
2341 103 2
3412 102 23
If you know that 3 is always the max amount of plans then some left joins will work fine:
select c.claim_id, c.ac_name,
cp1.plan_name as plan_name_1, pa1.pending_tally as pending_tally1,
cp2.plan_name as plan_name_2, pa2.pending_tally as pending_tally2,
cp3.plan_name as plan_name_3, pa3.pending_tally as pending_tally3,
from company_claims c
left join claim_plans cp1 on c.claim_id = cp1.claim_id and cp1.planid = 101
left join claim_plans cp2 on c.claim_id = cp2.claim_id and cp2.planid = 102
left join claim_plans cp3 on c.claim_id = cp3.claim_id and cp3.planid = 103
left join pending_amounts pa1 on cp1.claim_id = pa1.claimid and cp1.planid = pa1.plainid
left join pending_amounts pa2 on cp2.claim_id = pa2.claimid and cp2.planid = pa2.plainid
left join pending_amounts pa3 on cp3.claim_id = pa3.claimid and cp3.planid = pa3.plainid
I would first join all your data so that you get the relevant columns: claim_id, ac_name, plan_name, pending tally.
Then I would add transform this to get plan name and plan tally on different rows, with a label tying them together.
Then it should be easy to pivot.
I would tie these together with common table expressions.
Here's the query:
with X as (
select cc.*, cp.plan_name, pa.pending_tally,
rank() over (partition by cc.claim_id order by plan_name) as r
from company_claims cc
join claim_plans cp on cp.claim_id = cc.claim_id
join pending_amounts pa on pa.claim_id = cp.claim_id
and pa.plan_id = cp.plan_id
), P as (
select
X.claim_id,
x.ac_name,
x.plan_name as value,
'plan_name_' + cast(r as varchar(max)) as label
from x
union all
select
X.claim_id,
x.ac_name,
cast(x.pending_tally as varchar(max)) as value,
'pending_tally' + cast(r as varchar(max)) as label
from x
)
select claim_id, ac_name, [plan_name_1], [pending_tally1],[plan_name_2], [pending_tally2],[plan_name_3], [pending_tally3]
from (select * from P) p
pivot (
max(value)
for label in ([plan_name_1], [pending_tally1],[plan_name_2], [pending_tally2],[plan_name_3], [pending_tally3])
) as pvt
order by pvt.claim_id, ac_name
Here's a fiddle showing it in action: http://sqlfiddle.com/#!3/68f62/10