SQLite query JOIN , CONCAT, subquery - sqlite

I have been trying to use LEFT OUTER JOIN, GROUP BY and (failing) to use the CONCAT || function to get the maximum score for the best book of the decade but have had no luck finding out when two books get the same max score in the same decade.
I need the output below:
There are 2 tables:
Table 1: bookName
Schema: uniqueBookNameId, BookName, yearPublished (from 1901 to 2022)
Table 2: bookRating
Schema: uniqueBookNameId, bookRating

Use a CTE where you join the tables and rank the books with RANK() window function.
Then filter the results to get the top books of each decade:
WITH cte AS (
SELECT r.bookRating,
n.BookName,
n.yearPublished / 10 * 10 || 's' AS Decade,
RANK() OVER (PARTITION BY n.yearPublished / 10 * 10 ORDER BY r.bookRating DESC) AS rnk
FROM bookName n INNER JOIN bookRating r
ON r.uniqueBookNameId = n.uniqueBookNameId
)
SELECT DISTINCT bookRating, BookName, Decade
FROM cte
WHERE rnk = 1
ORDER BY Decade;
or:
WITH cte AS (
SELECT r.bookRating,
n.BookName,
n.yearPublished / 10 * 10 || 's' AS Decade,
RANK() OVER (PARTITION BY n.yearPublished / 10 * 10 ORDER BY r.bookRating DESC) AS rnk
FROM bookName n INNER JOIN bookRating r
ON r.uniqueBookNameId = n.uniqueBookNameId
)
SELECT bookRating, GROUP_CONCAT(DISTINCT BookName) AS BookName, Decade
FROM cte
WHERE rnk = 1
GROUP BY Decade
ORDER BY Decade;

Related

How do I include all max values within a row?

I'm very new to learning SQL, I apologize if my question isn't completely accurate.
The question I'm trying to answer with this query is "What is the most popular music genre in each country?" I've had to use a subquery and it works, but I found that for a few countries in the table, more than one genre has the MAX value. I'm stuck with how to edit my query so that all genres with the max value show in the results. Here is my code, using DB Browser for SQLite:
SELECT BillingCountry AS Country , name AS Genre , MAX(genre_count) AS Purchases
FROM (
SELECT i.BillingCountry, g.name, COUNT(g.genreid) AS genre_count
FROM Invoice i
JOIN InvoiceLine il
ON il.InvoiceId = i.InvoiceId
JOIN TRACK t
ON il.trackid = t.TrackId
JOIN Genre g
ON t.genreid = g.GenreId
GROUP BY 1,2
) sub
GROUP BY 1
Here is an example of the result:
| Country | Genre |Purchase|
|---------|-------|--------|
|Agrentina| Punk | 9 |
|Australia| Rock | 22 |
BUT in running just the subquery to COUNT the purchases, Argentina has two Genres with 9 Purchases (the max number for that country). How do I adjust my query to include both and not just the first one in the row?
You can do it with RANK() window function:
SELECT BillingCountry, name, genre_count
FROM (
SELECT i.BillingCountry, g.name, COUNT(*) AS genre_count,
RANK() OVER (PARTITION BY i.BillingCountry ORDER BY COUNT(*) DESC) rnk
FROM Invoice i
INNER JOIN InvoiceLine il ON il.InvoiceId = i.InvoiceId
INNER JOIN TRACK t ON il.trackid = t.TrackId
INNER JOIN Genre g ON t.genreid = g.GenreId
GROUP BY i.BillingCountry, g.name
)
WHERE rnk = 1
This will return the ties in separate rows.
If you want 1 row for each country, you could also use GROUP_CONCAT():
SELECT BillingCountry, GROUP_CONCAT(name) AS name, MAX(genre_count) AS genre_count
FROM (
SELECT i.BillingCountry, g.name, COUNT(*) AS genre_count,
RANK() OVER (PARTITION BY i.BillingCountry ORDER BY COUNT(*) DESC) rnk
FROM Invoice i
INNER JOIN InvoiceLine il ON il.InvoiceId = i.InvoiceId
INNER JOIN TRACK t ON il.trackid = t.TrackId
INNER JOIN Genre g ON t.genreid = g.GenreId
GROUP BY i.BillingCountry, g.name
)
WHERE rnk = 1
GROUP BY BillingCountry

SQL - Performing additional math calculations on results obtained from aggregate functions within a query

I am fairly new to writing SQL queries. I have used a subquery so that I could obtain the results of aggregate functions applied to 2 different tables. Furthermore, I would like to obtain the ratio between the results from these 2 aggregate functions. In other words, Result of Aggregate function 1 / Result of Aggregate function 2.
INPUT:
TABLE USERS
Id
1
2
3
4
5
6
7
TABLE EVENTS
User_Id Event_Name
1 View_User_Profile
1 View_User_Activity
1 View_User_Profile
2 View_User_Activity
3 View_User_Activity
4 View_User_Profile
5 View_User_Activity
7 View_User_Activity
This is my code so far:
SELECT COUNT(*) AS Number_of_Users,
(SELECT COUNT(DISTINCT U.Id) AS Number_of_Users_Viewed_Profile
FROM dsv1069.Users U Left Join dsv1069.Events E
ON U.Id = E.user_id
WHERE E.Event_Name = 'view_user_profile') AS Number_of_Users_Viewed_Profile
FROM dsv1069.Users
RESULTS:
Number_of_Users: 7
Number_of_Users_Viewed_Profile: 2
EXPECTED OUTPUT:
Number_of_Users: 7
Number_of_Users_Viewed_Profile: 2
PERCENT OF USERS VIEWED PROFILE: 28.6%
ISSUE: What my code doesn't do so far is calculate the ratio 2/7 = 28.6%
I have done lots of searches on aggregate functions but can't find any information on how to use the results from those functions as part of the query. Thank you for any assistance!
I believe that you can obtain the column Number_of_Users_Viewed_Profile without the join:
SELECT COUNT(DISTINCT user_id)
FROM dsv1069.Events
WHERE Event_Name = 'view_user_profile'
So this is equivalent to your query:
SELECT
COUNT(*) AS Number_of_Users,
(
SELECT COUNT(DISTINCT user_id)
FROM dsv1069.Events
WHERE Event_Name = 'View_User_Profile'
) AS Number_of_Users_Viewed_Profile
FROM dsv1069.Users
You can get the ratio column with a CTE:
WITH cte AS (
SELECT
COUNT(*) AS Number_of_Users,
(
SELECT COUNT(DISTINCT user_id)
FROM Events
WHERE Event_Name = 'View_User_Profile'
) AS Number_of_Users_Viewed_Profile
FROM Users
)
SELECT *, ROUND(100.0 * Number_of_Users_Viewed_Profile / Number_of_Users, 1) AS ratio
FROM cte
See the demo.
Or without the CTE:
SELECT
t1.Number_of_Users,
t2.Number_of_Users_Viewed_Profile,
ROUND(100.0 * t2.Number_of_Users_Viewed_Profile / t1.Number_of_Users, 1) AS ratio
FROM (SELECT COUNT(*) AS Number_of_Users FROM Users) AS t1
CROSS JOIN (
SELECT COUNT(DISTINCT user_id) AS Number_of_Users_Viewed_Profile
FROM Events
WHERE Event_Name = 'View_User_Profile'
) AS t2
See the demo.
Results:
| Number_of_Users | Number_of_Users_Viewed_Profile | ratio |
| --------------- | ------------------------------ | ----- |
| 7 | 2 | 28.6 |

How to use with statement in this query?

In this query, I want to use with-statement. I have a subquery that calculates A union all B and I want to use it with with-statement. But when I use with-statement I face the error that says "table or view does not exist".
what surprises me is when I replace the first part with with-statement it works correctly. But when I replace the second part, I face this error!!
select
deposit.BRNCH_COD||'-'||deposit.DP_TYPE_COD||''||deposit.CUSTOMER_NUM||'-
'||deposit.DEPOSIT_SERIAL AS DEPOSIT_NUMBER,
deposit.IBAN AS IBAN,
deposit.CURRENCY_DESC AS DEPOSIT_CURRCOD,
deposit.BRNCH_COD AS BRNCH_COD,
MAIN_7.Still_Days
AS Still_Lenght,
to_char(MAIN_7.Startdate, 'yyyy/mm/dd' ,'nls_calendar=persian') AS
START_DATE,
MAIN_7.AMOUNT
AS TOTAL_AMOUNT,
MAIN_7.TRN_Count
AS TRN_Count
from
(
select Trans_Table.DEPOSIT_KEY AS DEPOSIT_KEY,
Trans_Table.TRN_Start_DATE AS Startdate,
MAX(Active_Time_Table.EFFECTIVE_DATE) AS Lastdate,
H.PASSIVE_DAYS AS Still_Days,
SUM(Active_Time_Table.AMOUNT) AS AMOUNT,
Count(Active_Time_Table.AMOUNT) AS TRN_Count
from
(
Select F.DEPOSIT_KEY,
SUM (F.AMOUNT) AS TRN_AMOUNT,
MIN (F.EFFECTIVE_DATE) AS TRN_Start_DATE
from
(
A
union all
B
)F
Group by (F.DEPOSIT_KEY)
Having ( SUM (F.AMOUNT) >10000000000)
)Trans_Table
inner join
H
on (Trans_Table.DEPOSIT_KEY = H.DEPOSIT_KEY and
Trans_Table.TRN_Start_DATE-1 = H.EFFECTIVE_DATE)
inner join
(
A
union all
B
)Active_Time_Table
on (Trans_Table.DEPOSIT_KEY = Active_Time_Table.DEPOSIT_KEY and
Active_Time_Table.EFFECTIVE_DATE - Trans_Table.TRN_Start_DATE< 4 and
Active_Time_Table.EFFECTIVE_DATE - Trans_Table.TRN_Start_DATE>=0)
group by ( Trans_Table.DEPOSIT_KEY ,
Trans_Table.TRN_Start_DATE,H.PASSIVE_DAYS)
Having (SUM(Active_Time_Table.AMOUNT)) > 10000000000
)MAIN_7
inner join dimamldeposit deposit
on deposit.DEPOSIT_KEY = MAIN_7.DEPOSIT_KEY
***********************************************************
with rep as
(A union all B)
select
deposit.BRNCH_COD||'-'||deposit.DP_TYPE_COD||'-
'||deposit.CUSTOMER_NUM||'-'||deposit.DEPOSIT_SERIAL AS DEPOSIT_NUMBER,
deposit.IBAN AS IBAN,
deposit.CURRENCY_DESC AS DEPOSIT_CURRCOD,
deposit.BRNCH_COD AS BRNCH_COD,
MAIN_7.Still_Days AS Still_Lenght,
to_char(MAIN_7.Startdate, 'yyyy/mm/dd' ,'nls_calendar=persian') AS START_DATE,
MAIN_7.AMOUNT AS TOTAL_AMOUNT,
MAIN_7.TRN_Count AS TRN_Count
from
(
select Trans_Table.DEPOSIT_KEY AS DEPOSIT_KEY,
Trans_Table.TRN_Start_DATE AS Startdate,
MAX(rep.EFFECTIVE_DATE) AS Lastdate,
H.PASSIVE_DAYS AS Still_Days,
SUM(rep.AMOUNT) AS AMOUNT,
Count(rep.AMOUNT) AS TRN_Count
from
(
Select rep.DEPOSIT_KEY,
SUM (rep.AMOUNT) AS TRN_AMOUNT,
MIN (rep.EFFECTIVE_DATE) AS TRN_Start_DATE
from
rep
Group by (rep.DEPOSIT_KEY)
Having ( SUM (rep.AMOUNT) >10000000000)
)Trans_Table
inner join
H
on (Trans_Table.DEPOSIT_KEY = H.DEPOSIT_KEY and Trans_Table.TRN_Start_DATE-1 = H.EFFECTIVE_DATE)
inner join
rep rep
on (Trans_Table.DEPOSIT_KEY = rep.DEPOSIT_KEY and rep.EFFECTIVE_DATE - Trans_Table.TRN_Start_DATE< 4 and rep.EFFECTIVE_DATE - Trans_Table.TRN_Start_DATE>=0)
group by ( Trans_Table.DEPOSIT_KEY , Trans_Table.TRN_Start_DATE,H.PASSIVE_DAYS)
Having (SUM(rep.AMOUNT)) > 10000000000
)MAIN_7
inner join dimamldeposit deposit
on deposit.DEPOSIT_KEY = MAIN_7.DEPOSIT_KEY
That's a lot of code, but - to make it simple, I'd suggest you use WITH factoring clause as the first command, include all tables you use into it, and then - as the final SELECT - fetch data from all those CTEs. Something like this:
with
a as (select ... from ...),
b as (select ... from ...),
f as (select ... from ...),
...
select a.col1, b.col2, f.col3
from a join b on a.id = b.id
left join f on f.id = b.id
where ...

calculate percentages with postgresql join queries

I am trying to calculate percentages by joining 3 tables data to get the percentages of positive_count, negative_count, neutral_count of each user's tweets. I have succeeded in getting positive, negative and neutral counts, but failing to get the same as percentages instead of counts. Here is the query to get counts:
SELECT
t1.u_id,count() as total_tweets_count ,
(
SELECT count() from t1,t2,t3 c
WHERE
t1.u_id='18839785' AND
t1.u_id=t2.u_id AND
t2.ts_id=t3.ts_id AND
t3.sentiment='Positive'
) as pos_count ,
(
SELECT count() from t1,t2,t3
WHERE
t1.u_id='18839785' AND
t1.u_id=t2.u_id AND
t2.ts_id=t3.ts_id AND
t3.sentiment='Negative'
) as neg_count ,
(
SELECT count() from t1,t2,t3
WHERE
t1.u_id='18839785' AND
t1.u_id=t2.u_id AND
t2.ts_id=t3.ts_id AND
t3.sentiment='Neutral'
) as neu_count
FROM t1,t2,t3
WHERE
t1.u_id='18839785' AND
t1.u_id=t2.u_id AND
t2.ts_id=t3.ts_id
GROUP BY t1.u_id;
**OUTPUT:**
u_id | total_tweets_count | pos_count | neg_count | neu_count
-----------------+--------------------+-----------+-----------+-------
18839785| 88 | 38 | 25 | 25
(1 row)
Now I want the same in percentages instead of counts. I have written the query in the following way but failed.
SELECT
total_tweets_count,pos_count,
round((pos_count * 100.0) / total_tweets_count, 2) AS pos_per,neg_count,
round((neg_count * 100.0) / total_tweets_count, 2) AS neg_per,
neu_count, round((neu_count * 100.0) / total_tweets_count, 2) AS neu_per
FROM (
SELECT
count(*) as total_tweets_count,
count(
a.u_id='18839785' AND
a.u_id=b.u_id AND
b.ts_id=c.ts_id AND
c.sentiment='Positive'
) AS pos_count,
count(
a.u_id='18839785' AND
a.u_id=b.u_id AND
b.ts_id=c.ts_id AND
c.sentiment='Negative'
) AS neg_count,
count(
a.u_id='18839785' AND
a.u_id=b.u_id AND
b.ts_id=c.ts_id AND
c.sentiment='Neutral') AS neu_count
FROM t1,t2, t3
WHERE
a.u_id='18839785' AND
a.u_id=b.u_id AND
b.ts_id=c.ts_id
GROUP BY a.u_id
) sub;
Can anyone help me out in achieving as percentages for each user data as below?
u_id | total_tweets_count | pos_count | neg_count | neu_count
------------------+--------------------+-----------+-----------+-----
18839785| 88 | 43.18 | 28.4 | 28.4
(1 row)
I am not entirely sure what you are looking for.
For starters, you can simplify your query by using conditional aggregation instead of three scalar subqueries (which btw. do not need to repeat the where condition on a.u_id)
You state you want to "count for all users", so you need to remove the WHERE clause in the main query. The simplification also gets rid of the repeated WHERE condition.
select u_id,
total_tweets_count,
pos_count,
round((pos_count * 100.0) / total_tweets_count, 2) AS pos_per,
neg_count,
round((neg_count * 100.0) / total_tweets_count, 2) AS neg_per,
neu_cont,
round((neu_count * 100.0) / total_tweets_count, 2) AS neu_per
from (
SELECT
t1.u_id,
count(*) as total_tweets_count,
count(case when t3.sentiment='Positive' then 1 end) as pos_count,
count(case when t3.sentiment='Negative' then 1 end) as neg_count,
count(case when t3.sentiment='Neutral' then 1 end) as neu_count
FROM t1
JOIN t2 ON t1.u_id=t2.u_id
JOIN t3 t2.ts_id=t3.ts_id
-- no WHERE condition on the u_id here
GROUP BY t1.u_id
) t
Note that I replaced the outdated, ancient and fragile implicit joins in the WHERE clause with "modern" explicit JOIN operators
With a more up-do-date Postgres version, the expression count(case when t3.sentiment='Positive' then 1 end) as pos_count can also be re-written to:
count(*) filter (where t3.sentiment='Positive') as pos_count
which is a bit more readable (and understandable I think).
In your query you can achieve the repetition of the global WHERE condition on the u_id by using a co-related subquery, e.g.:
(
SELECT count(*)
FROM t1 inner_t1 --<< use different aliases than in the outer query
JOIN t2 inner_t2 ON inner_t2.u_id = inner_t1.u_id
JOIN t3 inner_t3 ON inner_t3.ts_id = inner_t2.ts_id
-- referencing the outer t1 removes the need to repeat the hardcoded ID
WHERE innter_t1.u_id = t1.u_id
) as pos_count
The repetition of the table t1 isn't necessary either, so the above could be re-written to:
(
SELECT count(*)
FROM t2 inner_t2
JOIN t3 inner_t3 ON inner_t3.ts_id = inner_t2.ts_id
WHERE inner_t2.u_id = t1.u_id --<< this references the outer t1 table
) as pos_count
But the version with conditional aggregation will still be a lot faster than using three scalar sub-queries (even if you remove the unnecessary repetition of the t1 table).

Advice on SQL-Server Query

I'm looking to improve this query I wrote for a small web application in ASP.NET 4.0 using SQL-Server 2005. This application will allow the user to search by Product ID and have it return the following information:
Highest Purchase Price + Most Recent Date of purchase # this price
Lowest Purchase Price + Most Recent Date of purchase # this price
Most Recent Purchase Price + Date
Average Purchase Price (optional, i thought this might improve the usefulness of the app)
Here is the structure of the Products table (I'm only including relevant columns, this is a DB already in production and these are non-pk columns)
product_id (nvarchar(20))
price (decimal(19,2))
pDate (datetime)
Before I put down the query I have so far I just want to say that I can get this information easily through multiple queries, so if this is the best practice then disregard improving the query, but I was aiming to minimize the number of queries needed to get all needed information.
What I have so far: (Note: There are rows with price = 0 so I ignored those in the bottom select looking for the MIN price)
SELECT price, MAX(pDate)
FROM Products
WHERE product_id = #product_id AND
(price = (SELECT MAX(price)
FROM Products
WHERE product_id =#product_id) OR
price = (SELECT MIN(price)
FROM Products
WHERE product_id = #product_id AND price > 0))
GROUP BY price
Now this is returning 2 rows:
first = the lowest price + date
second row = high price + date
What I would like ideally is to have a query return 1 row with all the needed information stated above if possible, as it would simplify displaying the information in ASP for me. And like I said earlier, if multiple queries is the be approach then no need to re-write a complex query here.
Edit
Here is some sample data
Desired query results: (ignore the format as I typed this in excel)
Here is the query I will be using thanks to Ken Benson:
SELECT TOP 1 prod.product_id,
minp.price AS minprice, minp.pDate as minlastdate,
maxp.price AS maxprice, maxp.pDate as maxlastdate,
ag.price AS averageprice
FROM products AS prod
LEFT JOIN (SELECT lmd.product_id,max(lmd.pDate) as pDate,mn.price FROM products as lmd INNER JOIN
(SELECT product_id, min(price) AS price from products WHERE price > 0 group by product_id) as mn ON lmd.product_id=mn.product_id AND lmd.price=mn.price
group by lmd.product_id,mn.price ) AS minp ON minp.product_id=prod.product_id
LEFT JOIN (SELECT lxd.product_id,max(lxd.pDate) as pDate,mx.price FROM products as lxd INNER JOIN
(SELECT product_id, max(price) AS price from products group by product_id) as mx ON lxd.product_id=mx.product_id AND lxd.price=mx.price
group by lxd.product_id,mx.price ) AS maxp ON maxp.product_id=prod.product_id
LEFT JOIN (SELECT product_id,avg(price) as price FROM products WHERE price > 0 GROUP BY product_id) AS ag ON ag.product_id=prod.product_id
WHERE prod.product_id=#product_id
I think you can do a couple of joins back to the table ...
Select product_id, min.price, min.pDate, max.price, max.pDate
FROM products as p
LEFT JOIN (Select Min(price), pDate, product_id FROM products GROUP BY product_id)
as min on min.product_id=p.product_id
LEFT JOIN (Select max(price), pDate, product_id FROM products GROUP BY product_id)
as max on max.product_id=p.product_id
Where p.product_id = #product_id
This second bit of code should produce desired results....
SELECT prod.product_id,
minp.price AS minprice, minp.pDate as minlastdate,
maxp.price AS maxprice, maxp.pDate as maxlastdate,
ag.price AS averageprice
FROM products AS prod
LEFT JOIN (SELECT lmd.product_id,max(lmd.pDate) as pDate,mn.price FROM products as lmd INNER JOIN
(SELECT product_id, min(price) AS price from products group by product_id) as mn ON lmd.product_id=mn.product_id
group by lmd.product_id,mn.price ) AS minp ON minp.product_id=prod.product_id
LEFT JOIN (SELECT lxd.product_id,max(lxd.pDate) as pDate,mx.price FROM products as lxd INNER JOIN
(SELECT product_id, max(price) AS price from products group by product_id) as mx ON lxd.product_id=mx.product_id
group by lxd.product_id,mx.price ) AS maxp ON maxp.product_id=prod.product_id
LEFT JOIN (SELECT product_id,avg(price) as price FROM products GROUP BY product_id) AS ag ON ag.product_id=prod.product_id
WHERE prod.product_id=1
LIMIT 1
Yep - left out an 'and' condition:
SELECT TOP 1
prod.product_id,
minp.price AS minprice, minp.pDate as minlastdate,
maxp.price AS maxprice, maxp.pDate as maxlastdate,
ag.price AS averageprice
FROM products AS prod
LEFT JOIN (SELECT lmd.product_id,max(lmd.pDate) as pDate,mn.price FROM products as lmd INNER JOIN
(SELECT product_id, min(price) AS price from products group by product_id) as mn ON lmd.product_id=mn.product_id **AND lmd.price=mn.price**
group by lmd.product_id,mn.price ) AS minp ON minp.product_id=prod.product_id
LEFT JOIN (SELECT lxd.product_id,max(lxd.pDate) as pDate,mx.price FROM products as lxd INNER JOIN
(SELECT product_id, max(price) AS price from products group by product_id) as mx ON lxd.product_id=mx.product_id AND **lxd.price=mx.price**
group by lxd.product_id,mx.price ) AS maxp ON maxp.product_id=prod.product_id
LEFT JOIN (SELECT product_id,avg(price) as price FROM products GROUP BY product_id) AS ag ON ag.product_id=prod.product_id
WHERE prod.product_id=#product_id
I would do this with a combination of ranking functions and conditional aggregations:
select product_id,
max(case when seqnum_hi = 1 then price end) as highPrice,
max(case when seqnum_hi = 1 then pdate end) as highPrice_date
max(case when seqnum_low = 1 then price end) as lowPrice,
max(case when seqnum_low = 1 then pdate end) as lowPrice_date,
max(case when seqnum_rec = 1 then price end) as recentPrice,
max(case when seqnum_rec = 1 then pdate end) as recentPrice_date,
avg(price) as avg_price
from (select p.*,
row_number() over (partition by product_id order by price asc) as seqnum_low,
row_number() over (partition by product_id order by price desc) as seqnum_hi,
row_number() over (partition by product_id order by pdate desc) as seqnum_rec
from price
where product_id = #product_id
group by product_id
The seguence numbers identify the rows with the particular attributes you care about (high price, low price, most recent). The conditional max then just selects information from those rows.
The following should get what you want. It's pretty long, but is readable so should be easily modified by anyone who needs to:
;WITH CTE_MaxPrice AS
(
SELECT product_id, MAX(P.price) AS MaxPrice
FROM Products P
GROUP BY product_id
HAVING product_id = #product_id
),
CTE_MinPrice AS
(
SELECT product_id, MIN(P.price) AS MinPrice
FROM Products P
GROUP BY product_id
HAVING product_id = #product_id
),
CTE_MaxPriceDate AS
(
SELECT P.product_id, MAX(P.pDate) AS MaxDate
FROM Products P
INNER JOIN CTE_MaxPrice MaxP ON P.product_id = MaxP.product_id
AND P.price = MaxP.MaxPrice
GROUP BY P.product_id
),
CTE_MinPriceDate AS
(
SELECT P.product_id, MAX(P.pDate) AS MinDate
FROM Products P
INNER JOIN CTE_MinPrice MinP ON P.product_id = MinP.product_id
AND P.price = MinP.MinPrice
GROUP BY P.product_id
)
SELECT MaxP.MaxPrice, MaxPD.MaxDate,
MinP.MinPrice, MinPD.MinDate,
RP.price AS RecentPrice, MAX(RP.pDate) AS RecentDate,
AVG(AP.price) AS AveragePrice
FROM Products P
INNER JOIN CTE_MaxPrice MaxP ON P.product_id = MaxP.product_id
INNER JOIN CTE_MinPrice MinP ON P.product_id = MinP.product_id
AND MinP.MinPrice > 0
INNER JOIN CTE_MaxPriceDate MaxPD ON P.product_id = MaxPD.product_id
INNER JOIN CTE_MinPriceDate MinPD ON P.product_id = MinPD.product_id
INNER JOIN Products RP ON P.product_id = RP.product_id
INNER JOIN Products AP ON P.product_id = AP.product_id
GROUP BY MaxP.MaxPrice, MaxPD.MaxDate,
MinP.MinPrice, MinPD.MinDate, RP.price
HAVING P.product_id = #product_id
Well since there have been three attempts to answer, and none have worked quite how you want, I'll tell you how I would do it - and this assumes you can use a stored procedure and also assumes that the product table is not so huge that multiple seperate queries would be a problem:
CREATE PROCEDURE myproc AS
DECLARE #Price1 money
DECLARE #Date1 smalldatetime
DECLARE #Price2 money
DECLARE #Date2 smalldatetime
DECLARE #Price3 money
DECLARE #Date3 smalldatetime
DECLARE #Price4 money
SELECT #Price1 = MAX(Price) FROM Products
SELECT #Date1 = MAX(pDate) FROM Products WHERE Price=#Price1
SELECT #Price2 = Min(Price) FROM Products WHERE Price >0
SELECT #Date2 = MAX(pDate) FROM Products WHERE Price=#Price2
SELECT #Date3 = Max(pDate) FROM Products
SELECT #Price3 = MAX(Price) FROM Products WHERE pDate=#Date3 --max in case there are more than one purchases with the same date.
SELECT #Price4 = AVG(Price) FROM Products WHERE Price>0
SELECT #Price1 As MaxPrice,
#Date1 As MaxPriceDate,
#Price2 As LowPrice,
#Date2 As LowPriceDate,
#Price4 As AveragePrice,
#Price3 As RecentPrice,
#Price3 As RecentPriceDate
GO
Forgive any typographical errors, I didn't test this, but if you can use stored procedures, this will work.
So this is not much different than doing your multiple queries from the client, but should perform better putting them all into a single SP. You could also cut the number of queries down a bit by using some of the code from your other answers, but I have left it this way for clarity.

Resources