SQL Server Recursion Question - sql

I need to create links between pairs of items and rows of item pairs:
ItemA ItemB
----------------
1 2
1 3
4 5
4 6
6 2
7 8
9 2
9 10
11 12
11 13
14 15
Matching on either side of a pair constitutes a link:
Link A B
---------------
1 1 2
1 1 3
1 4 5
1 4 6
1 6 2
2 7 8
1 9 2
1 9 10
3 11 12
3 11 13
4 14 15
The Link-Item relationship will be stored in the DB as:
Link Item
--------------
1 1
1 2
1 3
1 4
1 5
1 6
1 9
1 10
2 7
2 8
3 11
3 12
3 13
4 14
4 15
Any ideas on the most efficient way to do this (SQL Server 2005)?
ItemA = 1 and ItemB = 2 is the first pair. There are 11 pairs in the set to process. Does that make sense?

I suspect that this question is dead, but here goes.
Judging by the comments, there may have been some misunderstanding of what is being done here. It looks like the Link table is being generated by the pairs. In effect, the Links represent a partition of the Items based on the relationship "is paired with".
Here is a partial answer (in the sense that it creates the Link table as illustrated but may not be the most efficient). Perhaps somebody can improve on this.
DECLARE #pairs TABLE (ItemA INT, ItemB INT)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 1, 2)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 1, 3)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 4, 5)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 4, 6)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 6, 2)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 7, 8)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 9, 2)
INSERT INTO #pairs (ItemA, ItemB) VALUES ( 9, 10)
INSERT INTO #pairs (ItemA, ItemB) VALUES (11, 12)
INSERT INTO #pairs (ItemA, ItemB) VALUES (11, 13)
INSERT INTO #pairs (ItemA, ItemB) VALUES (14, 15)
DECLARE #links TABLE (Link INT, Item INT)
DECLARE #nextItem INT
DECLARE #nextLink INT
SET #nextLink = 0
DECLARE #itemsLeft BIT
SET #itemsLeft = 1
DECLARE #insertCount INT
WHILE #itemsLeft = 1
BEGIN
-- Get the next Item not already in a link
SELECT #nextItem = MIN(allItems.Item)
FROM (SELECT ItemA AS Item FROM #pairs UNION SELECT ItemB FROM #pairs) AS allItems
LEFT JOIN #links l ON l.Item = allItems.Item
WHERE l.Link IS NULL
SET #nextLink = #nextLink + 1
IF (#nextItem IS NOT NULL)
BEGIN
-- There will be at least 1 new link
INSERT INTO #links (Link, Item) VALUES (#nextLink, #nextItem)
SET #insertCount = 1
-- Keep going until no new Items found...
WHILE (#insertCount > 0)
BEGIN
INSERT INTO #links (Link, Item)
SELECT la.Link, p.ItemB
FROM #pairs p
INNER JOIN #links la ON la.Item = p.ItemA
LEFT JOIN #links lb ON lb.Item = p.ItemB
WHERE lb.Link IS NULL
SET #insertCount = ##ROWCOUNT
INSERT INTO #links (Link, Item)
SELECT lb.Link, p.ItemA
FROM #pairs p
INNER JOIN #links lb ON lb.Item = p.ItemB
LEFT JOIN #links la ON la.Item = p.ItemA
WHERE la.Link IS NULL
SET #insertCount = #insertCount + ##ROWCOUNT
END
END
ELSE
SET #itemsLeft = 0
END
SELECT * FROM #links ORDER BY 1,2

Related

Update column with a dynamic sequence with Row_number()

I tried to update in MSSQL a column(Y) of a table(A) with with an ascending sequence that resets itself when the value of another column(X) of the same table changes.
Table A at the beginning:
id
X
Y
1
1
1
2
1
1
3
2
1
4
2
1
5
2
1
6
3
1
As it should be after the script:
id
X
Y
1
1
1
2
1
2
3
2
1
4
2
2
5
2
3
6
3
1
I tried with row_number() but in the loop, it modify all the rows :
With a counter and variable to increment:
UPDATE dbo.A
SET "Y" = #MyInc
FROM (
SELECT ROW_NUMBER() OVER ( "Id" ASC) AS row_num_Id
, Id
, X
, Y
FROM dbo.A) AS sub
WHERE row_num_Id = #MyCounter;
This will give you the results you want
CREATE TABLE #T (
Id INT NOT NULL,
X INT NOT NULL,
Y INT NOT NULL
)
INSERT INTO #T(Id, X, Y)
VALUES
(1, 1, 1),
(2, 1, 1),
(3, 2, 1),
(4, 2, 1),
(5, 2, 1),
(6, 3, 1);
GO
WITH WithRowNumbers AS (
SELECT
Id,
X,
ROW_NUMBER() OVER (PARTITION BY X ORDER BY Id) As RowNumber
FROM #T
)
UPDATE T
SET Y = WRN.RowNumber
FROM WithRowNumbers AS WRN
INNER JOIN #T AS T ON T.Id = WRN.Id
SELECT * FROM #T
Or as #CharlieFace mentions you can simplify even more, as the CTE is like a view of the original table.
UPDATE T
SET Y = T.RowNumber
FROM WithRowNumbers AS T;

SQL Server : set a row value based on a condition

I don't know what would be the appropriate title for this problem, but here is what I need to accomplish
Here is my dataset:
State TimeInState
--------------------------
1 20
3 0
4 5
8 2
5 10
1 18
3 30
12 2
2 0
What I want is another column in here, lets say FooID. What FooID is a int value that will remain same until the state is 1 again.
So the dataset would look like this:
State TimeInState FooID
------------------------------------------
1 20 1
3 0 1
4 5 1
8 2 1
5 10 1
1 18 2
3 30 2
12 2 2
2 0 2
So if there was another row at the end with State=1 then FooID will be 3 until the next state is changed.
How can I accomplish this in T-SQL?
Thanks in advance.
If you have some way of ordering rows (like an ID of sorts), then here is an example of how you could do something like this:
DECLARE #T TABLE (ID INT IDENTITY(1, 1), State INT, TimeInState INT)
INSERT #T (State, TimeInState)
VALUES (1, 20), (3, 0), (4, 5), (8, 2), (5, 10), (1, 18)
, (3, 30), (12, 2), (2, 0), (1, 1), (1, 1), (2, 1);
WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (ORDER BY CASE WHEN State = 1 THEN 0 ELSE 1 END, ID) RN
FROM #T
)
SELECT State, TimeInState, Foo.FooID
FROM CTE T
CROSS APPLY (SELECT MAX(RN) FooID FROM CTE WHERE State = 1 AND ID <= T.ID) Foo
ORDER BY ID;
But if you don't have the data ordered in some way already, then I don't think you can ensure the result set will sort the data in the way you want to sort it.

Add column with row number

I want to add a column to my select showing a set of number from say 1 to 4.
Example:
Select * gives me
Id Transaction
1 10
2 11
3 12
4 13
5 14
6 15
I want to add a column called "Flow". The result should be like this.
Id Transaction Flow
1 10 1
2 11 2
3 12 3
4 13 4
5 14 1
6 15 2
In this example the flow is from 1-4. Could be 1-n.
No particular relation between Id and Flow is needed.
If you're using SQL Server or other DBMS that allows ROW_NUMBER, you could do this:
CREATE TABLE #Tbl(Id INT, [Transaction] INT);
INSERT INTO #Tbl VALUES
(1, 10), (2, 11), (3, 12), (4, 13), (5, 14), (6, 15);
DECLARE #N INT = 4;
SELECT *,
Flow = 1 + ((ROW_NUMBER() OVER(ORDER BY Id) - 1) % #N)
FROM #Tbl
DROP TABLE #Tbl;
If you are using mySql.
Query
set #r := 0;
select Id, `Transaction`,
#r := (#r % 4) + 1 as Flow
from your_table_name
order by Id;
Demo
EDIT
Following sql query can be used irrespective of rdbms.
Query
select *, (
select ((count(*) - 1) % 4) + 1 as Flow
from your_table_name t2
where t1.Id >= t2.Id
) as Flow
from your_table_name t1;

SQL query to return table with if exists retun price 2 else return price 1

I need help making a query to show the folowing result.
Supose I have tables:
Table 1
ProductId Description
1 Banana
2 Apple
3 Melon
4 Orange
Table 2
ProductId PriceNumber Price
1 1 86
1 2 55
2 1 58
3 1 99
3 3 66
4 1 87
4 2 78
I need to show PriceNumber = 2 and if it doesn't exists show PriceNumber = 1
Wanted result:
ProductId Description PriceNum Price
1 Banana 2 55
2 Apple 1 58
3 Melon 1 99
4 Orange 2 78
Thank you!
Here's the setup of the tables:
CREATE TABLE Table1
(`ProductId` int, `Description` varchar(6))
;
INSERT INTO Table1
(`ProductId`, `Description`)
VALUES
(1, 'Banana'),
(2, 'Apple'),
(3, 'Melon'),
(4, 'Orange')
;
CREATE TABLE Table2
(`ProductId` int, `PriceNumber` int, `Price` varchar(5))
;
INSERT INTO Table2
(`ProductId`, `PriceNumber`, `Price`)
VALUES
(1, 1, '7,86'),
(1, 2, '3,55'),
(2, 1, '10,58'),
(3, 1, '2,99'),
(4, 1, '9,87'),
(4, 2, '6,78')
;
Here's the actual answer in code:
SELECT distinct(Table2.ProductId),
Description,
PriceNumber,
Price
FROM Table2
INNER JOIN Table1
ON Table1.ProductId = Table2.ProductId
WHERE (PriceNumber = 2) OR
(
(Table2.ProductId not in (
SELECT ProductId
FROM Table2
WHERE PriceNumber = 2
)
)
AND
(PriceNumber = 1)
)
Here's a link to a sqlfiddle where you can play with the code:
http://sqlfiddle.com/#!9/234ab/4/0

MIcrosoft SQL Server WHERE/ CASE clauses

I have a where statement that depends on an id and based off the id the next where is determined. EX: if ID = 1 the where statement should be a<= 3 and b between 4 and 7 if ID <> 1 the where statement should be a<= 4 and b between 5 and 7. Not sure how to do this. Tried a Case clause but had no luck.
Here is a sample table in tempdb with data.
-- Just a test
use tempdb;
go
-- Drop table
if object_id('test') > 0
drop table test
go
-- Create table
create table test
(
id int,
a int,
b int
);
-- Add data
insert into test values
(1, 3, 4),
(2, 4, 5),
(1, 4, 4),
(2, 5, 5),
(1, 3, 3),
(2, 4, 4);
-- Full table
select * from test;
Here is a solution using the CASE statement.
-- Show the data
select
*
from
test
where
(
case
when id = 1 and a <= 3 and b between 4 and 7 then 1
when id <> 1 and a <= 4 and b between 5 and 7 then 1
else 0
end
) = 1;
Something like:
where
(id = 1 and a <= 3 and b between 4 and 7) or
(id <> 1 and a <= 4 and b between 5 and 7)
Based on your requirements you just need to parenthetical WHERE statements with an OR:
...
WHERE (ID = 1 AND a <= 3 AND b BETWEEN 4 AND 7)
OR (ID <> 1 AND a<= 4 AND b BETWEEN 5 AND 7)