SQL to select items not already in other table - sql-server

I currently have sql table (with its columns) as follows:
ORDERS (orderid, orderno, orderdate, customer)
ORDERDETAILS (id, orderid, itemcode, qty, price)
DELIVERY (deliveryid, orderid, deliverydate)
DELIVERYDETAILS (id, deliveryid, itemcode, qty)
ORDERS and ORDERDETAILS contains order of items. DELIVERY and DELIVERYDETAILS contains items that have been delivered, and it is 1 to Many to order (ie. 1 order can have many deliveries).
Example.
ORDERS: 1, '001', '2016-07-01', 'CUST001'
ORDERDETAILS:
1, 1, 'ITEM001', 1, 2.50
2, 1, 'ITEM002', 3, 7.50
3, 1, 'ITEM003', 6, 8.50
DELIVERY: 1, 1, '2016-07-02'
DELIVERY DETAILS:
1, 1, 'ITEM001', 1
2, 1, 'ITEM002', 1
DELIVERY: 2, 1, '2016-07-03'
DELIVERY DETAILS:
3, 2, 'ITEM002', 1
4, 2, 'ITEM003', 2
How do I need to generate a list of items that have not been delivered of an order using sql as follows.
UNDELIVERED ITEM as of '2016-07-04': itemcode, qty
'ITEM001', 0
'ITEM002', 1
'ITEM003', 4
Appreciate any advice.
Updates: Change order to orders. Added examples and results

I am not sure that my understanding is correct. Because i am confused with order delivery relationship (ie. 1 to Many ).
But according to my understanding you need list of all order no that doesn't have an delivery Id
SELECT o.orderid,o.orderno,o.customer
FROM ORDER o
LEFT JOIN DELIVERY d
ON o.orderno = d.orderno
WHERE d.orderno IS NULL
So this query will return all the orderno that is not in delivery table.
Are you expecting anything like this ?
UPDATE
Query is may not optimized, But hope this will solve your issue.
SELECT dd.itemcode, SUM(dd.qty) as delivrdQty,
(SELECT od.qty FROM ORDERDETAILS od WHERE od.itemcode = dd.itemcode) as originalQty,
(SELECT od.qty FROM ORDERDETAILS od WHERE od.itemcode = dd.itemcode) - SUM(dd.qty) as remainQty
FROM DELIVERYDETAILS dd
INNER JOIN DELIVERY d ON dd.deliveryid = d.deliveryid
GROUP BY dd.itemcode ,dd.qty

Related

Join most recent Date AND other fields that belong to that date from another table

I want to JOIN a different table that has DATE values in it, and I only want the most recent Date to be added and te most recent Value that corresponds with that Date.
I have a table in which certain RENTALOBJECTS in the RENTALOBJECTTABLE have a N:1 relationship with the OBJECTTABLE
RENTALOBJECTTABLE:
RENTALOBJECTID, OBJECTID
1, 1
2, 1
3, 2
4, 3
5, 4
6, 4
OBJECTTABLE:
OBJECTID
1
2
3
4
Every OBJECTID can (and usually has, more than 1) VALUE
VALUETABLE:
OBJECTID, VALUE, VALIDFROM, VALIDTO, CODE
1, 2000, 1-1-1950, 31-12-1980, A
1, 3000, 1-1-1981, 31-12-2010, A
1, 4000, 1-1-2013, NULL, A
2, 1000, 1-1-1970, NULL, A
3, 2000, 1-1-2010, NULL, A
4, 2000, 1-1-2000, 31-12-2009, A
4, 3100, 1-1-2010, NULL, B
4, 3000, 1-1-2010, NULL, A
And combined I want for every RentalObject the most recent VALUE to be shown. End result expected:
RENTALOBJECTTABLE_WITHVALUE:
RENTALOBJECTID, OBJECTID, VALUE, VALIDFROM, VALIDTO, CODE
1, 1, 4000, 1-1-2013, NULL, A
2, 1, 4000, 1-1-2013, NULL, A
3, 2, 1000, 1-1-1970, NULL, A
4, 3, 2000, 1-1-2010, NULL, A
5, 4, 3000, 1-1-2010, NULL, A
6, 4, 3000, 1-1-2010, NULL, A
I so far managed to get the Most Recent Date joined to the table with the code below. However, as soon as I want to INCLUDE VALUETABLE.VALUE then the rowcount goes from 5000 (what the original dataset has) to 48000.
SELECT
RENTALOBJECTTABLE.RENTALOBJECTID
FROM RENTALOBJECTTABLE
LEFT JOIN OBJECTTABLE
ON OBJECTTABLE.OBJECTID = RENTALOBJECTTABLE.OBJECTID
LEFT JOIN (
SELECT
OBJECTID,
CODE,
VALUE, --without this one it gives the same rows as the original table
MAX(VALIDFROM) VALIDFROM
FROM VALUETABLE
LEFT JOIN PMETYPE
ON VALUETABLE.CODE = PMETYPE.RECID
AND PMETYPE.REGISTERTYPENO = 6
WHERE PMETYPE.[NAME] = 'WOZ'
GROUP BY OBJECTID, CODE, VALUE
) VALUETABLE ON OBJECTTABLE.OBJECTID = VALUETABLE.OBJECTID
When I include MAX(VALUE) next to the MAX(Date) it obviously has the original 5000 dataset rows again, but now it only selects the most recent date + highest value, which is not always correct.
Anyone any clue about how to solve this issue?
I think I miss something very obvious.
This gets you close
WITH cte AS
(
SELECT
o.OBJECTID,
v.VALUE,
v.VALIDFROM,
v.VALIDTO,
v.CODE,
ROW_NUMBER() OVER (PARTITION BY o.OBJECTID ORDER BY v.VALIDFROM DESC ) rn
FROM dbo.OBJECTTABLE o
INNER JOIN dbo.VALUETABLE v ON v.OBJECTID = o.OBJECTID
)
SELECT ro.RENTALOBJECTID,
ro.OBJECTID,
cte.OBJECTID,
cte.VALUE,
cte.VALIDFROM,
cte.VALIDTO,
cte.CODE
FROM dbo.RENTALOBJECTTABLE ro
INNER JOIN cte ON cte.OBJECTID = ro.OBJECTID
AND rn=1;
However, this might pull out the 3100 value for object 4 - there is nothing to separate the two values with the same validfrom. If you have (or can add) an identity column to the value table, you can use this in the order by on the partitioning to select the row you want.
Your Sample Data
select * into #RENTALOBJECTTABLE from (
SELECT 1 AS RENTALOBJECTID, 1 OBJECTID
UNION ALL SELECT 2,1
UNION ALL SELECT 3,2
UNION ALL SELECT 4,3
UNION ALL SELECT 5,4
UNION ALL SELECT 6,4) A
SELECT * INTO #OBJECTTABLE FROM(
SELECT
1 OBJECTID
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4)AS B
SELECT * INTO #VALUETABLE FROM (
SELECT 1OBJECTID,2000 VALUE,'1-1-1950'VALIDFROM,'31-12-1980' VALIDTO, 'A' CODE
UNION ALL SELECT 1,3000,'1-1-1981','31-12-2010', 'A'
UNION ALL SELECT 1,4000,'1-1-2013',NULL, 'A'
UNION ALL SELECT 2,1000,'1-1-1970',NULL, 'A'
UNION ALL SELECT 3,2000,'1-1-2010',NULL, 'A'
UNION ALL SELECT 4,2000,'1-1-2000','31-12-2009', 'A'
UNION ALL SELECT 4,3100,'1-1-2010',NULL, 'B'
UNION ALL SELECT 4,3000,'1-1-2010',NULL, 'A'
) AS C
Query:
;WITH CTE AS (
SELECT * , ROW_NUMBER()OVER(PARTITION BY OBJECTID ORDER BY OBJECTID DESC)RN FROM #VALUETABLE
)
SELECT RO.RENTALOBJECTID,RO.OBJECTID,C.VALUE,C.VALIDFROM,C.VALIDTO,C.CODE
FROM CTE C
CROSS APPLY (SELECT OBJECTID,MAX(RN)RN FROM CTE C1 WHERE C.OBJECTID=C1.OBJECTID GROUP BY OBJECTID )AS B
INNER JOIN #RENTALOBJECTTABLE RO ON RO.OBJECTID=C.OBJECTID
WHERE C.OBJECTID=B.OBJECTID AND C.RN=B.RN
OutPut Data:
RENTALOBJECTID, OBJECTID, VALUE, VALIDFROM, VALIDTO, CODE
1, 1, 4000, 1-1-2013, NULL, A
2, 1, 4000, 1-1-2013, NULL, A
3, 2, 1000, 1-1-1970, NULL, A
4, 3, 2000, 1-1-2010, NULL, A
5, 4, 3000, 1-1-2010, NULL, A
6, 4, 3000, 1-1-2010, NULL, A

Left outer join returning extra records

I have 2 tables namely "Item" and "Messages".
Item table has the columns like Id, Amount, etc.
Messages table has the columns like ItemId, Count, Comment, etc.
Here the common link between these 2 tables is the "Id" from Item and "ItemId" from Messages.
The "Count" column in the Messages table is just the count of comments per ItemId. i.e. When user updates the comment for any record, an entry gets created in the Messages table and Count for that particular ItemId shows as 1. If user updates one more comment to same record, the Count shows 2 and so on. If user does not update comment for a certain record, the entry does not get created in Messages table at all (NULL).
I want to capture all the records from the Item table irrespective of whether user has updated comment or not. If there are 0 comments, the query should return NULL in the Comments column for that record. But, If the user has updated the comment, it should pick up the comment having the highest "Count". E.g. if one record has 8 comments, the query should return only the record where Messages.Count=8 and not all 8 records. If only one comment, then that comment should be seen.
I have written LEFT OUTER JOIN but not able to get through as it shows all 8 records. In the results, I find 7 records with NULL as the count and the 8th record showing count as 8 but I need only this 8th record and not the other 7.
Any help would be highly appreciated. Below is my query:
Select
Id,
Amount,
Messages.Comment As Comments
From Item
Left Outer Join Messages ON Messages.ItemId=Item.Id
Left Outer Join (Select ItemId, MAX(Id) as max_id from Messages Group by ItemId) T ON Messages.ItemId=T.ItemId and Messages.Id=T.max_id
Where amount > 100
I've hooked up an example using temp tables which I think covers what you're looking for. Just remove the temp table stuff and replace with your actual tables and it should work.
CREATE TABLE #Item ( ID int PRIMARY KEY,
Amount numeric(9,2))
CREATE TABLE #Messages ( ItemId int REFERENCES #Item(ID),
[Count] smallint,
Comment nvarchar(max))
INSERT INTO #Item (ID, Amount)
SELECT 1, 100
UNION
SELECT 2, 120
UNION
SELECT 3, 140
UNION
SELECT 4, 50
INSERT INTO #Messages ( ItemID,
[Count],
Comment)
SELECT 1, 1, 'Comment 1 - 1'
UNION
SELECT 1, 2, 'Comment 1 - 2'
UNION
SELECT 2, 1, 'Comment 2 - 1'
UNION
SELECT 2, 1, 'Comment 3 - 1'
UNION
SELECT 2, 2, 'Comment 3 - 2'
SELECT I.Id,
I.Amount,
M.Comment
FROM #Item AS I
OUTER APPLY ( SELECT TOP 1 M.Comment
FROM #Messages AS M
WHERE M.ItemId = I.ID
ORDER BY M.[Count] DESC) AS M
WHERE i.amount > 100
DROP TABLE #Messages
DROP TABLE #Item
go for it bro....
Select
Id,
Amount,
T.Comment As Comments
From Item
Left Outer Join (Select ItemId, MAX(Id) as max_id, Comments from Messages Group by ItemId) T ON Item.ItemId=T.ItemId
Where amount > 100

Recursive CTE possible for this?

I have product data structured in the following format:
ProductID OptionID Lvl OptionDescription SubOptionID SubOptionDescription
HPH 6 1 Model 10 Studio
HPH 6 1 Model 11 DJ
HPH 7 2 Device 12 Bluetooth
HPH 7 2 Device 13 Cable
HPH 7 2 Device 14 Remote
There could be any number of levels to the product. I need to traverse the hierarchy and produce the following output - a description for each product option:
Studio-Bluetooth
Studio-Cable
Studio-Remote
DJ-Bluetooth
DJ-Cable
DJ-Remote
I've looked CTE's but the examples tend to incorporate adjacent lists (employeeID; managerID..etc) which don't seem appropriate here.
How can I achieve this output?
Thanks.
CREATE TABLE [dbo].[Products](
[ProductID] [varchar](50) NULL,
[OptionID] [int] NULL,
[Lvl] [int] NULL,
[OptionDescription] [varchar](50) NULL,
[SubOptionID] [int] NULL,
[SubOptionDescription] [varchar](50) NULL
) ON [PRIMARY]
insert into Products (ProductID, OptionID, Lvl, OptionDescription, SubOptionID, SubOptionDescription) values ('HPH', 6, 1, 'Model', 10, 'Studio')
insert into Products (ProductID, OptionID, Lvl, OptionDescription, SubOptionID, SubOptionDescription) values ('HPH', 6, 1, 'Model', 11, 'DJ')
insert into Products (ProductID, OptionID, Lvl, OptionDescription, SubOptionID, SubOptionDescription) values ('HPH', 7, 2, 'Device', 12, 'Bluetooth')
insert into Products (ProductID, OptionID, Lvl, OptionDescription, SubOptionID, SubOptionDescription) values ('HPH', 7, 2, 'Device', 13, 'Cable')
insert into Products (ProductID, OptionID, Lvl, OptionDescription, SubOptionID, SubOptionDescription) values ('HPH', 7, 2, 'Device', 14, 'Remote')
with cte as (
-- Root level
select p.Lvl, cast(p.SubOptionDescription as varchar(max)) as [ProductOption]
from #Products p where p.Lvl = 1
union all
-- Anchor part - cartesian here?
select p.Lvl, c.ProductOption + '-' + p.SubOptionDescription
from #Products p
inner join cte c on c.Lvl = p.Lvl - 1
)
select c.ProductOption from cte c;
A couple of notes.
Right now your sample answer implies that you need to create a cartesian product. I hope this is not the case, because the amount of rows will increase explosively. If there are other join conditions which are not apparent from your sample, you can introduce them in the anchor part of the CTE.
You would probably also want to return only leaf rows. There are several ways to do it - there may be some attribute in your actual data, or a combination of rank() and top (1) with ties will do the trick, although it won't be particularly efficient.

Sum of child levels total in a Hierarchy

I need to have each level be the sum of all children (in the hierarchy) in addition to any values set against that value itself for the Budget and Revised Budget columns.
I've included a simplified version of my table structure and some sample data to illustrate what is currently being produced and what I'd like to produce.
Sample table:
CREATE TABLE Item (ID INT, ParentItemID INT NULL, ItemNo nvarchar(10), ItemName nvarchar(max), Budget decimal(18, 4), RevisedBudget decimal(18, 4));
Sample data:
INSERT INTO Item (ID, ParentItemID, ItemNo, ItemName, Budget, RevisedBudget) VALUES (1, NULL, N'10.01', N'Master Bob', 0.00, 17.00);
INSERT INTO Item (ID, ParentItemID, ItemNo, ItemName, Budget, RevisedBudget) VALUES (2, 1, N'10.01.01', N'Bob 1', 0.00, 0.00);
INSERT INTO Item (ID, ParentItemID, ItemNo, ItemName, Budget, RevisedBudget) VALUES (3, 2, N'10.01.02', N'Bob 2', 2.00, 2.00);
INSERT INTO Item (ID, ParentItemID, ItemNo, ItemName, Budget, RevisedBudget) VALUES (4, 2, N'10.02.01', N'Bob 1.1', 1.00, 1.00);
CTE SQL to generate Hierarchy:
WITH HierarchicalCTE
AS
(
SELECT ID, ParentItemID, ItemNo, ItemName, Budget, RevisedBudget, 0 AS LEVEL
FROM Item
WHERE Item.ParentItemID IS NULL
UNION ALL
SELECT i.ID, i.ParentItemID, i.ItemNo, i.ItemName, i.Budget, i.RevisedBudget, cte.LEVEL + 1
FROM HierarchicalCTE cte
INNER JOIN Item i ON i.ParentItemID = cte.ID
)
So, currently my CTE produces (simplified):
ID: 1, Level: 0, Budget: 0, RevisedBudget: 17
ID: 2, Level: 1, Budget: 0, RevisedBudget: 0
ID: 3, Level: 2, Budget: 2, RevisedBudget: 2
ID: 4, Level: 2, Budget: 1, RevisedBudget: 1
And I want the results to produce:
ID: 1, Level: 0, Budget: 3, RevisedBudget: 20
ID: 2, Level: 1, Budget: 3, RevisedBudget: 3
ID: 3, Level: 2, Budget: 2, RevisedBudget: 2
ID: 4, Level: 2, Budget: 1, RevisedBudget: 1
Hopefully that is easy enough to understand.
Link to SQLFiddle with table and initial CTE: http://sqlfiddle.com/#!3/66f8b/4/0
Please note, any proposed solution will need to work in SQL Server 2008R2.
Your ItemNo appears to have the item hierarchy embedded in it. However, the first value should be '10' rather than '10.01'. If this were fixed, the following query would work:
select i.ID, i.ParentItemID, i.ItemNo, i.ItemName,
sum(isum.Budget) as Budget,
sum(isum.RevisedBudget) as RevisedBudget
from item i left outer join
item isum
on isum.ItemNo like i.ItemNo+'%'
group by i.ID, i.ParentItemID, i.ItemNo, i.ItemName;
EDIT:
To do this as a recursive CTE requires a somewhat different approach. The idea of the recursion is to generate a separate row for each possible value for an item (that is, everything below it), and then to aggregate the values together.
The following does what you need, except it puts the levels in the reverse order (I don't know if that is a real problem):
WITH HierarchicalCTE AS
(
SELECT ID, ParentItemID, ItemNo, ItemName,
Budget, RevisedBudget, 0 AS LEVEL
FROM Item i
UNION ALL
SELECT i.ID, i.ParentItemID, i.ItemNo, i.ItemName,
cte.Budget, cte.RevisedBudget,
cte.LEVEL + 1
FROM HierarchicalCTE cte join
Item i
ON i.ID = cte.ParentItemID
)
select ID, ParentItemID, ItemNo, ItemName,
sum(Budget) as Budget, sum(RevisedBudget) as RevisedBudget,
max(level)
from HierarchicalCTE
group by ID, ParentItemID, ItemNo, ItemName;

How to find Hierarchical order in SQL server

Assume an organization assigning employees to do annual reviews to others employees. Each ReviewID (who is an employee) can get reviewed by multiple employeeIDs. An employee can start/do the review only if the particular reviewID completed all his reviewIDs or has no pending reviewIDs.
Sample Data code:
CREATE TABLE FindOrder
(
EmployeeID int
,ReviewID int
)
insert findorder
values (1,3), (1,10), (1,12), (2,3), (2,5), (2,7), (3,0), (4,6), (5, 3), (6,0), (7,0), (10,0), (12,5)
EmployeeIDs that have nothing to review (ReviewID=0) should be my first set of list (3, 6, 7, 10). EmployeedIDs who can start their review now are 4,5 ( should be my second set) as they need to review 6, 3 who dont have pending ReviewIDs. Here not employeeIDs 1 or 2 because 1 has reviewID 12 who did not complete all his reviews. so on...
Please let me know if I am still not clear.
I want to find the order levels such that level 0 is (6,10,7,3), level 1 is (5, 4), level 2 is (2, 12), level 3 is (1).
I tried this cte to find order:
;WITH CTE AS
(
SELECT EmployeeID, ReviewID, 0 AS [Level] FROM FindOrder WHERE NETOUT = '0'
UNION ALL
SELECT NN.EmployeeID, NN.ReviewID, [Level]+1 FROM FindOrder nn
JOIN CTE ON NN.ReviewID=CTE.EmployeeID
)
SELECT * FROM CTE
But I get Employeeid 1 in level 1 and level 3. EmployeeID 1 should not come in level 1 as all ppl Employee 1 has to review did not complete their reviews ie., Employee 1 should come as Employee 12 did not complete his review.
In general, new subset of data in recursive query above should have filtered EmployeeID 1 and 2.
Little tricky to explain but I hope I am clear now :(
It looks like your level should actually be the longest path of reviews needed for a given employee. For example, employee one has the following paths...
1->3
1->10
1->12->5->3
The level for this employee is the longest path, and if I'm understanding your question, the only one you care about. Try this...
;WITH CTE AS
(
SELECT EmployeeID, ReviewID, 0 AS [Level] FROM FindOrder WHERE ReviewId = '0'
UNION ALL
SELECT NN.EmployeeID, NN.ReviewID, [Level]+1 FROM FindOrder nn
JOIN CTE ON NN.ReviewID=CTE.EmployeeID
)
SELECT EmployeeId, MAX(Level) AS Level FROM CTE
GROUP BY EmployeeID
ORDER BY MAX(Level)

Resources