SQL Server 2008 Recursive query - sql-server-2008

FromID ToID
-------------- ----------
1 2
2 3
3 4
5 6
6 7
9 10
I would like to use recursive query in SQL Server 2008 to create an output as
FromID Path
1 1,2,3,4
5 5,6,7
9 9,10
I have been trying to construct a SQL statement using referring to online examples as below
;WITH items AS (
SELECT FromID
, CAST(FromID AS VARCHAR(255)) AS Path
FROM tablex
UNION ALL
SELECT i.FromID
, CAST(Path + '.' + CAST(i.FromID AS VARCHAR(255)) AS VARCHAR(255)) AS Path
FROM tablex i
INNER JOIN items itms ON itms.FromID = i.ToID
)
SELECT *
FROM items
ORDER BY Path
However above doesn't work. Any ideas?

It's not entirely clear to me why your expected output is what it is. The path from 6-10 isn't the complete path to 10: that path starts at ID 5. I've written an example that outputs the full path to illustrate how you'd go about doing that. If you really do want it to start at 6 for some reason, then please clearly state the rule that determines which nodes should appear as starting points in the result set.
I noticed that every ID in your sample data has exactly one predecessor but potentially multiple successors. For that reason, I've chosen to start by identifying the nodes that are endpoints, then work backwards to the starting points. Hopefully the code comments below suffice to explain the rest of what's going on.
declare #TableX table (FromID int, ToID int);
insert #TableX values (1, 2), (2, 3), (3, 4), (5, 6), (6, 7), (6, 9), (9, 10);
with PathCTE as
(
-- BASE CASE
-- Any ID that appears as a "to" but not a "from" is the endpoint of a path. This
-- query captures the ID that leads directly to that endpoint, plus the path
-- represented by that one row in the table.
select
X1.FromID,
[Path] = convert(varchar(max), X1.FromID) + ',' + convert(varchar(max), X1.ToID)
from
#TableX X1
where
not exists (select 1 from #TableX X2 where X2.FromID = X1.ToID)
union all
-- RECURSIVE CASE
-- For every path previously identified, look for another record in #TableX that
-- leads to its starting point and prepend that record's from ID to the overall path.
select
X.FromID,
[Path] = convert(varchar(max), X.FromID) + ',' + PathCTE.[Path]
from
PathCTE
inner join #TableX X on PathCTE.FromID = X.ToID
)
-- Any ID that appears as a "from" but not a "to" is the starting point of one or more
-- paths, so we get all results beginning at one of those points. All other output from
-- PathCTE is partial paths, which we can ignore.
select *
from
PathCTE
where
not exists (select 1 from #TableX X where PathCTE.FromID = X.ToID)
order by
FromID, [Path];
Output:
FromID Path
1 1,2,3,4
5 5,6,7
5 5,6,9,10

Related

how to use concat, like and subconsultas with where?

SELECT asset_tag.asset_id, LEFT(asset_tag,SUBSTRING(asset_tag)-1 AS 'ETIQ'
from (SELECT DISTINCT S2.asset_id + ',' AS etiquetas
(SELECT S1.tag_id
FROM asset_tag AS S1
WHERE S1.tag_id
ORDER BY S1.tag_id
FOR XML PATH (''),TYPE
).VALUE('TEXT(1)`[1]','ninteger(MAX')[aset_tag] FROM asset_tag AS S2 ) asset_tag;
I have to group by asset and the asset 1 have in one column 1,2,3,4,5 or the tag that it have
how to use heidisql functions, on dbforge? I know but here not I use heidisql version 12. and is my first time working with this
The objective is that the source table that has two columns, group by column 1 and that a new column indicate separated by commas what column 1 has in column 2 (of origin).
columna 1 - 1 1 2 2 3 3 4 4
columna 2 - a b c a d a f g
and in a new column or table 1 - a b / 2 - b c
I see this answer on this page: https://stackoverflow.com/a/545672/20100117 But i donĀ“t know what mean "st1" or [text()] the alias?
SELECT Main.SubjectID,
LEFT(Main.Students,Len(Main.Students)-1) As "Students" FROM
(
SELECT DISTINCT ST2.SubjectID,
(
SELECT ST1.StudentName + ',' AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH (''), TYPE
).value('text()[1]','nvarchar(max)') [Students]
FROM dbo.Students ST2
) [Main]
If you use HeidiSQL, you can use the completion proposal to help finding the right syntax for the various functions. Just type some first characters and press Ctrl+Space:
Here's a basic example of how SUBSTRING() works:
SELECT SUBSTRING(name, 2, 3) FROM mytable;
How to concatenate rows of the same column in PostgreSQL?
Given that you want to concatenate rows of the same column, and not different columns (which is what you do when using CONCAT_WS()), what you would really be looking for is to use the ARRAY_AGG aggregation function within the ARRAY_TO_STRING function.
Documentation: https://www.postgresql.org/docs/13/functions-array.html
solution:
SELECT
a.asset_id, ARRAY_TO_STRING(ARRAY_AGG(a.tag_id), ',') AS etiqueta
FROM public.asset_tag AS a
GROUP BY a.asset_id;
Result:
on asset_id 1 | 1,3,5 tag_id
on asset_id 6 | 1,2 tag_id
If you insert this:
CREATE TABLE asset_tag ( asset_id INT,tag_id INT);
INSERT INTO asset_tag VALUES (1,1);
INSERT INTO asset_tag VALUES (1,3);
INSERT INTO asset_tag VALUES (1,5);
INSERT INTO asset_tag VALUES (6,1);
INSERT INTO asset_tag VALUES (6,2);
thanks to the person who gave me this answer .

select one row multiple time when using IN()

I have this query :
select
name
from
provinces
WHERE
province_id IN(1,3,2,1)
ORDER BY FIELD(province_id, 1,3,2,1)
the Number of values in IN() are dynamic
How can I get all rows even duplicates ( in this example -> 1 ) with given ORDER BY ?
the result should be like this :
name1
name3
name2
name1
plus I shouldn't use UNION ALL :
select * from provinces WHERE province_id=1
UNION ALL
select * from provinces WHERE province_id=3
UNION ALL
select * from provinces WHERE province_id=2
UNION ALL
select * from provinces WHERE province_id=1
You need a helper table here. On SQL Server that can be something like:
SELECT name
FROM (Values (1),(3),(2),(1)) As list (id) --< List of values to join to as a table
INNER JOIN provinces ON province_id = list.id
Update: In MySQL Split Comma Separated String Into Temp Table can be used to split string parameter into a helper table.
To get the same row more than once you need to join in another table. I suggest to create, only once(!), a helper table. This table will just contain a series of natural numbers (1, 2, 3, 4, ... etc). Such a table can be useful for many other purposes.
Here is the script to create it:
create table seq (num int);
insert into seq values (1),(2),(3),(4),(5),(6),(7),(8);
insert into seq select num+8 from seq;
insert into seq select num+16 from seq;
insert into seq select num+32 from seq;
insert into seq select num+64 from seq;
/* continue doubling the number of records until you feel you have enough */
For the task at hand it is not necessary to add many records, as you only need to make sure you never have more repetitions in your in condition than in the above seq table. I guess 128 will be good enough, but feel free to double the number of records a few times more.
Once you have the above, you can write queries like this:
select province_id,
name,
#pos := instr(#in2 := insert(#in2, #pos+1, 1, '#'),
concat(',',province_id,',')) ord
from (select #in := '0,1,2,3,1,0', #in2 := #in, #pos := 10000) init
inner join provinces
on find_in_set(province_id, #in)
inner join seq
on num <= length(replace(#in, concat(',',province_id,','),
concat(',+',province_id,',')))-length(#in)
order by ord asc
Output for the sample data and sample in list:
| province_id | name | ord |
|-------------|--------|-----|
| 1 | name 1 | 2 |
| 2 | name 2 | 4 |
| 3 | name 3 | 6 |
| 1 | name 1 | 8 |
SQL Fiddle
How it works
You need to put the list of values in the assignment to the variable #in. For it to work, every valid id must be wrapped between commas, so that is why there is a dummy zero at the start and the end.
By joining in the seq table the result set can grow. The number of records joined in from seq for a particular provinces record is equal to the number of occurrences of the corresponding province_id in the list #in.
There is no out-of-the-box function to count the number of such occurrences, so the expression at the right of num <= may look a bit complex. But it just adds a character for every match in #in and checks how much the length grows by that action. That growth is the number of occurrences.
In the select clause the position of the province_id in the #in list is returned and used to order the result set, so it corresponds to the order in the #in list. In fact, the position is taken with reference to #in2, which is a copy of #in, but is allowed to change:
While this #pos is being calculated, the number at the previous found #pos in #in2 is destroyed with a # character, so the same province_id cannot be found again at the same position.
Its unclear exactly what you are wanting, but here's why its not working the way you want. The IN keyword is shorthand for creating a statement like ....Where province_id = 1 OR province_id = 2 OR province_id = 3 OR province_id = 1. Since province_id = 1 is evaluated as true at the beginning of that statement, it doesn't matter that it is included again later, it is already true. This has no bearing on whether the result returns a duplicate.

Mysql Dynamic crosstab Pivot comparison on one table

I have a many to many table setup. This is an example of table I am focusing on.
many_to_many_id, foreign_key_id
1, 1
1, 2
1, 3
2, 1
2, 2
3, 1
3, 4
I need to given many_to_many_id 1 find any other many_to_many_ids that have matching foreign keys that exist within the first set. Given the first set 1, 2, 3 attached to many_to_many_id would return 2 as 1, and 2 are inside the set, but 3 would not be returned as 4 is not part of the test set. My boss has said I should use a dynamic cross tab to create two tables to compare with a join. I have looked for examples but they have not been helpful.
You can do this with a few simple sub-queries. The first will make sure that for each many_to_many_id there is at least one foreign_key_id in the set you're looking for (1, 2, 3) and the second will make sure there isn't even one foreign_key_id not in the set you're looking for.
SET #search_id = 1;
SELECT m.many_to_many_id FROM SampleTable m
WHERE m.many_to_many_id != #search_id
AND EXISTS ( SELECT 1 FROM SampleTable a WHERE a.many_to_many_id = m.many_to_many_id AND a.foreign_key_id IN ( SELECT b.foreign_key_id FROM SampleTable b WHERE b.many_to_many_id = #search_id ) )
AND NOT EXISTS ( SELECT 1 FROM SampleTable a WHERE a.many_to_many_id = m.many_to_many_id AND a.foreign_key_id NOT IN ( SELECT b.foreign_key_id FROM SampleTable b WHERE b.many_to_many_id = #search_id ) )
GROUP BY m.many_to_many_id
SQL Fiddle Here

SQL query one table multiple choices

I need help for designing an SQL statement. It will be created on the fly in a Web Server back end, depending on the request. This will filter a database for the desired results.
To describe my need in simplest form:
One table, two fields, 50 staff, 30 skills.
Example:
staff_id, skill_id
1, 1
1, 2
1, 3
1, 4
2, 2
2, 3
2, 4
3, 2
4, 3
Q: Who has Skill 3
A: Staff 1,2,4
(This is easy)
Q: Who has Skill 2 AND 3
A: Staff 1,2
The actual query will have a request for 5 or 6 Skills
Considering that the number of skills varies from time to time, the best way to handle this via temp table..Here is the pseudocode...syntax needs to be fixed.
create procedure my_procedure(string of skillsets)
begin
create table #tmp_skillsets(skillset smallint)
foreach skillset in skillsets
insert into #tmp_skillsets(convert_to_smallint(skillset))
select distinct staff from myTable, #tmp_skillset
where myTable.skillset = #tmp_skillsets
end
Call this procedure like: "exec my_procedure("1456") or "exec my_procedure("179248503")
I think this should work:
SELECT DISTINCT staff_id
FROM oneTable t
WHERE EXISTS (SELECT 1 FROM oneTable ti WHERE t.staff_id = ti.staff_id AND ti.skill_id = 2)
AND EXISTS (SELECT 1 FROM oneTable ti WHERE t.staff_id = ti.staff_id AND ti.skill_id = 3)

SQL, build a query using data provided in the query itself

For experimental purposes only.
I would like to build a query but not querying data extracted for any table but querying data provided in the query it self. Like:
select numbers.* from (1, 2, 3) as numbers;
or
select numbers.* from (field1 = 1, field2 = 2, field3 = 3) as numbers;
so I can do things like
select
numbers.*
from (field1 = 1, field2 = 2, field3 = 3) as numbers
where numbers.field1 > 1;
If the solution is specific for a database engine could be interesting too.
If you wanted the values to be on separate rows instead of three fields of the same row, the method is the same, just one row per value linked with a union all.
select *
from(
select 1 as FieldName union all
select 2 union all
select 3 union all
select 4 union all -- we could continue this for a long time
select 5 -- the end
) as x;
select numbers.*
from(
select 1 ,2, 3
union select 3, 4, 5
union select 6, 7, 8
union select 9, 10, 11 -- we could continue this for a long time
union select 12, 13, 14 -- the end
) as numbers;
This works with MySQL and Postgres (and most others as well).
[Edit] Use union all rather than just union as you do not need to remove duplicates from a list of constants. Give the field(s) in the first select a meaningful name. Otherwise, you can't specify a specific field later on: where x.FieldName = 3.
If you don't provide meaningful names for the fields (as in the second example), the system (at least MySQL where this was tested) will assign the name "1" for the first field, "2" as the second and so on. So, if you want to specify one of the fields, you have to write expressions like this:
where numbers.1 = 3
Use the values row constructor:
select *
from (values (1),(2),(3)) as numbers(nr);
or using a CTE.
with numbers (nr) as (
values (1),(2),(3)
)
select *
from numbers
where nr > 2;
Edit: I just noticed that you also taggeg your question with mysql: the above will not work with MySQL, only with Postgres (and a few other DBMS)
You can use a subquery without table like so:
SELECT
numbers.*
FROM (
SELECT
1 AS a,
2 AS b,
3 AS c
UNION
SELECT
4,
5,
6
) AS numbers
WHERE
numbers.a > 1
If you like queries to always have a table referenced there is a Psuedo table that always has 1 row and no columns called DUAL, you can use it like so:
SELECT
numbers.*
FROM (
SELECT
1 AS a,
2 AS b,
3 AS c
FROM
DUAL
UNION
SELECT
4,
5,
6
FROM
DUAL
) AS numbers
WHERE
numbers.a > 1