SQL UNION GROUP BY Pagination - sql

I'm trying to compose a UNION query between 2 tables and paginate the results. The problem is the INNER JOINS results in duplicate parent rows in the results so the pagination is incorrect.
I've created a dummy db to illustrate what I'm trying to do.
[Here's the schema diagram]
This is the SQL I have so far:
SELECT * FROM
(
SELECT 'Delivery' as [table_name]
,[Delivery].Id
,[Delivery].StreetAddress
,[Delivery].City
,[Delivery].[State]
,[Delivery].Zip
,[Delivery].Longitude
,[Delivery].Latitude
,[Delivery].OrderId
,[Order].[Date] AS 'Date'
,[Order].Total AS 'Total'
,[OrderProduct].[Name]
FROM [Delivery]
INNER JOIN [Order] ON [Delivery].OrderId = [Order].Id
INNER JOIN [OrderProduct] ON [Order].Id = [OrderProduct].OrderId
UNION
SELECT 'Collection' as [table_name]
,[Collection].Id
,[Collection].StreetAddress
,[Collection].City
,[Collection].[State]
,[Collection].Zip
,[Collection].Longitude
,[Collection].Latitude
,[Collection].OrderId
,[Order].[Date] AS 'Date'
,[Order].Total AS 'Total'
,[OrderProduct].[Name]
FROM [Collection]
INNER JOIN [Order] ON [Collection].OrderId = [Order].Id
INNER JOIN [OrderProduct] ON [Order].Id = [OrderProduct].OrderId
) AS Result
ORDER BY Result.Id
OFFSET 0 ROWS
FETCH NEXT 1000 ROWS ONLY
GO
There are multiple OrderProducts per Order so the results contain duplicate Result.Id. I've tried to use GROUP BY on the Result.Id but I get this error 'Column 'Result.table_name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.' How do I achieve correct pagination in this scenario?
Furthermore I need to consume the results in EntityFramework Core 2.2. Unfortunately I can't upgrade to a later version.
Many thanks

Related

SQL Count uses info from join

I need to count the amount of times InternalMenuLinkItemNumber appears per sitenumber and per order mode. Then i need to show MenuItemID and i do that with a inner join using item numbers, but when i add this join it skews the QTY result. I've tried using distinct in the COUNT but then all the QTY is 1. Please assist.
Query and result where QTY result is 100% correct but no MenuItemID.
SELECT ST_Sites.BusinessUnit,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,InternalMenuLinkItemNumber,[ST_SalesMix].MenuItemID,OrderMode,SellingPrice,COUNT(ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
inner join ST_Sites on ST_Sites.SiteNumber= [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
where [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
group by InternalMenuLinkItemNumber,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,OrderMode,SellingPrice,ST_Sites.BusinessUnit,[ST_SalesMix].MenuItemID
order by InternalMenuLinkItemNumber
Result where QTY comes out as expected:
If I add the inner join to get MenuItemID:
Query:
SELECT ST_Sites.BusinessUnit,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,InternalMenuLinkItemNumber,[ST_SalesMix].MenuItemID,OrderMode,SellingPrice,COUNT(ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
inner join ST_SalesMix on [ST_AlohaSalesMixTransactions_RealTimeFeed].InternalMenuLinkItemNumber= ST_SalesMix.ItemNumber
inner join ST_Sites on ST_Sites.SiteNumber= [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
where [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
group by InternalMenuLinkItemNumber,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,OrderMode,SellingPrice,ST_Sites.BusinessUnit,[ST_SalesMix].MenuItemID
order by InternalMenuLinkItemNumber
Result where QTY is now way off:
If I use distinct:
Query:
SELECT ST_Sites.BusinessUnit,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,InternalMenuLinkItemNumber,[ST_SalesMix].MenuItemID,OrderMode,SellingPrice,COUNT(distinct ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
inner join ST_SalesMix on [ST_AlohaSalesMixTransactions_RealTimeFeed].InternalMenuLinkItemNumber= ST_SalesMix.ItemNumber
inner join ST_Sites on ST_Sites.SiteNumber= [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
where [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
group by InternalMenuLinkItemNumber,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,OrderMode,SellingPrice,ST_Sites.BusinessUnit,[ST_SalesMix].MenuItemID
order by InternalMenuLinkItemNumber
Result for QTY is now all 1:
If I understand correctly, you want something like
SELECT SiteNumber, OrderMode, count([DISTINCT?] InternalMenuLinkItemNumber)
...
GROUP BY SiteNumber, OrderMode
You want to count the InternalMenuLinkItemNumber, so InternalMenuLinkItemNumber must not occur in the GROUP BY clause.
EDIT:
When using GROUP BY, the SELECT list may only contain columns also mentioned in the GROUP BY clause, or aggregate functions (on arbitrary columns).
Try this:
SELECT a.InternalMenuLinkItemNumber, a.SiteNumber, a.OrderMode, a.SellingPrice, a.BusinessUnit, a.MenuItemID, a.QTY, CASE WHEN MAX(b.MenuItemID) = MIN(b.MenuItemID) THEN MAX(b.MenuItemID) ELSE -1 END AS MenuItemID
FROM
(SELECT ST_Sites.BusinessUnit, [ST_SalesMixTransactions_RealTimeFeed].SiteNumber, InternalMenuLinkItemNumber, [ST_SalesMix].MenuItemID, OrderMode, SellingPrice, COUNT(ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
INNER JOIN ST_Sites on ST_Sites.SiteNumber = [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
WHERE [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
GROUP BY InternalMenuLinkItemNumber, [ST_SalesMixTransactions_RealTimeFeed].SiteNumber, OrderMode, SellingPrice, ST_Sites.BusinessUnit, [ST_SalesMix].MenuItemID
) a
INNER JOIN ST_SalesMix b ON a.InternalMenuLinkItemNumber = b.ItemNumber
GROUP BY a.InternalMenuLinkItemNumber, a.SiteNumber, a.OrderMode, a.SellingPrice, a.BusinessUnit, a.MenuItemID, a.QTY
ORDER BY a.InternalMenuLinkItemNumber
It works on the theory that your first query gives good counts, so keep that as it is (it's now the inner query) and then do the problematic join outside of it. Obviously there are many rows from ST_SalesMix for each properly counted row in the first query, so I'm grouping on the original group list but that means that you might get multiple MenuItemIDs. I'm checking for that in the CASE statement by testing the MAX and MIN MenuItemIDs - if they are the same return MAX(MenuItemID) otherwise I'm returning -1 as an error flag to indicate that there were multiple MenuItemIDs associated with this group. It might not be the most efficient method but I didn't have much to go on.
I hope this helps.
all is sorted now. Thanks to everyone.
#jwolf your suggested query was the answer.

Ms access get row number from a subquery not table

I have the following Ms Access query that retrieves data successfully:
SELECT stockInventory.purchaseId, stockInventory.itemId, item.itemName, stockInventory.unitId, unit.unitDesc, stockInventory.quantity, stockInventory.costPrice
FROM unit INNER JOIN (item INNER JOIN stockInventory ON item.itemId = stockInventory.itemId) ON unit.unitId = stockInventory.unitId
WHERE (((stockInventory.purchaseId)=1))
Now I want to retrieve these data with row number!
I tried the following:
SELECT A.*, ( SELECT COUNT(*) FROM A WHERE A.itemId>=itemId ) as rowNo
FROM
(
SELECT stockInventory.purchaseId, stockInventory.itemId, item.itemName, stockInventory.unitId, unit.unitDesc, stockInventory.quantity, stockInventory.costPrice
FROM unit INNER JOIN (item INNER JOIN stockInventory ON item.itemId = stockInventory.itemId) ON unit.unitId = stockInventory.unitId
WHERE (((stockInventory.purchaseId)=1))
) AS A;
But it says: The Microsoft access database engine cannot find the input table or query 'A' as the following picture:
How can I solve this problem?
The additional SELECT part
( SELECT COUNT(*) FROM A WHERE A.itemId>=itemId ) as rowNo
is a separate query that doesn't know about A.
I think you must save your original query (= the subquery) as new named query, then you can reference it in both SELECT parts.
SELECT A.*,
( SELECT COUNT(*) FROM mySubquery AS B WHERE B.itemId>=A.itemId ) as rowNo
FROM mySubquery AS A
Now it also gets clearer that you need two instances of the subquery (A and B).
I hope you don't have too many records, because performance will probably be bad. But that wasn't the focus here...
Consider directly adding rowNo subquery in original query:
SELECT (SELECT Count(*) FROM stockInventory AS sub
WHERE sub.itemId <= stockInventory.itemId) AS rowNo,
stockInventory.purchaseId, stockInventory.itemId, item.itemName,
stockInventory.unitId, unit.unitDesc, stockInventory.quantity,
stockInventory.costPrice
FROM unit
INNER JOIN (item
INNER JOIN stockInventory
ON item.itemId = stockInventory.itemId)
ON unit.unitId = stockInventory.unitId
WHERE (((stockInventory.purchaseId)=1))

SQL: SELECT DISTINCT not returning distinct values

The code below is supposed to return unique records in the lp_num field from the subquery to then be used in the outer query, but I am still getting multiples of the lp_num field. A ReferenceNumber can have multiple ApptDate records, but each lp_num can only have 1 rf_num. That's why I tried to retrieve unique lp_num records all the way down in the subquery, but it doesn't work. I am using Report Builder 3.0.
Current Output
Screenshot
The desired output would be to have only unique records in the lp_num field. This is because each value in the lp_num field is a pallet, one single pallet. the info to the right is when it arrived (ApptDate) and what the reference number is for the delivery (ref_num). Therefore, it makes no sense for a pallet to have multiple receipt dates...it can only arrive once...
SELECT DISTINCT
dbo.ISW_LPTrans.item,
dbo.ISW_LPTrans.lot,
dbo.ISW_LPTrans.trans_type,
dbo.ISW_LPTrans.lp_num,
dbo.ISW_LPTrans.ref_num,
(MIN(CONVERT(VARCHAR(10),dbo.CW_CheckInOut.ApptDate,101))) as appt_date_only,
dbo.CW_CheckInOut.ApptTime,
dbo.item.description,
dbo.item.u_m,
dbo.ISW_LPTrans.qty,
(CASE
WHEN dbo.ISW_LPTrans.trans_type = 'F'
THEN 'Produced internally'
ELSE
(CASE
WHEN dbo.ISW_LPTrans.trans_type = 'R'
THEN 'Received from outside'
END)
END
) as original_source
FROM
dbo.ISW_LPTrans
INNER JOIN dbo.CW_Dock_Schedule ON LTRIM(RTRIM(dbo.ISW_LPTrans.ref_num)) = dbo.CW_Dock_Schedule.ReferenceNumber
INNER JOIN dbo.CW_CheckInOut ON dbo.CW_CheckInOut.TruckID = dbo.CW_Dock_Schedule.TruckID
INNER JOIN dbo.item ON dbo.item.item = dbo.ISW_LPTrans.item
WHERE
(dbo.ISW_LPTrans.trans_type = 'R') AND
--CONVERT(VARCHAR(10),dbo.CW_CheckInOut.ApptDate,101) <= CONVERT(VARCHAR(10),dbo.ISW_LPTrans.trans_date,101) AND
dbo.ISW_LPTrans.lp_num IN
(SELECT DISTINCT
dbo.ISW_LPTrans.lp_num
FROM
dbo.ISW_LPTrans
INNER JOIN dbo.item ON dbo.ISW_LPTrans.item = dbo.item.item
INNER JOIN dbo.job ON dbo.ISW_LPTrans.ref_num = dbo.job.job AND dbo.ISW_LPTrans.ref_line_suf = dbo.job.suffix
WHERE
(dbo.ISW_LPTrans.trans_type = 'W' OR dbo.ISW_LPTrans.trans_type = 'I') AND
dbo.ISW_LPTrans.ref_num IN
(SELECT
dbo.ISW_LPTrans.ref_num
FROM
dbo.ISW_LPTrans
--INNER JOIN dbo.ISW_LPTrans on dbo.ISW_LPTrans.
WHERE
dbo.ISW_LPTrans.item LIKE #item AND
dbo.ISW_LPTrans.lot LIKE #lot AND
dbo.ISW_LPTrans.trans_type = 'F'
GROUP BY
dbo.ISW_LPTrans.ref_num
) AND
dbo.ISW_LPTrans.ref_line_suf IN
(SELECT
dbo.ISW_LPTrans.ref_line_suf
FROM
dbo.ISW_LPTrans
--INNER JOIN dbo.ISW_LPTrans on dbo.ISW_LPTrans.
WHERE
dbo.ISW_LPTrans.item LIKE #item AND
dbo.ISW_LPTrans.lot LIKE #lot AND
dbo.ISW_LPTrans.trans_type = 'F'
GROUP BY
dbo.ISW_LPTrans.ref_line_suf
)
GROUP BY
dbo.ISW_LPTrans.lp_num
HAVING
SUM(dbo.ISW_LPTrans.qty) < 0
)
GROUP BY
dbo.ISW_LPTrans.item,
dbo.ISW_LPTrans.lot,
dbo.ISW_LPTrans.trans_type,
dbo.ISW_LPTrans.lp_num,
dbo.ISW_LPTrans.ref_num,
dbo.CW_CheckInOut.ApptDate,
dbo.CW_CheckInOut.ApptTime,
dbo.item.description,
dbo.item.u_m,
dbo.ISW_LPTrans.qty
ORDER BY
dbo.ISW_LPTrans.lp_num
In a nutshell - the way you use DISTINCT is logically wrong from SQL perspective.
Your DISTINCT is in an IN subquery in the WHERE clause - and at that point of code it has absolutely no effect (except from the performance penalty). Think on it - if the outer query returns non-unique values of dbo.ISW_LPTrans.lp_num (which obvioulsy happens) those values can still be within the distinct values of the IN subquery - the IN does not enforce a 1-to-1 match, it only enforces the fact that the outer query values are within the inner values, but they can match multiple times. So it is definitely not DISTINCT's fault.
I would go through the following check steps:
See if there is insufficient JOIN ON condition(s) in the outer FROM section that leads to data multiplication (e.g. if a table has primary-to-foreign key relation on several columns, but you join on one of them only etc.).
Check which of the sources contains non-distinct records in the outer FROM section - then either cleanse your source, or adjust the JOIN condition and / or the WHERE clause so that you only pick distinct & correct records. In fact you might need to SELECT DISTINCT in the FROM sections - there it would make much more sense.

SQL Join only if all records have a match

I have 3 tables:
CP_carthead (idOrder)
CP_cartrows (idOrder, idCartRow)
CP_shipping (idCartRow, idShipping, dateShipped)
There can be multiple idCartRows per idOrder.
I want to get all orders where all its idCartRows exist in CP_shipping. This seems like it should be simple, but I haven't found much on the web.
Here's my query now:
SELECT
s.idOrder
, s.LatestDateShipped
FROM
CP_carthead o
LEFT OUTER JOIN (
SELECT
MAX(s.dateShipped) [LatestDateShipped]
, r.idOrder
FROM
CP_shipping s
LEFT OUTER JOIN CP_cartrows r ON s.idCartRow = r.idCartRow
GROUP BY
r.idOrder
) s ON o.idOrder = s.idOrder
Your query is returning rows from "s" and not the orders. Based on your question, I came up with this query:
select o.*
from CP_Carthead o
where o.orderId in (select cr.idOrder
from cp_cartrows cr left outer join
cp_shipping s
on cr.idCartRow = s.IdCartrow
group by cr.idOrder
having count(s.idCartRow) = COUNT(*)
)
The subquery in the in statement is getting orders all of whose cartrows are in shipping.

What could create a syntax error if you take a SQL query and perform an UNION with itself?

I have this strange error in SQL Server 2005 where I take a working query, add the UNION keyword below it and then copy the query again. In my opinion, this should always be working, but it is not. I get the message 'Incorrect syntax near the keyword 'union'.
What could create this problem ?
To be more specific, here is the complete query :
select distinct deliveries.id, orders.id, 20 + sum(orders.mass1) as allowed_duration
from features_resources
inner join features on features.id = featureid
inner join orders on orders.id = features_resources.resourceid
inner join orderinformations on orders.id = orderinformations.orderid
inner join deliveries on orderinformations.deliveryid = deliveries.id
where features.name = 'O_FRAIS'
and (deliveries.ID IN
(SELECT ID
FROM dbo.DeliveriesInExportedSchedule))
group by deliveries.id, features.name ,orders.id order by deliveries.id
union
select distinct deliveries.id, orders.id, 20 + sum(orders.mass1) as allowed_duration
from features_resources
inner join features on features.id = featureid
inner join orders on orders.id = features_resources.resourceid
inner join orderinformations on orders.id = orderinformations.orderid
inner join deliveries on orderinformations.deliveryid = deliveries.id
where features.name = 'O_FRAIS'
and (deliveries.ID IN
(SELECT ID
FROM dbo.DeliveriesInExportedSchedule))
group by deliveries.id, features.name ,orders.id order by deliveries.id
I have tried to reproduce the error on a smaller query, by starting from a simple query and adding features one by one (inner join, nested queryes, group by, sum,....) but failed to reproduce the error again.
Any idea ?
It is actually the order by deliveries.id in the top half that causes the problem.
The order by needs to apply to the whole query.
Example Syntax
SELECT v1.number
FROM master.dbo.spt_values v1
WHERE v1.number > 2000
UNION
SELECT v2.number
FROM master.dbo.spt_values v2
WHERE v2.number < 10
ORDER BY v1.number
Try putting the individual SELECTs in parentheses:
(SELECT ... )
UNION
(SELECT ... )
The way you have it now, the second WHERE and GROUP BY clauses are ambiguous - should that apply to the SELECT, or to the UNION? I don't have any way to tell, and neither has your DB server.