Recurcive cte to identify circular reference in data - sql-server

I am trying to identify recursive/circular reference in my data for which I need recursive cte.
For example I have table that contains Product_ID and Inner_Product_ID. I want results when Product_ID A is inner to Product_ID B, which is inner to Product_ID C, which is inner to Product_ID A.
Sample data
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
1 2
3 4
Expected output
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
I have tried basic query with cte but not sure how to implement recursive cte for this problem:
;WITH RNCTE
AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY pr1.PRODUCT_ID
ORDER BY pr1.PRODUCT_ID
) rn
FROM
TableName pr1),
cte
AS ( SELECT *
FROM RNCTE
WHERE RNCTE.rn = 1
UNION ALL
SELECT *
FROM cte c
JOIN RNCTE r
ON r.PRODUCT_ID = c.PRODUCT_ID
AND r.rn = c.rn + 1)
SELECT *
FROM cte;

try this - it walks through the linked records, and finds if the 'walk' eventually terminates, or not. If it lasts for more than the number of records in the table, then it must be a loop. 'Efficient' I am not sure of that!
;WITH UCNT AS (SELECT count(0) c from products),
RNCTE
AS (SELECT 1 as Levle, Product_ID, INNER_PRODUCT_ID FROM Products
UNION ALL
SELECT levle + 1, P.Product_ID, P.INNER_PRODUCT_ID
FROM RNCTE R
JOIN Products P
ON P.PRODUCT_ID = R.INNER_PRODUCT_ID
WHERE levle <= (SELECT c + 2 FROM UCNT))
--when the recursion count levle exceeds the count of records in the table,
--we must have recursion, because
--termination has to otherwise occur. The most extreme case is
--that all records are linked, with termination
--after this, we have to be in a 'loop'
SELECT TOP 1 with ties * FROM RNCTE order by levle desc
option (maxrecursion 0)

I think you don't need to use CTE or RECUSRIVE CTE :
SELECT pr1.*
FROM TableName pr1
WHERE EXISTS (SELECT 1 FROM TableName pr2 WHERE pr2.INNER_PRODUCT_ID = pr1.PRODUCT_ID);

Related

SQL - get all rows in one table created in a time difference of 1 minute

I have a table as:
Id Ticket_NUM Date Comments
== ========== ======================== =======
1 2 2014-08-29 08:44:34.122 a
2 5 2014-08-29 08:44:34.125 b
3 3 2014-08-29 08:44:34.137 a
4 4 2016-08-29 08:44:34.137 b
Now, I would like to get Ticket_NUM that are created on the same datetime(2014-08-29 08:44:34.122) with a maximum time difference of < 60 seconds. Can anyone let me know how can we write a query to get this data. I used self join on the table but I'm not getting Ticket_NUM(2, 5, 3) that I'm looking for.
One way would be with LEAD and LAG
select * from(
select
*,
myFlag = case
when abs(datediff(second,[date],lag([Date]) over (order by [Date]))) < 60 then 1
when abs(datediff(second,[date],lead([Date]) over (order by [Date]))) < 60 then 1
else 0
end
from yourtable)
where myFlag = 1
order by [Date]
You can remove the where myFlag = 1 to see which ones are flagged.
Or an uglier method:
with cte as(
select *, RN = row_number() over (order by [Date])
from yourtable
),
staging as(
select
c.ID
,c.Ticket_NUM
,c.[Date]
,c.Comments
,c2DT = min(c2.[Date])
,c3DT = max(c3.[Date])
from
cte c
left join cte c2 on c2.RN = c.RN + 1 --row above
left join cte c3 on c3.RN = c.RN - 1 --row below
group by
c.ID
,c.Ticket_NUM
,c.[Date]
,c.Comments)
select
c.ID
,c.Ticket_NUM
,c.[Date]
,c.Comments
from stating c
where abs(datediff(second,c.[Date],c.c2DT)) < 60 or abs(datediff(second,c.[Date],c.c3DT)) < 60

SQL Server multiple select on an Offset...fetch...next query

I'm trying to get data into datatables (js library for data table) by server-side processing.
The data should be produced as below
+---------+--------+--------+
| Name | TotalA | TotalB |
+---------+--------+--------+
| Person1 | 10 | 40 |
+---------+--------+--------+
The query that I tried
select
a.Name,
(select count(*) from SummaryA where id = a.id) as TotalA,
(select count(*) from SummaryB where id = a.id) as TotalB
from
records a
order by
a.Name
offset 0 rows fetch next 10 rows only
and
select
aa.Name,
(select count(*) from SummaryA where id = aa.id) as TotalA,
(select count(*) from SummaryB where id = aa.id) as TotalB
from
(select
a.Name, a.id
from
records a
order by
a.Name
offset 0 rows fetch next 10 rows only) as aa
However, these queries will result in an error as below
Error in query: Invalid usage of the option NEXT in the FETCH statement.
Running below query is not a problem
select
a.Name
from
records a
offset 0 rows fetch next 10 rows only
Issue- offset_row_count_expression can be a variable, parameter, or constant scalar subquery. When a subquery is used, it cannot reference any columns defined in the outer query scope.
link
Try
;with temp as (select a.name ,
count(b.id) as TotalA ,
count(c.id) as Totalb
FROM records a
left join SummaryA b
b.id=a.id
left join SummaryB c
c.id=a.id
group by a.name)
select * from temp
order by temp.Name
Offset 0 rows
fetch next 10 rows only
This can also be solved
with tmp as (
select a.name ,
a.id
FROM records a
order by temp.Name
Offset 0 rows
fetch next 10 rows only
)
select a.name ,
count(b.id) as TotalA ,
count(c.id) as Totalb
FROM tmp a
left join SummaryA b
b.id=a.id
left join SummaryB c
c.id=a.id
group by a.name order by a.Name

Left join with Sum Clause with more than 1 table gives incorrect Sum

I am trying to get the Sum of rows while applying a left join with more than 1 table. It seems it is creating a matrix of result which results in wrong sum function.
Example:
First Table: Customer
Second Table: TotalAssets
Third Table: TotalLiability
Table Structure:
Customer
CustID(int) CustomerName(varchar)
1 Abc
2 Def
3 Ghi
TotalAssets
CustID Amount
1 2000
1 1000
2 600
TotalLiability
CustID Amount
1 1000
1 1000
2 800
Output Expected
CustID TotalAssets TotalLiability
1 3000 2000
2 600 800
Current Query
Select c.CustID , Sum(a.Amount) , Sum(l.Amount) From Customer c
left join TotalAssests a on a.CustID = c.CustID
left join TotalLiability l on l.CustID = c.CustID
Group by c.CustID
The problem with this current query is the sum is not correct as i think the first left join create a first set with multiple records and then second one is applied.
Any help is appreciated
UPDATE:
I find some luck by following method but it seems a bad/hacky option as in my case i have over 7-8 elements in group by and adding more left clauses results in query difficult to manage.
New Query which is resulting correct result but looks very bad to maintains
Select Set1.CustID , Set1.TotalAssets, Sum(l.Amount) from (Select c.CustID , Sum(a.Amount) as TotalAssets From Customer c
left join TotalAssests a on a.CustID = c.CustID
Group by c.CustID)Set1
left join TotalLiability l on l.CustID = Set1.CustID.
Group by Set1.CustID , Set1.TotalAssets
I think this gets you what you want with minimum complexity:
select c.CustId, isnull(a.Amount, 0) as TotalAssets, isnull(l.Amount, 0) as TotalLiability
from Customers c
left join (
select CustId, sum(Amount) as Amount from TotalAssets group by CustId
) a on a.CustId = c.CustId
left join (
select CustId, sum(Amount) as Amount from TotalLiability group by CustId
) l on l.CustId = c.CustId
You need to group/sum the two tables separately, since the data in them is independent. Left-joining both to the customers table ensures that customers with no entries in either/both tables are still reported.
This should work:
Select c.CustID
, (select sum(a.amount) from TotalAssests a where a.CustId = c.CustID) as SumAsset
, (select Sum(l.Amount) TotalLiability l where l.CustID = c.CustID) as SumLiability
From Customer c
Hope the below works with less maintenance,
DECLARE #Customer TABLE (CustID int, CustomerName varchar(50)) DECLARE #TotalAssets TABLE (CustID int, Amount INT) DECLARE #TotalLiability TABLE (CustID int, Amount INT)
INSERT INTO #Customer
SELECT 1,
'ABC'
UNION
SELECT 2,
'DEF'
UNION
SELECT 3,
'GHI'
--Select * From #Customer
INSERT INTO #TotalAssets
SELECT 1,
2000
UNION
SELECT 1,
1000
UNION
SELECT 2,
600
--Select * From #TotalAssets
INSERT INTO #TotalLiability
SELECT 1,
1000
UNION
SELECT 1,
1000
UNION
SELECT 2,
800
--Select * From #TotalLiability
SELECT *
FROM #Customer
SELECT C.CustID,
C.CustomerName,
Sum(A.Amount) TotalAssets,
Sum(L.Amount) TotalLiability
FROM #Customer C
JOIN #TotalAssets A ON C.CustID = A.CustID
JOIN #TotalLiability L ON C.CustId = L.CustID
GROUP BY C.CustID,
C.CustomerName

sql server: Creating a ranked list by assigning weights

I have a table Survey with 10 entries. http://i.imgur.com/9pvqWEW.png. 10 users had to submit a ranked list of items 1-5. 1 being most important. How can I write a query that can assign a weight to each column, count the number of times a item occurred in that column, and return a the 5 items in a ranked list 1-5 with its "score". Is this even possible with SQL Server?
So I guess +5 to each item for every time it occurred in column Growth1. +4 to for column Growth2 etc.
Is that the best way to go about creating a ranked list?
Desired output: possible
1 Market Share 45
2 Disease Profile Development 30
3 Physician Recruitment 28
4 Referrals 21
5 Splitters 18
SELECT ROW_NUMBER() OVER (ORDER BY
ISNULL(b.count_1,0) +
ISNULL(c.count_2,0) +
ISNULL(d.count_3,0) +
ISNULL(e.count_4,0) +
ISNULL(f.count_5,0) DESC) AS rank
,a.categories
,ISNULL(b.count_1,0) + ISNULL(c.count_2,0) + ISNULL(d.count_3,0) + ISNULL(e.count_4,0) + ISNULL(f.count_5,0) AS total_score FROM
(SELECT DISTINCT Growth1 AS categories FROM [TESTDB].[dbo].[testtable]
UNION SELECT DISTINCT Growth2 AS categories FROM [TESTDB].[dbo].[testtable]
UNION SELECT DISTINCT Growth3 AS categories FROM [TESTDB].[dbo].[testtable]
UNION SELECT DISTINCT Growth4 AS categories FROM [TESTDB].[dbo].[testtable]
UNION SELECT DISTINCT Growth5 AS categories FROM [TESTDB].[dbo].[testtable]) AS a
LEFT JOIN
(SELECT Growth1, COUNT(Growth1) * 5 AS count_1
FROM [TESTDB].[dbo].[testtable]
GROUP BY Growth1) AS b
ON a.categories = b.Growth1
LEFT JOIN
(SELECT Growth2, COUNT(Growth2) * 4 AS count_2
FROM [TESTDB].[dbo].[testtable]
GROUP BY Growth2) AS c
ON a.categories = c.Growth2
LEFT JOIN
(SELECT Growth3, COUNT(Growth3) * 3 AS count_3
FROM [TESTDB].[dbo].[testtable]
GROUP BY Growth3) AS d
ON a.categories = d.Growth3
LEFT JOIN
(SELECT Growth4, COUNT(Growth4) * 2 AS count_4
FROM [TESTDB].[dbo].[testtable]
GROUP BY Growth4) AS e
ON a.categories = e.Growth4
LEFT JOIN
(SELECT Growth5, COUNT(Growth5) * 1 AS count_5
FROM [TESTDB].[dbo].[testtable]
GROUP BY Growth5) AS f
ON a.categories = f.Growth5
You can simplify the query by splitting it up in a few common table expressions;
WITH cte AS (
SELECT 5 score, growth1 area FROM mytable
UNION ALL
SELECT 4 score, growth2 area FROM mytable
UNION ALL
SELECT 3 score, growth3 area FROM mytable
UNION ALL
SELECT 2 score, growth4 area FROM mytable
UNION ALL
SELECT 1 score, growth5 area FROM mytable
), cte2 AS (
SELECT area, SUM(score) score FROM cte GROUP BY area
HAVING area IS NOT NULL
), cte3 AS (
SELECT area, score,
RANK() OVER (ORDER BY score DESC) rank
FROM cte2
)
SELECT rank, area, score
FROM cte3
ORDER BY rank;
cte1 basically just extracts the data from each column along with the associated score.
cte2 sums the score up by area.
cte3 ranks the total score per area.
The outer query just orders the result by rank.
An SQLfiddle to test with.

select records After top 1000 rows

I want to select records from 1000 to 2000 rows and so on in batch of 1000.
I have written query to select top 1000 records but how can i select from 1000 to 2000.
can you help me with a query that can select those records.
SELECT TOP 1000 *
FROM tblProductInformation p1 INNER JOIN tblProduct1 p
ON p.productname = p1.productname
I think you need to order on specific a column, for example order on the primary key.
SELECT *
FROM
(
SELECT tbl.*, p.*, ROW_NUMBER() OVER (ORDER BY ProductID_PRIMARYKEY) rownum
FROM tblProductInformation as tbl INNER JOIN tblProduct1 p
ON p.productname = p1.productname
) seq
WHERE seq.rownum BETWEEN 1000 AND 2000
WITH cte AS(
SELECT ROW_NUMBER()OVER(Order By p1.productname ASC, p1.ID ASC) As RowNum
,p1 .*
from tblProductInformation p1
inner join tblProduct1 p on p.productname = p1.productname
)
SELECT * FROM cte
WHERE RowNum BETWEEN #FromRowNum AND #ToRowNum
ROW_NUMBER: http://msdn.microsoft.com/en-us/library/ms186734.aspx
Paging on SQL-Server: http://www.mssqltips.com/sqlservertip/1175/page-through-sql-server-results-with-the-rownumber-function/
WITH Results AS (
select TOP 1000 f.*, ROW_NUMBER() OVER (ORDER BY f.[type]) as RowNumber
from tblProductInformation f
) select *
from Results
where RowNumber between 1001 and 2000
tutorial
Answer Late .. but could be helpful for some one coming here ... simple one
Another simple approach ...
You can create a similar table "tblProductInformation_tmp" OR #tblProductInformation_tmp - with an extra column "UniqueID" and make that auto-increment IDENTITY column.
then just insert the same data to table :
insert * into tblProductInformation_tmp
select * from tblProductInformation
Now its simple ryt :
select * from tblProductInformation_tmp where UniqueID < 1001
select * from tblProductInformation_tmp where UniqueID between 1001 and 2001
:) Dont forget to delete : tblProductInformation_tmp
Rigin

Resources