I have created a query but it is not working - sql

WITH rows AS
(
SELECT
*, ROW_NUMBER() OVER (ORDER BY dtDeliveryDate) AS rn
FROM
TCheckRecipient
)
SELECT
AVG(avarage)
FROM
(SELECT
TCheck.iCheckId,
AVG(DATEDIFF(minute, mc.dtDeliveryDate, mp.dtDeliveryDate)) AS average
FROM
rows mc
LEFT JOIN
TCheck ON TCheck.iCheckId = mc.iCheckId
JOIN
rows mp ON mc.rn = mp.rn - 1
GROUP BY
TCheck.iCheckId)

You have Typo error in AVG(avarage) , it should be average.
And also, remember to assign alias on your subquery.
WITH rows AS
(
SELECT
*, ROW_NUMBER() OVER (ORDER BY dtDeliveryDate) AS rn
FROM
TCheckRecipient
)
SELECT
AVG(avarage)
FROM
(SELECT
TCheck.iCheckId,
AVG(DATEDIFF(minute, mc.dtDeliveryDate, mp.dtDeliveryDate)) AS average
FROM
rows mc
LEFT JOIN
TCheck ON TCheck.iCheckId = mc.iCheckId
JOIN
rows mp ON mc.rn = mp.rn - 1
GROUP BY
TCheck.iCheckId) as A

Related

Left join tables in sqlite ordered by desc limit

I want to left join two tables in sqlite. Short summary:
I have a table named "_Menu", one of the fields in this table is named "menu_id" with unique numbers.
Another table is called "_Approvals". This table has a history of which items has been "approved" or "unapproved". This table also has a field named "menu_id".
I want to get the lowest row (if there is any) from "_Approvals" for a given menu_id and join the two tables.
What I have so far is:
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m
LEFT JOIN (
SELECT * FROM _Approvals ORDER BY _Approvals.approval_id DESC LIMIT 1) as a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
My problem is only the absolute last row in "_Approvals" gets joined. I.e I only know the status of the last item approved/unapproved.
Any help is greatly appreciated!
You want the lowest row (if there is any) from "_Approvals" for a given menu_id and not the the lowest row from "_Approvals" which is what your code does.
One way to do what you need is by using NOT EXISTS in the subquery that you join:
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m
LEFT JOIN (
SELECT * FROM _Approvals t
WHERE NOT EXISTS (
SELECT 1 FROM _Approvals
WHERE menu_id = t.menu_id AND approval_id > t.approval_id
)
) AS a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
Another way with a CTE:
WITH cte AS (
SELECT t.* FROM _Approvals t
INNER JOIN (
SELECT menu_id, MAX(approval_id)
FROM _Approvals
GROUP BY menu_id
) g
ON g.menu_id = t.menu_id
)
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m LEFT JOIN cte AS a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
Or with window function ROW_NUMBER() if your version of SQLite is 3.25.0+:
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY menu_id ORDER BY approval_id DESC) rn
FROM _Approvals
)
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m LEFT JOIN (
SELECT * FROM cte
WHERE rn = 1
) AS a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
I would recommend writing this using row_number():
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url,
a.status, a.auth
FROM _Menu m LEFT JOIN
(SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY a.menu_id ORDER BY a.approval_id DESC) as seqnum
FROM _Approvals a
) a
ON a.menu_id = m.menu_id AND a.seqnum = 1
ORDER BY m.menu_id;
No aggregation should be needed for the query, assuming that menu_id is the primary key in _Menu.
If you are using an old version of SQLite that doesn't support window functions, then there are several options. Probably the simplest is:
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url,
a.status, a.auth
FROM _Menu m LEFT JOIN
_Approvals a
ON a.menu_id = m.menu_id LEFT JOIN
(SELECT a.menu_id, MAX(a.approval_id) as max_approval_id
FROM _Approvals a
GROUP BY a.menu_id
) aa
ON aa.menu_id = a.menu_id AND
aa.max_approval_id = a.approval_id
ORDER BY m.menu_id;

calculating the first three HIGHEST GPA of each school

I have calculated the HIGHEST GPA scored by student from each school. But how can I get the list of top 3 GPA, I mean RANK 1, 2, and 3 scored by students from each school ?
SELECT A.*
FROM (
SELECT B.SCHOOL_NAME
,C.STUDENT_NAME
,A.SYMBOL_NO
,A.AVG_GPA
,MAX(A.AVG_GPA) OVER (PARTITION BY B.SCHOOL_NAME) AS MAX_GPA
FROM TBL_STUDENT_MARKS A
INNER JOIN TBL_SCHOOL B ON A.SCHOOL_ID = B.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO C ON A.SYMBOL_NO = C.SYMBOL_NO
AND B.SCHOOL_ID = C.SCHOOL_ID
) A
WHERE A.AVG_GPA = MAX_GPA
ORDER BY A.AVG_GPA DESC;
You can use either RANK() or DENSE_RANK() or ROW_NUMBER() to achieve the result.
However, RANK() and DENSE_RANK() are preferable for this scenario.
RANK():
SELECT A.*
FROM (
SELECT B.SCHOOL_NAME
,C.STUDENT_NAME
,A.SYMBOL_NO
,A.AVG_GPA
,RANK() OVER (PARTITION BY B.SCHOOL_NAME ORDER BY A.AVG_GPA DESC) AS SCHOOL_RANK
FROM TBL_STUDENT_MARKS A
INNER JOIN TBL_SCHOOL B ON A.SCHOOL_ID = B.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO C ON A.SYMBOL_NO = C.SYMBOL_NO AND B.SCHOOL_ID = C.SCHOOL_ID
) A
WHERE SCHOOL_RANK <= 3
ORDER BY SCHOOL_NAME, SCHOOL_RANK
DENSE_RANK():
SELECT A.*
FROM (
SELECT B.SCHOOL_NAME
,C.STUDENT_NAME
,A.SYMBOL_NO
,A.AVG_GPA
,DENSE_RANK() OVER (PARTITION BY B.SCHOOL_NAME ORDER BY A.AVG_GPA DESC) AS SCHOOL_DENSE_RANK
FROM TBL_STUDENT_MARKS A
INNER JOIN TBL_SCHOOL B ON A.SCHOOL_ID = B.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO C ON A.SYMBOL_NO = C.SYMBOL_NO AND B.SCHOOL_ID = C.SCHOOL_ID
) A
WHERE SCHOOL_DENSE_RANK<= 3
ORDER BY SCHOOL_NAME, SCHOOL_DENSE_RANK
ROW_NUMBER():
SELECT A.*
FROM (
SELECT B.SCHOOL_NAME
,C.STUDENT_NAME
,A.SYMBOL_NO
,A.AVG_GPA
,ROW_NUMBER() OVER (PARTITION BY B.SCHOOL_NAME ORDER BY A.AVG_GPA DESC) AS SCHOOL_ROW_NUMBER
FROM TBL_STUDENT_MARKS A
INNER JOIN TBL_SCHOOL B ON A.SCHOOL_ID = B.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO C ON A.SYMBOL_NO = C.SYMBOL_NO AND B.SCHOOL_ID = C.SCHOOL_ID
) A
WHERE SCHOOL_ROW_NUMBER <= 3
ORDER BY SCHOOL_NAME, SCHOOL_ROW_NUMBER
Please refer here for the difference between Rank, DenseRank and RowNumber
You can use DENSE_RANK() and PARTITION BY in combination:
DENSE_RANK() OVER (
PARTITION BY <expr1>[{,<expr2>...}]
ORDER BY <expr1> [ASC|DESC], [{,<expr2>...}]
)
In this case something like
DENSE_RANK() OVER (PARTITION BY B.SCHOOL_NAME ORDER BY AVG_GPA desc) AS SCHOOL_RANK
and then in the outer query you add
WHERE SCHOOL_RANK in (1, 2, 3)
Also
Give Common Table Expressions a look! I find it often nicer than nested SELECT.
WITH ranked as (
SELECT
SCHOOL_ID
, SYMBOL_NO
, AVG_GPA
, DENSE_RANK() OVER (PARTITION BY SCHOOL_ID ORDER BY AVG_GPA DESC) AS SCHOOL_RANK
FROM TBL_STUDENT_MARKS
)
SELECT
SC.SCHOOL_NAME
, INFO.STUDENT_NAME
, ST.SYMBOL_NO
, ST.AVG_GPA
, ST.SCHOOL_RANK
FROM ranked ST
INNER JOIN TBL_SCHOOL SC ON R.SCHOOL_ID = SC.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO INFO ON ST.SYMBOL_NO = INFO.SYMBOL_NO AND SC.SCHOOL_ID = INFO.SCHOOL_ID
WHERE SCHOOL_RANK <= 3
ORDER BY SCHOOL_NAME, SCHOOL_RANK;
You can use ROW_NUMBER() OVER (PARTITION BY B.SCHOOL_NAME ORDER BY A.AVG_GPA DESC)
SELECT A.*
FROM (
SELECT B.SCHOOL_NAME
,C.STUDENT_NAME
,A.SYMBOL_NO
,A.AVG_GPA
,ROW_NUMBER() OVER (PARTITION BY B.SCHOOL_NAME ORDER BY A.AVG_GPA DESC)
AS HIGHEST_GPA
FROM TBL_STUDENT_MARKS A
INNER JOIN TBL_SCHOOL B ON A.SCHOOL_ID = B.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO C ON A.SYMBOL_NO = C.SYMBOL_NO
AND B.SCHOOL_ID = C.SCHOOL_ID
) A
WHERE HIGHEST_GPA <= 3
ORDER BY A.AVG_GPA DESC;
Use Dense_rank window function:
select *
from(SELECT A.*,DENSE_RANK()over(partition by school_name order by MAX_GPA
desc)as rankno
FROM (
SELECT B.SCHOOL_NAME
,C.STUDENT_NAME
,A.SYMBOL_NO
,A.AVG_GPA
,MAX(A.AVG_GPA) OVER (PARTITION BY B.SCHOOL_NAME) AS MAX_GPA
FROM TBL_STUDENT_MARKS A
INNER JOIN TBL_SCHOOL B ON A.SCHOOL_ID = B.SCHOOL_ID
INNER JOIN TBL_STUDENT_INFO C ON A.SYMBOL_NO = C.SYMBOL_NO
AND B.SCHOOL_ID = C.SCHOOL_ID
) A
WHERE A.AVG_GPA = MAX_GPA )B
WHERE rankno IN(1,2,3);

Get the second last record in a date column within a inner join

I need to pull the second last record in a date column called OrderDate. However, I need to bring only one date (I am making the search into a table with all the purchases orders, dates and costs, in which a have to bring only the second last and its cost). The way its query is written today (and working) is pulling me the the newest date.
select distinct
a.PurchaseNum, a.ItemID, a.SupplierNum, a.Location, a.OrderDate, a.Cost
from
PurchaseOrder a
inner join
(select
l.SupplierNum, l.ItemID, l.Location, maxdate = max(l.OrderDate)
from
PurchaseOrder l
where
l.Cost <> 0
group by
l.SupplierNum, l.itemid, l.Location) l on a.SupplierNum = l.SupplierNumand a.itemid = l.itemid
and l.Location = a.Location
and a.OrderDate = l.maxdate
I have tried to use lag(), offset (but with limitations once is within a join, forcing me to use the order by and include the dateOrder column which is not what I want because we need only one date)
A bit of context: I have a report in which I need to show the last and second last cost of a purchase order for each supplier. Bring the last cost of an order is easy, the problem is go back to the second last... and it is where I am stuck right now.
Any thought?
If I'm understanding you correctly, here's one option using row_number to return the 2 highest orderdate records:
select *
from (
select *,
row_number() over (partition by SupplierNum, ItemID, Location
order by OrderDate desc) rn
from PurchaseOrder
where cost <> 0
) t
where rn <= 2
Inner query does order by desc and outside query does order by asc.
select distinct top 1 a.*
from PurchaseOrder a
inner join
(
select Top 2 l.*
from PurchaseOrder l
where
l.Cost <> 0
group by l.SupplierNum, l.itemid, l.Location order by orderdate desc) l
on a.SupplierNum= l.SupplierNumand a.itemid = l.itemid and l.Location=a.Location and a.OrderDate = l.Orderdate
order by a.orderdate
or
SELECT TOP 1 * FROM (SELECT * FROM PurchaseOrder a
EXCEPT SELECT TOP (SELECT (COUNT(*)-2) FROM PurchaseOrder a where
l.Cost <> 0
group by l.SupplierNum, l.itemid, l.Location) * FROM PurchaseOrder) A
or
SELECT *
FROM PurchaseOrder a
WHERE OrderDate = ( SELECT MAX(OrderDate)
FROM PurchaseOrder
WHERE Orderdate < ( SELECT MAX(OrderDate)
FROM PurchaseOrder l where
l.Cost <> 0
group by l.SupplierNum, l.itemid, l.Location
)
) ;
or
SELECT TOP (1) *
FROM PurchaseOrder
WHERE OrderDate < ( SELECT MAX(OrderDate)
FROM PurchaseOrder where ....
)
ORDER BY OrderDate DESC ;

SQL WITH not working

I have created a query but it is not working. Can anyone explain this to me? Thanks!
WITH rows AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY dtDeliveryDate) AS rn
FROM TCheckRecipient
)
select avg(avarage) from(
SELECT TCheck.iCheckId, AVG(DATEDIFF(minute, mc.dtDeliveryDate, mp.dtDeliveryDate)) as avarage
FROM rows mc
left join TCheck on TCheck.iCheckId = mc.iCheckId
JOIN rows mp
ON mc.rn = mp.rn - 1
group by TCheck.iCheckId
)
When I was doing
WITH rows AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY dtDeliveryDate) AS rn
FROM TCheckRecipient
)
SELECT TCheck.iCheckId, AVG(DATEDIFF(minute, mc.dtDeliveryDate, mp.dtDeliveryDate)) as avarage
FROM rows mc
left join TCheck on TCheck.iCheckId = mc.iCheckId
JOIN rows mp
ON mc.rn = mp.rn - 1
group by TCheck.iCheckId
It works well.
But when I try to make the average of those returns, and I can not,
I'd be happy if someone would answer me how to do it thanks
I'll put an answer in so that my comment has more clarity. You might just need to alias the inner query.
WITH rows AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY dtDeliveryDate) AS rn
FROM TCheckRecipient
)
select avg(avarage) from(
SELECT TCheck.iCheckId, AVG(DATEDIFF(minute, mc.dtDeliveryDate, mp.dtDeliveryDate)) as avarage
FROM rows mc
left join TCheck on TCheck.iCheckId = mc.iCheckId
JOIN rows mp
ON mc.rn = mp.rn - 1
group by TCheck.iCheckId
) A

SQL: improving join efficiency

If I turn this sub-query which selects sales persons and their highest price paid for any item they sell:
select *,
(select top 1 highestProductPrice
from orders o
where o.salespersonid = s.id
order by highestProductPrice desc ) as highestProductPrice
from salespersons s
in to this join in order to improve efficiency:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders ) o on s.id = o.salespersonid
It still touches every order record (it enumerates the entire table before filtering by salespersonid it seems.) However you cannot do this:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders
where orders.salepersonid = s.id) o on s.id = o.salespersonid
The where clause in the join causes a `multi-part identifier "s.id" could not be bound.
Is there any way to join the top 1 out of each order group with a join but without touching each record in orders?
Try
SELECT
S.*,
T.HighestProductPrice
FROM
SalesPersons S
CROSS APPLY
(
SELECT TOP 1 O.HighestProductPrice
FROM Orders O
WHERE O.SalesPersonid = S.Id
ORDER BY O.SalesPersonid, O.HighestProductPrice DESC
) T
would
select s.*, max(highestProductPrice)
from salespersons s
join orders o on o.salespersonid = s.id
group by s.*
or
select s.*, highestProductPrice
from salespersons s join (select salepersonid,
max(highestProductPrice) as highestProductPrice
from orders o) as o on o.salespersonid = s.id
work?