I have 2 tables:
People:
ID | Name
----------
1 | John
2 | David
3 | Jennifer
another which is has a simple FK to the first
Note:
ID | People_ID | Note
----------------------
1 | 1 | A note
2 | 1 | Another note
3 | 3 | Jen's note
I want to get the note associated with the max(ID) from Note for each person, or a null if no notes, so the desired result is:
People_ID | Name | Note
----------------------------
1 |John | Another Note
2 |David | NULL
3 |Jennifer| Jen's Note
I can perform a join, but can't include David because the max criteria doesn't bring back the null column. Any help please?
That's a left join - and I would recommend pre-aggregating the notes in a subquery:
select p.*, n.*
from people p
left join (
select people_id, max(id) max_note_id
from note
group by people_id
) n on n.people_id = p.id
There are situations where a lateral join would be more efficient:
select p.*, n.*
from people p
outer apply (
select top(1) id max_note_id
from note n
where n.people_id = p.id
order by id desc
) n
The nice thing about the lateral join is that you can easily bring more columns from the top matching record in the note table if you want to (like the text of the note, or else).
You can use below query:
Demo
SELECT A.NAME, A.ID, MAX(B.ID) FROM PEOPLE A LEFT OUTER JOIN NOTE B
ON (A.ID = B.PEOPLE_ID) GROUP BY A.NAME, A.ID;
Related
I am trying to find a solution for the following issue that I have in sql-server:
I have one table t1 of which I want to use each date for each agency and loop it through the query to find out the avg_rate. Here is my table t1:
Table T1:
+--------+-------------+
| agency | end_date |
+--------+-------------+
| 1 | 2017-10-01 |
| 2 | 2018-01-01 |
| 3 | 2018-05-01 |
| 4 | 2012-01-01 |
| 5 | 2018-04-01 |
| 6 | 2017-12-01l |
+--------+-------------+
I literally want to use all values in the column end_date and plug it into the query here (I marked it with ** **):
with averages as (
select a.id as agency
,c.rate
, avg(c.rate) over (partition by a.id order by a.id ) as avg_cost
from table_a as a
join rates c on a.rate_id = c.id
and c.end_date = **here I use all values from t1.end_date**
and c.Start_date = **here I use all values from above minus half a year** = dateadd(month,-6,end_date)
group by a.id
,c.rate
)
select distinct agency, avg_cost from averages
order by 1
The reason why I need two dynamic dates is that the avg_rates vary if you change the timeframe between these dates.
My problem and my question is now:
How can you take the end_date from table t1 plug it into the query where c.end_date is and loop if through all values in t1.end_date?
I appreciate your help!
Do you really need a windowed average? Try this out.
;with timeRanges AS
(
SELECT
T.end_date,
start_date = dateadd(month,-6, T.end_date)
FROM
T1 AS T
)
select
a.id as agency,
c.rate,
T.end_date,
T.start_date,
avg_cost = avg(c.rate)
from
table_a as a
join rates c on a.rate_id = c.id
join timeRanges AS T ON A.DateColumn BETWEEN T.start_date AND T.end_date
group by
a.id ,
c.rate,
T.end_date,
T.start_date
You need a date column to join your data against T1 (I called it DateColumn in this example), otherwise all time ranges would return the same averages.
I can think of several ways to do this - Cursor, StoredProcedure, Joins ...
Given the simplicity of your query, a cartesian product (Cross Join) of Table T1 against the averages CTE should do the magic.
I have a table that lists all of the classes all of our students are taking. Along with the Students ID, there are 4 columns in the table that contain the IDs of our teachers assigned to a students class, the column names showing what position this teacher has for that student.
An example of the [Courses] table structure is:
|StudentsID|CourseChair|Member1|Member2|Member3|
------------------------------------------------
| 1234 | 12 | 44 | 38 | 99 |
| 2345 | 44 | NULL | NULL | NULL |
| 4566 | 38 | 88 | 72 | 31 |
| 4368 | 11 | 93 | 44 | NULL |
| 9812 | 12 | 38 | 99 | 44 |
We have tens of thousands of students with hundreds of teachers, but I want to see a list of Teacher IDs only if that ID is present in all 4 columns, regardless of the row/record. So in the above table, I would want to return only Teacher ID 44 since that ID is a CourseChair, and a Member1, Member2, and a Member3; what student or what class is unimportant in this situation.
I've tried this with CTE, multiple nested SELECT in the SELECT and/or in the WHERE, as well as 4 self-joins, none of which are very efficient with as many records I'm dealing with. Can someone help me find the best way to write this query? I'm using SQL Server 2008 R2.
You can also use UNPIVOT
SELECT StaffMember
FROM StudentClass
UNPIVOT (StaffMember FOR Position IN ([CourseChair], [Member1], [Member2], [Member3])) U
GROUP BY StaffMember
HAVING COUNT(DISTINCT Position) = 4
You can try using intersect:
select CourseChair from #yourstudents where CourseChair is not null
intersect
select member1 from #yourstudents where Member1 is not null
intersect
select member2 from #yourstudents where member2 is not null
intersect
select member3 from #yourstudents where member3 is not null
This should be pretty straightforward. Just JOIN (INNER) on each column.
SELECT DISTINCT C0.CourseChair
FROM Courses C0
INNER JOIN Courses C1
ON C0.CourseChair = C1.Member1
INNER JOIN Courses C2
ON C1.Member1 = C2.Member2
INNER JOIN Coureses C3
ON C2.Member2 = C3.Member3
Here's another way:
SELECT DISTINCT CourseChair
FROM Courses
WHERE (SELECT COUNT(*)
FROM Courses C1
WHERE c1.Member1 = Courses.CourseChair) > 0
AND (SELECT COUNT(*)
FROM Courses C2
WHERE c2.Member2 = Courses.CourseChair) > 0
AND (SELECT COUNT(*)
FROM Courses C3
WHERE c3.Member2 = Courses.CourseChair) > 0
If, however, the issue is efficiency, you probably need to look at indexing. If you have Database Engine Tuning Adviser, that can help.
You would probably need an index on each column. If you don't, then you're doing table searches for each of them, and that will, in fact, take forever.
** UPDATE **
Here's another strategy that may improve the efficiency/speed:
SELECT ID, COUNT(*)
FROM (
SELECT DISTINCT (CourseChair) AS ID FROM Courses
UNION ALL
SELECT DISTINCT (Member1) AS ID FROM Courses
UNION ALL
SELECT DISTINCT (Member2) AS ID FROM Courses
UNION ALL
SELECT DISTINCT (Member3) AS ID FROM Courses
) T1
GROUP BY ID
HAVING COUNT(*) = 4
** And one final thought***
SELECT DISTINCT (CourseChair)
FROM Courses
WHERE CourseChair IN
(
SELECT DISTINCT (Member1)
FROM Courses
WHERE Member1 IN
(
SELECT DISTINCT (Member2)
FROM Courses
WHERE Member2 IN
(
SELECT DISTINCT (Member3)
FROM Courses
)
)
)
Can be done using EXISTS as well. Demo - http://rextester.com/SRSIH80664
SELECT T1.CourseChair
FROM Table1 as T1
WHERE EXISTS (SELECT 1 FROM Table1 as T2 WHERE T1.CourseChair = T2.Member1 AND T2.Member1 IS NOT NULL)
AND EXISTS (SELECT 1 FROM Table1 as T3 WHERE T1.CourseChair = T3.Member2 AND T3.Member2 IS NOT NULL)
AND EXISTS (SELECT 1 FROM Table1 as T4 WHERE T1.CourseChair = T4.Member3 AND T4.Member3 IS NOT NULL);
What I need to do is the following:
I have in my database a table like this:
idx | name | age
------ ---------- -------
1 | John | 18
2 | Marry | 19
3 | Eric | 17
Then I get a secondTable:
name | age
------ -----
Moses | 29
John | 18
Eric | 20
I would like to run an except query like:
select *
from firstTable
where (name, age) not in (select * from secondTable)
and an intersect query like this:
select *
from firstTable
where (name, age) in (select * from secondTable)
So the result for the first query will be:
2 | Marry | 19
---- -------- ----
3 | Eric | 17
and the result for the second query will be:
1 | John | 18
I've also found a solution that recommends on the following:
select *
from firstTable
where EXISTS (select 1
from secondTable
where firstTable.name = secondTable.name
and firstTable.age = secondTable.age))
but then if I have on both tables "john - null" it will treat them as unknown (neither equal nor un-equal). I know the reason for that, but I do need them to be equal.
The reason I need to do this is in order to preserve the current index values to the query's result.
You just need to include handling the NULL values into your query logic. It would be like so:
SELECT *
FROM firstTable
WHERE EXISTS (SELECT TOP(1) 1
FROM secondTable
WHERE firstTable.name = secondTable.name
AND (
firstTable.age = secondTable.age
OR
(firstTable.age IS NULL AND secondTable.age IS NULL)
)
);
Should work like a charm. =)
Try this:
select distinct a.idx,a.name,a.age,b.name,b.age from first_table as a
inner join
second_table as b
on a.name = b.name and a.age = b.age
This one display only the record with same value both first_table and second_table
And this query Display not in second_table and union both table if has:
select distinct a.idx,b.name,b.age from first_table as a
inner join
second_table as b
on a.name = b.name and a.age = b.age
union all
select a.idx,a.name,a.age
from first_table as a where a.name not in(select name from second_table)
I have two tables.
MainTable:
MainID | LastValue | LastReadingDate
1 | 234 | 01.01.2012
2 | 534 | 03.02.2012
Readings:
MainID | ValueRead | ReadingDate
1 | 123 | 03.02.2012
1 | 488 | 04.03.2012
2 | 324 | 03.02.2012
2 | 683 | 05.04.2012
I want to get
SELECT MainTable.MainID, MainTable.LastValue, MainTable.LastReadingDate, (SELECT ValueRead, MAX(ReadingDate)
FROM Readings
WHERE Readings.MainID=MainTable.MainID ORDER BY ValueRead)
In other words, I want to get the current LastValue and LastReadingDate from MainTable along side the ValueRead with the most recent ReadingDate from Readings.
Here is a query you could use. It'll show all MainTable entries, including those that doesn't have a "Reading" entry yet. Change the LEFT JOIN to an INNER JOIN if you don't want it like that.
WITH LastReads AS (
SELECT ROW_NUMBER() OVER (PARTITION BY MainID ORDER BY ReadingDate DESC) AS ReadingNumber,
MainID,
ValueRead,
ReadingDate
FROM Readings
)
SELECT M.MainID, M.LastValue, M.LastReadingDate, R.ValueRead, R.ReadingDate
FROM MainTable M
LEFT OUTER JOIN LastReads R
ON M.MainID = R.MainID
AND R.ReadingNumber = 1 -- Last reading, use 2 or 3 to get the 2nd newest, 3rd newest, etc.
SQLFiddle-link: http://sqlfiddle.com/#!3/16c68/3
Another link with N number of readings per mainid: http://sqlfiddle.com/#!3/16c68/4
Not tried this myself, but here goes. Please try
select max(r.readingdate), max(t.lastvalue), max(t.lastreadingdate)
from readings r inner join
( select MainID, LastValue, LastReadingDate
from MainTable m
where LastReadingDate =
(select max(minner.LastReadingDate)
from MainTable minner
where minner.MainID = m.MainID
)
) t
on (r.mainid = t.mainid)
try this:
select M.LastValue, M.LastReadingDate,
(select top 1 ValueRead from Readings where MainID=M.MainID order by ReadingDate desc)
from MainTable M
I got two problems, the first problem is my two COUNTS that I start with. GroupID is a string that keep products together (Name_Year together), same product but different size.
If I have three reviews in tblReview and they all have the same GroupID I want to return 3. My problem is that if I have three Products with different ProductID but same GroupID and I add three Review to that GroupID I got 9 returns (3*3). If I only have one Product With the same GroupID and three Reviews it works (1*3=3 returns)
The Second problem is that if I have the ORDER BY CASE Price I have to add GROUP BY Price as well and then I don't get the DISTINCT effect that I want. And that is to just show products that have unique GroupID.
Here's the query, hope somebody can help me with this.
ALTER PROCEDURE GetFilterdProducts
#CategoryID INT, #ColumnName varchar(100)
AS
SELECT COUNT(tblReview.GroupID) AS ReviewCount,
COUNT(tblComment.GroupID) AS CommentCount,
Product.ProductID,
Product.Name,
Product.Year,
Product.Price,
Product.BrandID,
Product.GroupID,
AVG(tblReview.Grade) AS Grade
FROM Product LEFT JOIN
tblComment ON Product.GroupID = tblComment.GroupID LEFT JOIN
tblReview ON Product.GroupID = tblReview.GroupID
WHERE (Product.CategoryID = #CategoryID)
GROUP BY Product.ProductID, Product.BrandID, Product.GroupID, Product.Name, Product.Year, Product.Price
HAVING COUNT(distinct Product.GroupID) = 1
ORDER BY
CASE
WHEN #ColumnName='Name' THEN Name
WHEN #ColumnName='Year' THEN Year
WHEN #ColumnName='Price' THEN Price
END
My tabels:
Product:
ProductID, Name, Year, Price, BrandID, GroupID
tblReview:
ReviewID, Description, Grade, ProductID, GroupID
tblComment:
CommentID, Description, ProductID, GroupID
I think that my problem is that if I have three GroupID with the same name, ex Nike_2010 in Product and I have three Reviews in tblReview that counts the first row in Products that contain Nike_2010 counts how many reviews in tblReview with the same GroupID, Nike_2010 and then the second row in Product that contains Nike_2010 and then do the same count again and again, that results to 9 rows. How do I avoid that?
For starters, because you're joining on multiple tables, you're going to end up with the cross product of all of them as a result. Your counts will then return the total count of rows containing data in that column. Consider the following example:
- PRODUCTS - -- COMMENTS -- --- REVIEWS ---
Key | Name Key | Comment Key | Review
1 | A 1 | Foo 1 | Great
2 | B 1 | Bar 1 | Wonderful
The query
SELECT PRODUCTS.Key, PRODUCTS.Name, COMMENTS.Comment, REVIEWS.Review
FROM PRODUCTS
LEFT OUTER JOIN COMMENTS ON PRODUCTS.KEY = COMMENTS.KEY
LEFT OUTER JOIN REVIEWS ON PRODUCTS.KEY = REVIEWS.KEY
will result in the following data:
Key | Name | Comment | Review
1 | A | Foo | Great
1 | A | Foo | Wonderful
1 | A | Bar | Great
1 | A | Bar | Wonderful
2 | B | NULL | NULL
Thus, counting in this format
SELECT PRODUCTS.Key, PRODUCTS.Name, COUNT(COMMENTS.Comment), COUNT(REVIEWS.Review)
FROM PRODUCTS
LEFT OUTER JOIN COMMENTS ON PRODUCTS.KEY = COMMENTS.KEY
LEFT OUTER JOIN REVIEWS ON PRODUCTS.KEY = REVIEWS.KEY
GROUP BY PRODUCTS.Key, PRODUCTS.Name
will give you
Key | Name | Count1 | Count2
1 | A | 4 | 4
2 | B | 0 | 0
because it's counting each row in the table produced by the join!
Instead, you want to count each table separately in a subquery before joining it back like the following:
SELECT PRODUCTS.Key, PRODUCTS.Name, ISNULL(CommentCount.NumComments, 0),
ISNULL(ReviewCount.NumReviews, 0)
FROM PRODUCTS
LEFT OUTER JOIN (SELECT Key, COUNT(*) as NumComments
FROM COMMENTS
GROUP BY Key) CommentCount on PRODUCTS.Key = CommentCount.Key
LEFT OUTER JOIN (SELECT Key, COUNT(*) as NumReviews
FROM REVIEWS
GROUP BY Key) ReviewCount on PRODUCTS.Key = ReviewCount.Key
which will produce the following
Key | Name | NumComments | NumReviews
1 | A | 2 | 2
2 | B | 0 | 0
As for the "DISTINCT effect" you refer to, I'm not exactly sure I follow. Could you elaborate a bit?
About second problem - cannot you group by same CASE statement? You shouldn't have Price field in results list then though.