how to write nested query - sql-server

I have a table named Options. have Three fields Caption, OptionID, ParentOptionID.
it contains some records like :
OptiondID Caption ParentOptionID
1 Entry 0
2 Sale 1
3 Sale Invoice 2
----------------------------------------------
I want the result as :
OptiondID Caption ParentOptionID
1 Entry 0
2 Entry - Sale 1
3 Entry - Sale - Sale Invoice 2
-----------------------------------------------
Option Caption of its parent option - added in current Options Caption, and it should be nested.
This is the query that I have tried:
;with MyRelation as (
-- Anchor member definition
select OID, Cast(Caption as Varchar(1000)) as Caption, POID, iid
from #tmpOptions as e
UNION ALL
-- Recursive member definition
select e.OID, Cast(e.Caption + '-' + r.Caption as Varchar(1000)) as Caption, e.POID, e.iid
from #tmpOptions as e join MyRelation R on e.POID = R.OID
)
-- Statement that executes the CTE
select OID, Caption, POID, iid
from MyRelation

Could you tried with below query
;WITH MyRelation AS (
SELECT OptiondID, convert(varchar(max), Caption) AS Caption, ParentOptionID
FROM Options
WHERE ParentOptionID = 0
UNION ALL
SELECT Options.OptiondID, MyRelation.Caption + ' - ' + Options.Caption, Options.ParentOptionID
FROM Options
INNER JOIN MyRelation ON Options.ParentOptionID = MyRelation.OptiondID
WHERE Options.ParentOptionID <> 0
)
SELECT * FROM MyRelation

Related

How to use ##ROWCOUNT in a SQL Server derived table?

I have multiple join (left join) derived tables in my query and one of them returns an empty result set which gives me a problem in my WHERE clause.
WHERE Clause:
WHERE (ISNULL(BalanceThis.Balance,0) + ISNULL(RL.Balance,0) + ISNULL(OtherPayThis.Balance,0) + ISNULL(RUPBalThis.Balance,0)) <> 0
Query:
LEFT JOIN (
SELECT AE3.IDNo, SUM(ISNULL(AE3.Debit,0)) AS Debit, SUM(ISNULL(AE3.Credit,0)) AS Credit,
ABS(SUM(ISNULL(AE3.Debit,0))-SUM(ISNULL(AE3.Credit,0))) AS Balance
FROM AccountingEntries AE3 WITH (NOLOCK)
WHERE AE3.BookName NOT IN ('BFSL')
AND AE3.GLAccount = '1.03.01' AND ISNULL(AE3.TC,'') = 'LAC'
AND ((Datepart(MM,AE3.DateEntry)=Datepart(MM,#Date))
AND Datepart(YEAR,AE3.DateEntry)=Datepart(YEAR,#Date))
GROUP BY AE3.IDNo
) RL ON RL.IDNo = Loans.LoanID
Result set:
IDNo Debit Credit Balance
Is there a way to return a static result set whenever a derive table is empty by using ##ROWCOUNT?
like this:
IDNo Debit Credit Balance
EMP12 0 0 0
Thankss.
Here's one way to do what you want:
WITH MyQuery As
(
-- This is your original query
-- This returns no rows
SELECT 'I have no rows' As OriginalQuery WHERE 0=1
)
-- Select from your query
SELECT * FROM MyQuery
UNION ALL
-- If there's nothing in your query, UNION ALL this part
SELECT 'I only appear when there are no rows' WHERE NOT EXISTS (SELECT * FROM MyQuery)

How to use alias in where clause in SQL Server

I have following query and i am getting perfect result what i want but i want to use stManufacturerPartReference alias in where clause condition Like following second query but it gives me error.
WITH ProductsCTE (inProductId, inCategoryId, stCategory, stManufacturers, inCompanyId, stERPId,stManufacturerPartReference,
stProductName, stProductNumber, stModel, stFileLink, stImage, dcPrice,dcStandardPrice, dcOnHandQty,dcQtyOnPO,dtEstimatedShipDate, dcWeight, inSyncStatus, dtLastSyncDate,
inErrorRetry,flgIsActive, flgIsDeleted, inCreatedBy, inModifiedBy, dtModificationDate, dtCreationDate, inRecordCount)
AS (
SELECT
product.inProductId,
product.inCategoryId,
product.stCategory,
product.stManufacturers,
product.inCompanyId,
product.stERPId,
product.stProductName,
STUFF((SELECT ', ' + PM.stManufacturerPartReference
FROM tblProductManufacturers PM
JOIN tblProducts Product on PM.inProductId = Product.inProductId
JOIN tblManufacturers M on M.inManufacturerId = PM.inManufacturerId
WHERE PM.inProductId=product.inProductId
ORDER BY M.stManufacturer
FOR XML PATH('')), 1, 1, '') as stManufacturerPartReference,
product.stProductNumber,
product.stModel,
product.stFileLink,
product.stImage,
product.dcPrice,
product.dcStandardPrice,
product.dcOnHandQty,
product.dcQtyOnPO,
product.dtEstimatedShipDate,
product.dcWeight,
product.inSyncStatus,
product.dtLastSyncDate,
product.inErrorRetry,
product.flgIsActive,
product.flgIsDeleted,
product.inCreatedBy,
product.inModifiedBy,
product.dtModificationDate,
product.dtCreationDate,
CAST((COUNT(product.inProductId) OVER()) AS BIGINT) AS inRecordCount
FROM tblProducts Product WITH (NOLOCK)
WHERE 1=1
AND product.flgIsDeleted <> 1
AND flgIsHistoricItem <> 1 AND (product.inCompanyId = 1) )
SELECT P.inProductId,
P.inCategoryId,
P.stCategory,
P.stManufacturers,
P.stManufacturerPartReference,
P.inCompanyId,
P.stERPId,
P.stProductName,
P.stProductNumber,
P.stModel,
P.stFileLink,
P.stImage,
P.dcPrice,
P.dcStandardPrice,
P.dcOnHandQty,
P.dcQtyOnPO,
P.dtEstimatedShipDate,
P.dcWeight,
P.inSyncStatus,
P.dtLastSyncDate,
P.inErrorRetry,
P.flgIsActive,
P.flgIsDeleted,
P.inCreatedBy,
P.inModifiedBy,
P.dtModificationDate,
P.dtCreationDate,
P.inRecordCount
FROM ProductsCTE P
ORDER BY stCategory ASC
OFFSET (1 - 1) * 1000 ROWS
FETCH NEXT 1000 ROWS ONLY;
i want to use stManufacturerPartReference in Where Clause Like Following.
WITH ProductsCTE (inProductId, inCategoryId, stCategory, stManufacturers, inCompanyId, stERPId,stManufacturerPartReference,
stProductName, stProductNumber, stModel, stFileLink, stImage, dcPrice,dcStandardPrice, dcOnHandQty,dcQtyOnPO,dtEstimatedShipDate, dcWeight, inSyncStatus, dtLastSyncDate,
inErrorRetry,flgIsActive, flgIsDeleted, inCreatedBy, inModifiedBy, dtModificationDate, dtCreationDate, inRecordCount)
AS (
SELECT
product.inProductId,
product.inCategoryId,
product.stCategory,
product.stManufacturers,
product.inCompanyId,
product.stERPId,
product.stProductName,
STUFF((SELECT ', ' + PM.stManufacturerPartReference
FROM tblProductManufacturers PM
JOIN tblProducts Product on PM.inProductId = Product.inProductId
JOIN tblManufacturers M on M.inManufacturerId = PM.inManufacturerId
WHERE PM.inProductId=product.inProductId
ORDER BY M.stManufacturer
FOR XML PATH('')), 1, 1, '') as stManufacturerPartReference,
product.stProductNumber,
product.stModel,
product.stFileLink,
product.stImage,
product.dcPrice,
product.dcStandardPrice,
product.dcOnHandQty,
product.dcQtyOnPO,
product.dtEstimatedShipDate,
product.dcWeight,
product.inSyncStatus,
product.dtLastSyncDate,
product.inErrorRetry,
product.flgIsActive,
product.flgIsDeleted,
product.inCreatedBy,
product.inModifiedBy,
product.dtModificationDate,
product.dtCreationDate,
CAST((COUNT(product.inProductId) OVER()) AS BIGINT) AS inRecordCount
FROM tblProducts Product WITH (NOLOCK)
WHERE 1=1
AND product.flgIsDeleted <> 1
AND flgIsHistoricItem <> 1 AND (product.inCompanyId = 1) AND stManufacturerPartReference LIKE '%ABC DEF%' )
SELECT P.inProductId,
P.inCategoryId,
P.stCategory,
P.stManufacturers,
P.stManufacturerPartReference,
P.inCompanyId,
P.stERPId,
P.stProductName,
P.stProductNumber,
P.stModel,
P.stFileLink,
P.stImage,
P.dcPrice,
P.dcStandardPrice,
P.dcOnHandQty,
P.dcQtyOnPO,
P.dtEstimatedShipDate,
P.dcWeight,
P.inSyncStatus,
P.dtLastSyncDate,
P.inErrorRetry,
P.flgIsActive,
P.flgIsDeleted,
P.inCreatedBy,
P.inModifiedBy,
P.dtModificationDate,
P.dtCreationDate,
P.inRecordCount
FROM ProductsCTE P
ORDER BY stCategory ASC
OFFSET (1 - 1) * 1000 ROWS
FETCH NEXT 1000 ROWS ONLY;
But it Gives me Error "Invalid column name 'stManufacturerPartReference'."
So how can i use alias in where clause please help.
thanks.
I would do instead :
SELECT *, STUFF(stManufacturerPartReference, 1, 1, '') AS stManufacturerPartReference
FROM . . . .
. . . . CROSS APPLY
( SELECT ', ' + PM.stManufacturerPartReference
FROM tblProductManufacturers PM JOIN
tblProducts Product
ON PM.inProductId = Product.inProductId JOIN
tblManufacturers M
ON M.inManufacturerId = PM.inManufacturerId
WHERE PM.inProductId=product.inProductId
FOR XML PATH('')
) tt(stManufacturerPartReference)
WHERE . . . AND
stManufacturerPartReference LIKE '%ABC DEF%';
You need to learn about the order of execution in a SQL query: https://sqlbolt.com/lesson/select_queries_order_of_execution
WHERE comes immediately after FROM, and therefore any aliases are not available to filter on.
As it's wrapped in a CTE, filter on stManufacturerPartReference in the query after it.
WITH ProductsCTE (inProductId, inCategoryId, stCategory, stManufacturers, inCompanyId, stERPId,stManufacturerPartReference,
stProductName, stProductNumber, stModel, stFileLink, stImage, dcPrice,dcStandardPrice, dcOnHandQty,dcQtyOnPO,dtEstimatedShipDate, dcWeight, inSyncStatus, dtLastSyncDate,
inErrorRetry,flgIsActive, flgIsDeleted, inCreatedBy, inModifiedBy, dtModificationDate, dtCreationDate, inRecordCount)
AS (
SELECT
product.inProductId,
product.inCategoryId,
product.stCategory,
product.stManufacturers,
product.inCompanyId,
product.stERPId,
product.stProductName,
STUFF((SELECT ', ' + PM.stManufacturerPartReference
FROM tblProductManufacturers PM
JOIN tblProducts Product on PM.inProductId = Product.inProductId
JOIN tblManufacturers M on M.inManufacturerId = PM.inManufacturerId
WHERE PM.inProductId=product.inProductId
ORDER BY M.stManufacturer
FOR XML PATH('')), 1, 1, '') as stManufacturerPartReference,
product.stProductNumber,
product.stModel,
product.stFileLink,
product.stImage,
product.dcPrice,
product.dcStandardPrice,
product.dcOnHandQty,
product.dcQtyOnPO,
product.dtEstimatedShipDate,
product.dcWeight,
product.inSyncStatus,
product.dtLastSyncDate,
product.inErrorRetry,
product.flgIsActive,
product.flgIsDeleted,
product.inCreatedBy,
product.inModifiedBy,
product.dtModificationDate,
product.dtCreationDate,
CAST((COUNT(product.inProductId) OVER()) AS BIGINT) AS inRecordCount
FROM tblProducts Product WITH (NOLOCK)
WHERE 1=1
AND product.flgIsDeleted 1
AND flgIsHistoricItem 1 AND (product.inCompanyId = 1) )
SELECT P.inProductId,
P.inCategoryId,
P.stCategory,
P.stManufacturers,
P.stManufacturerPartReference,
P.inCompanyId,
P.stERPId,
P.stProductName,
P.stProductNumber,
P.stModel,
P.stFileLink,
P.stImage,
P.dcPrice,
P.dcStandardPrice,
P.dcOnHandQty,
P.dcQtyOnPO,
P.dtEstimatedShipDate,
P.dcWeight,
P.inSyncStatus,
P.dtLastSyncDate,
P.inErrorRetry,
P.flgIsActive,
P.flgIsDeleted,
P.inCreatedBy,
P.inModifiedBy,
P.dtModificationDate,
P.dtCreationDate,
P.inRecordCount
FROM ProductsCTE P
WHERE P.stManufacturerPartReference LIKE '%ABC DEF%'
ORDER BY stCategory ASC
OFFSET (1 - 1) * 1000 ROWS
FETCH NEXT 1000 ROWS ONLY;
You have to use outer apply or cross apply (select .. AS stManufacturerPartReference) and if your server is 2017 you could use String_AGG function to get list of values instead of for xml clause.

SQLSERVER Combine multiple lines into 1 row

I have the following tables:
Product_Order
Order_Num Order_Date
1 10/12/2017
2 10/31/2017
3 11/01/2017
Product_Order_Dtl
Order_Num Product_Desc
1 Toy_01
1 Toy_02
1 Toy_03
2 Toy_01
2 Toy_05
3 Toy_01
I am trying to update the Product Order Table to list all the products associated with that order into a new column called Product_List. Just like the following:
Product_Order
Order_Num Order_Date Product_List
1 10/12/2017 Toy_01, Toy_02, Toy_03
2 10/31/2017 Toy_01, Toy_05
3 11/01/2017 Toy_01
Is there a way to do that using an update statement? I am using a version of SQL 2012.
you can use below query
SELECT A.Order_Num, A.Order_Date, STUFF((SELECT ',' + Product_Desc
FROM Product_Order_Dtl C
where C.Order_Num=A.Order_Num
FOR XML PATH('')), 1, 1, '') AS Product_List
from Product_Order A
inner join Product_Order_Dtl B on A.Order_Num =B.Order_Num
group by A.Order_Num,A.Order_Date
This will do it:
SELECT
PO.Order_Num
, PO.Order_Date
,
STUFF
(
(
SELECT ', ' + POD.Product_Desc
FROM Product_Order_Dtl POD
WHERE POD.Order_Num = PO.Order_Num
ORDER BY POD.Product_Desc
FOR XML PATH ('')
), 1, 2, ''
) Product_List
FROM Product_Order PO

How to Find Circular References in CTE between Parent/Child tables

I have a CTE to show dependency tree over 2 tables (a parent and a child table).
There is a data problem resulting in a circular dependency, resulting in a Max Recursion level error being thrown.
i.e.
Table: Parent
Id
ItemId
Table: Child
Id
ParentId
ItemId
Example Circular Ref data
Table: Parent
Id ItemId
1 A
2 B
Table: Child
Id ParentId ItemId
1 1 B
2 2 A
There are thousands of rows in these tables. How can I write a query identify the offending reference? Or is there a way to set the Max Recursion level that will then just stop the CTE once hit instead of throw an error...then I could view the results and identify the problem child.
WITH Recursive_CTE AS
(
SELECT
ItemId,
CAST(ItemDescription AS varchar(100)) AS ItemDescription,
Qty,
CAST(ParentItemId AS SmallInt) AS ParentItemId,
CAST(ItemId AS varchar(100)) AS ParentGroupItemId,
CAST(' -' AS varchar(100)) AS LVL,
CAST(ItemId AS varchar(100)) AS HierarchyItem,
CAST(SKU AS varchar(100)) AS HierarchySKU,
CAST(ItemDescription AS varchar(100)) AS HierarchyName,
0 AS RecursionLevel
FROM dbo.vw_BOM AS child
WHERE (ParentItemId = 0)
--and ItemId = #BOMHeaderItemId
UNION ALL
SELECT
child.ItemId,
CAST(parent.LVL + child.ItemDescription AS varchar(100)) AS ItemDescription,
child.Qty,
CAST(child.ParentItemId AS SmallInt) AS ParentItemId,
parent.ParentGroupItemId,
CAST(' -' + parent.LVL AS varchar(100)) AS LVL,
CAST(parent.HierarchyItem + ':' + CAST(child.ItemId AS varchar(100)) AS varchar(100)) AS HierarchyItem,
CAST(parent.HierarchySKU + ':' + CAST(child.SKU AS varchar(100)) AS varchar(100)) AS HierarchySKU,
CAST(parent.HierarchyName + '/' + CAST(child.ItemDescription AS varchar(100)) AS varchar(100)) AS HierarchyName,
parent.RecursionLevel + 1 AS RecursionLevel
FROM Recursive_CTE AS parent INNER JOIN
dbo.vw_BOM AS child ON child.ParentItemId = parent.ItemId
)
SELECT
Recursive_CTE_1.RecursionLevel,
Recursive_CTE_1.ParentGroupItemId,
Recursive_CTE_1.ParentItemId,
Recursive_CTE_1.ItemId,
Recursive_CTE_1.Qty,
DATALENGTH(Recursive_CTE_1.LVL) AS LVLLength,
Recursive_CTE_1.ItemDescription,
item.SKU,
item.OnHandQty,
item.AllocQty,
item.AvailableQty,
item.ToBeReceivedQty,
item.AvailableWFutureQty,
Recursive_CTE_1.HierarchyItem,
Recursive_CTE_1.HierarchySKU,
Recursive_CTE_1.HierarchyName
FROM Recursive_CTE AS Recursive_CTE_1 INNER JOIN
dbo.vw_ItemInventorySummary AS item ON Recursive_CTE_1.ItemId = item.Id
ORDER BY Recursive_CTE_1.HierarchySKU
option (maxrecursion 200)
The View vw_BOM
SELECT dbo.BillOfMaterialHeader.Id AS Id, dbo.BillOfMaterialHeader.ItemId AS ItemId, 0 AS ParentItemId, FGItems.SKU AS SKU, FGItems.SKU + N': ' + FGItems.ShortDescription AS ItemDescription,
dbo.BillOfMaterialHeader.Quantity AS Qty
FROM dbo.BillOfMaterialHeader INNER JOIN
dbo.Items AS FGItems ON dbo.BillOfMaterialHeader.ItemId = FGItems.Id
UNION ALL
SELECT dbo.BillOfMaterialDetail.Id AS Id, dbo.BillOfMaterialDetail.ItemId AS ItemId, BOMHdr.ItemId AS ParentItemId, RMItems.SKU AS SKU, RMItems.SKU + N': ' + RMItems.ShortDescription AS ItemDescription,
dbo.BillOfMaterialDetail.Quantity AS Qty
FROM dbo.Items AS RMItems INNER JOIN
dbo.BillOfMaterialDetail ON RMItems.Id = dbo.BillOfMaterialDetail.ItemId INNER JOIN
dbo.BillOfMaterialHeader BOMHdr ON dbo.BillOfMaterialDetail.BillOfMaterialHeaderId = BOMHdr.Id
UPDATE
Tab's answer pointed me in the right direction. I used the flattened Parent Child table in vw_BOM and then joined it to itself per Tab's answer, which showed me where 6 Items had the same Item Id in the Parent Table and the Child Table.
Like so:
SELECT dbo.vw_BOM.SKU AS ParentSKU, vw_BOM_1.SKU AS ChildSKU
FROM dbo.vw_BOM INNER JOIN
dbo.vw_BOM AS vw_BOM_1 ON dbo.vw_BOM.ItemId = vw_BOM_1.ParentItemId AND dbo.vw_BOM.ParentItemId = vw_BOM_1.ItemId
Your CTE already has a hierarchy with the ItemID path concatenated. How about using that to determine if the item has already been seen?
Add a new column to the anchor portion of your CTE, HasCycle = Convert(bit, 0).
Then in the recursive portion of your CTE, add the column and a condition in the WHERE clause like so:
...
UNION ALL
SELECT
... other columns,
HasCycle = Convert(bit,
CASE
WHEN ':' + parent.HierarchyItem + ':' LIKE
'%:' + Convert(varchar(100), child.ItemID) + ':%'
THEN 1
ELSE 0
END)
FROM
...
WHERE
...
AND parent.HasCycle = 0 --terminate after cycle is found
;
Then you can select from the recursive CTE WHERE HasCycle = 1 and see all the rows that begin a cycle and their exact path upwards in the HierarchyItem.
Simple self-join should do it:
SELECT * FROM MyTable t1
INNER JOIN MyTable t2
ON t1.Parent=t2.Child
AND t1.Child=t2.Parent
I have seen these issues before and have resorted to adding items one level at a time, ignoring those seen before.

SQL server: WHERE in xml field

Example: I have table TableA with 3 records:
record 1:
id = 1, value = '<Employee id='1' name='Employee1'></Employee><Employee id='2' name='Employee2'></Employee>'
record 2:
id = 2, value = '<Employee id='1' name='Employee1'></Employee><Employee id='2' name='Employee2'></Employee><Employee id='3' name='Employee3'></Employee>'
record 3:
id = 3, value = '<Employee id='1' name='Employee1'></Employee><Employee id='2' name='Employee2'></Employee><Employee id='3' name='Employee3'></Employee><Employee id='4' name='Employee4'></Employee>'
the query:
SELECT * FROM TableA
WHERE...
How can I put the where clause to get only record 1?
Many thanks,
The problem with the data is that it doesn't contain well formed xml - you will need to wrap it before you can use the xml tools in Sql like xquery.
SELECT *
FROM
(
SELECT
Nodes.node.value('(./#id)[1]', 'int') AS EmployeeId,
Nodes.node.value('(./#name)[1]', 'varchar(50)') AS EmployeeName
FROM
(
SELECT CAST('<xml>' + value + '</xml>' AS Xml) As WrappedXml
FROM TableA
) AS x
cross apply x.WrappedXml.nodes('//Employee') as Nodes(node)
) as y
WHERE
y.EmployeeId = 1;
Inner select -wraps the xml
Middle select - standard xquery
Outer select - where filter
You haven't clarified what you mean w.r.t. get only record 1, but if you mean just the first element of each row (which coincidentally also has id = 1), you can use ROW_NUMBER() to assign a sequence:
SELECT *
FROM
(
SELECT
Nodes.node.value('(./#id)[1]', 'int') AS EmployeeId,
Nodes.node.value('(./#name)[1]', 'varchar(50)') AS EmployeeName,
ROW_NUMBER() OVER (PARTITION BY x.Id ORDER BY ( SELECT 1 )) SequenceId
FROM
(
SELECT Id, CAST('<xml>' + value + '</xml>' AS Xml) As WrappedXml
FROM TableA
) AS x
cross apply x.WrappedXml.nodes('//Employee') as Nodes(node)
) as y
WHERE SequenceId = 1;
Both Fiddles here
I tried with this query and It returns record 1 as I expect:
SELECT * FROM TableA
WHERE value.exist('(Employee[#id = 1])') = 1 and value.exist('(Employee[#id = 2])') = 1 AND value.value('count(Employee[#id])', 'int') = 2
Do you have any comments for this query? Should I use it? :)

Resources