Select recursive in a CTE SQL query - sql-server

I have a table with unique KEY (UserID and Type)
UserID | Type | Column1 | Column2
----------------------------
1 | 1 | *NULL* | 1
1 | 2 | ABC | *NULL*
1 | 3 | *NULL* | 2
2 | 1 | *NULL* | 1
2 | 2 | CDE | *NULL*
3 | 1 | *NULL* | 3
3 | 2 | DTE | *NULL*
I am trying to build a query for searching all Users IDs contains:
(Type=1 AND Column2=1) AND (Type=3 AND Column2=2)

This question has nothing to do with recursive CTEs. You can solve it with aggregation and a having clause:
select userid
from t
where (Type = 1 AND Column2 = 1) OR (Type = 3 AND Column2 = 2)
group by userid
having count(distinct type) = 2;

With CTE(userid)
AS
(
SELECT userid
FROM t
WHERE (Type = 1 AND Column2 = 1)
)
SELECT userid
FROM CTE
WHERE EXISTS(
SELECT 1
FROM CTE
WHERE Type = 3 AND Column2 = 2 )

Related

Consolidating 2 Recursive CTE Statements

The following statement uses 2 separate recursive CTE statements to build a list of steps from a starting location to an ending location based on trips. The desired output is correct, however I am wondering if it is possible to consolidate the 2 CTE statements into one.
The difficulty I am having is relating the endLocation to the startLocation in the first recursive iteration cte1.
The database is SQL Server 2017. I have added the SQL fiddle below:
[SQL Fiddle][1]
SQL Server 2017 Schema Setup:
Create Table TripLocation
(TripID int,
LocationID int,
StopOrder int
)
Create table FromTo
(tripID int,
fromLocationID int,
fromStopOrder int,
toLocationID int,
toStopOrder int
)
Create table cte1Temp
(startTripID int,
startLocationID int,
tripID int,
fromLocationID int,
toLocationID int,
step int)
Create table cte2Temp
(startTripID int,
startLocationID int,
endLocationID int,
tripID int,
fromLocationID int,
toLocationID int,
step int)
--LIST OF LOCATIONS FOR EACH TRIP
Insert into TripLocation
Values
(1,1,0),
(1,2,1),
(1,1,2),
(2,2,0),
(2,3,1),
(2,2,2),
(3,3,0),
(3,4,1),
(3,3,2)
--LIST OF POSSIBLE TO/FROM COMBINATIONS FOR EACH TRIP BASED ON STOPORDER
insert into FromTo
select
FromLocation.tripID,
FromLocation.LocationID [fromLocationID],
FromLocation.StopOrder [fromStopOrder],
ToLocation.LocationID [toLocationID],
ToLocation.StopOrder [toStopOrder]
from
TripLocation FromLocation
join TripLocation ToLocation
on FromLocation.tripID = ToLocation.tripID
and ToLocation.StopOrder >= FromLocation.StopOrder
and FromLocation.LocationID <> ToLocation.LocationID
;
--FIND ALL POSSIBLE END LOCATIONS FOR EACH START LOCATION IF TRIPS SHARE A COMMON LOCATION
with cte1 as
(
select
tripID [startTripID],
fromLocationID [startLocationID],
tripID,
fromLocationID,
toLocationID,
1 [step]
from
FromTo
union all
select
anchor.startTripID,
anchor.startLocationID,
member.tripID,
member.fromLocationID,
member.toLocationID,
anchor.step + 1 [step]
from
FromTo member
join cte1 anchor
on anchor.toLocationID = member.fromLocationID
and member.toLocationID <> anchor.fromLocationID
and member.tripID <> anchor.tripID
)
insert into cte1Temp
select
*
from
cte1
;
--GENERATE PLAN FOR EACH START LOCATION TO AN END LOCATION
with cte2 as
(
select
startTripID,
StartLocationID,
ToLocationID [EndLocationID],
tripID,
FromLocationID,
ToLocationID,
step
from
cte1Temp
union all
select
b.startTripID,
b.StartLocationID,
b.ToLocationID,
a.tripID,
a.FromLocationID,
a.ToLocationID,
a.step
from
cte1Temp b
join CTE2 a
on a.endLocationID = b.FromLocationID
and a.startLocationID = b.startLocationID
)
insert into cte2Temp
select
*
from
cte2
Query 1:
select
*
from
cte2Temp
order by
startlocationID, endLocationID, step
Results:
| startTripID | startLocationID | endLocationID | tripID | fromLocationID | toLocationID | step |
|-------------|-----------------|---------------|--------|----------------|--------------|------|
| 1 | 1 | 2 | 1 | 1 | 2 | 1 |
| 1 | 1 | 3 | 1 | 1 | 2 | 1 |
| 1 | 1 | 3 | 2 | 2 | 3 | 2 |
| 1 | 1 | 4 | 1 | 1 | 2 | 1 |
| 1 | 1 | 4 | 2 | 2 | 3 | 2 |
| 1 | 1 | 4 | 3 | 3 | 4 | 3 |
| 1 | 2 | 1 | 1 | 2 | 1 | 1 |
| 2 | 2 | 3 | 2 | 2 | 3 | 1 |
| 2 | 2 | 4 | 2 | 2 | 3 | 1 |
| 2 | 2 | 4 | 3 | 3 | 4 | 2 |
| 2 | 3 | 1 | 2 | 3 | 2 | 1 |
| 2 | 3 | 1 | 1 | 2 | 1 | 2 |
| 2 | 3 | 2 | 2 | 3 | 2 | 1 |
| 3 | 3 | 4 | 3 | 3 | 4 | 1 |
| 3 | 4 | 1 | 3 | 4 | 3 | 1 |
| 3 | 4 | 1 | 2 | 3 | 2 | 2 |
| 3 | 4 | 1 | 1 | 2 | 1 | 3 |
| 3 | 4 | 2 | 3 | 4 | 3 | 1 |
| 3 | 4 | 2 | 2 | 3 | 2 | 2 |
| 3 | 4 | 3 | 3 | 4 | 3 | 1 |
You can try this
Create Table TripLocation
(TripID int,
LocationID int,
StopOrder int
)
Create table FromTo
(tripID int,
fromLocationID int,
fromStopOrder int,
toLocationID int,
toStopOrder int
)
Create table cte1Temp
(startTripID int,
startLocationID int,
tripID int,
fromLocationID int,
toLocationID int,
step int,
path varchar(max)
)
--LIST OF LOCATIONS FOR EACH TRIP
Insert into TripLocation
Values
(1,1,0),
(1,2,1),
(1,1,2),
(2,2,0),
(2,3,1),
(2,2,2),
(3,3,0),
(3,4,1),
(3,3,2)
--LIST OF POSSIBLE TO/FROM COMBINATIONS FOR EACH TRIP BASED ON STOPORDER
insert into FromTo
select
FromLocation.tripID,
FromLocation.LocationID [fromLocationID],
FromLocation.StopOrder [fromStopOrder],
ToLocation.LocationID [toLocationID],
ToLocation.StopOrder [toStopOrder]
from
TripLocation FromLocation
join TripLocation ToLocation
on FromLocation.tripID = ToLocation.tripID
and ToLocation.StopOrder >= FromLocation.StopOrder
and FromLocation.LocationID <> ToLocation.LocationID
;
--FIND ALL POSSIBLE END LOCATIONS FOR EACH START LOCATION IF TRIPS SHARE A COMMON LOCATION
with cte1 as
(
select
tripID [startTripID],
fromLocationID [startLocationID],
tripID,
fromLocationID,
toLocationID,
1 [step],
cast('1_' + ltrim(str(tripID)) + '-' + ltrim(str(tolocationID)) as varchar(max)) [path]
from
FromTo
union all
select
anchor.startTripID,
anchor.startLocationID,
member.tripID,
member.fromLocationID,
member.toLocationID,
anchor.step + 1 [step],
anchor.path + ',' + ltrim(str(anchor.step + 1)) + '_' + + ltrim(str(member.tripID)) + '-' + ltrim(str(member.toLocationID))
from
FromTo member
join cte1 anchor
on anchor.toLocationID = member.fromLocationID
and member.toLocationID <> anchor.fromLocationID
and member.tripID <> anchor.tripID
)
insert into cte1Temp
select
*
from
cte1
;
select
StartLocationID,
TolocationId,
Substring(Value, 1,Charindex('-', Value)-1) as Trip,
Substring(Value, Charindex('-', Value)+1, LEN(Value)) as CrossingLocationID
from
cte1Temp
CROSS APPLY STRING_SPLIT(path, ',')
order by StartLocationId, ToLocationId, Trip
Results:
StartLocationID TolocationId Trip CrossingLocationID
1 2 1_1 2
1 3 1_1 2
1 3 2_2 3
1 4 1_1 2
1 4 2_2 3
1 4 3_3 4
2 1 1_1 1
2 3 1_2 3
2 4 1_2 3
2 4 2_3 4
3 1 1_2 2
3 1 2_1 1
3 2 1_2 2
3 4 1_3 4
4 1 1_3 3
4 1 2_2 2
4 1 3_1 1
4 2 1_3 3
4 2 2_2 2
4 3 1_3 3

How to find difference between two tables in MSSQL

I have got two tables 'Customer'.
The first one:
ID | UserID | Date
1. | 1 | 2018-05-01
2. | 1 | 2018-05-02
The second one:
ID | UserID | Date
1. | 1 | 2018-05-01
2. | 1 | 2018-05-02
3. | 1 | 2018-05-03
So, as you can see in the second table, there is one row more.
I have written so far this code:
;with cte_table1 as (
select UserID, count(id) cnt from db1.Customer group by UserID
),
cte_table2 as (
select UserID, count(id) cnt from db2.Customer group by UserID
)
select * from cte_table1 t1
join cte_table2 t2 on t2.UserID = t1.UserID
where t1.cnt <> t2.cnt
and this gives me expected result:
UserID | cnt | UserID | cnt
1 | 2 | 1 | 3
And so far, everything is fine. The thing is, these two tables have many rows and I'd like to have result with dates, where cnt does not match.
In other words, I'd like to have something like this:
UserID | cnt | Date | UserID | cnt | Date
1 | 2 | 2018-05-01 | 1 | 3 | 2018-05-01
1 | 2 | 2018-05-02 | 1 | 3 | 2018-05-01
1 | 2 | NULL | 1 | 3 | 2018-05-03
The best soulution would be resultset where both cte's are joined to give this:
UserID | cnt | Date | UserID | cnt | Date
1 | 2 | 2018-05-01 | 1 | 3 | 2018-05-01
1 | 2 | 2018-05-02 | 1 | 3 | 2018-05-01
1 | 2 | NULL | 1 | 3 | 2018-05-03
1 | 2 | 2018-05-30 | 1 | 3 | NULL
You should do a FULL OUTER JOIN query like below
Select
C1.UserID,
C1.cnt,
C1.Date,
C2.UserID,
C2.cnt,
C2.Date
from
db1.Customer C1
FULL OUTER JOIN
db2.Customer C2
on C1.UserId=C2.UserId and C1.date=C2.Date

Sql query to check if a certain value appears more than once in rows

I have table with 5 columns like this
+----+-------------------------+-----------+--------+-----------+
| Id | CreateDate | CompanyId | UserId | IsEnabled |
+----+-------------------------+-----------+--------+-----------+
| 1 | 2016-01-02 23:40:46.517 | 1 | 1 | 1 |
| 2 | 2016-01-16 00:07:59.857 | 1 | 2 | 1 |
| 3 | 2016-01-25 15:17:54.420 | 3 | 3 | 1 |
| 25 | 2016-03-07 16:48:39.260 | 24 | 10 | 0 |
| 26 | 2016-03-07 16:48:39.263 | 25 | 2 | 0 |
+----+-------------------------+-----------+--------+-----------+
(thanks http://www.sensefulsolutions.com/2010/10/format-text-as-table.html for ASCII table!)
I'm trying to check if a UserId is recorded for more than one CompanyId's.
So far I managed to check if a UserId happens to appear more than one by using this query
WITH T AS
(
SELECT * ,
Count(*) OVER (PARTITION BY UserId) as Cnt
From CompanyUser
)
select Distinct UserId
FROM T
Where Cnt >1
It returns 2 correctly.
Where I'm stuck is, how can I parameterize the UserId and check if an Id is recorded for more than one company.
Declare #UserID as bigint
Set #UserID = 2
select Distinct Count(CompanyID)
FROM ComapynUser
Where UserId = #UserId
I think this gives you what you need.

update order column in a table but maintain the order

i have a table
id | title | F_ID | order
----------------------------
1 | test 1| 1 | 44
2 | test 3| 1 | 3
3 | test 4| 1 | 1
4 | test 5| 2 | 1
i want to update order column to +10 for all rows that have F_ID 1 but keep the order
the result need to be
id | title | F_ID | order
----------------------------
1 | test 1| 1 | 30
2 | test 3| 1 | 20
3 | test 4| 1 | 10
4 | test 5| 2 | 1
i can insert all rows that i want to update to temp table
and then loop the rows and update every row in the real table by [id].
maybe there is a better option?
I think this should work:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
create table test (id int, title varchar(49), F_ID int, [order] int)
insert test values
(1 , 'test 1', 1, 44),
(2 , 'test 3', 1, 3),
(3 , 'test 4', 1, 1),
(4 , 'test 5', 2, 1)
Query 1:
update test
set [order] = new_order
from test t
inner join (
select
id,
new_order = ROW_NUMBER() over (partition by f_id order by [order]) * 10
from test t
where f_id = 1
) t2
on t.id = t2.id
Results:
Query 2:
select * from test
Results:
| ID | TITLE | F_ID | ORDER |
|----|--------|------|-------|
| 1 | test 1 | 1 | 30 |
| 2 | test 3 | 1 | 20 |
| 3 | test 4 | 1 | 10 |
| 4 | test 5 | 2 | 1 |
Well there might be a better solution then this but you can try this by using recursive CTE.
;WITH updCTE
AS
(
SELECT 30 AS YourOrder, 1 AS id
UNION ALL
SELECT YourOrder - 10 AS YourOrder, id + 1 AS id
FROM updCTE
WHERE YourOrder > 1
)
UPDATE YourTable
SET [order] = YourOrder
FROM updCTE
JOIN YourTable ON updCTE.id = YourTable.id
WHERE YourTable.F_ID = 1
ORDER BY YourTable.id

SQL Server 2005 T-SQL Problem: Need help in omitting records

Good day!
I need help in writing a query.. I have records in a table below.. The condition would be no records should be displayed if the succeeding records' new_state was repeated from the previous records(new_state) and if it is changed in the same date..
here record_id 1 has gone through the ff states: 0->1->2->1->3->4->3 in the same day.. state 1 was changed to state 2 then back to state 1 again (id 2 & 3 would not be displayed).. same with state 3 (id 5 & 6 would not be displayed)..
id | record_id| date_changed | old_state | new_state |
1 | 1 | 2009-01-01 | 0 | 1 |
2 | 1 | 2009-01-01 | 1 | 2 | not displayed
3 | 1 | 2009-01-01 | 2 | 1 | not displayed
4 | 1 | 2009-01-01 | 1 | 3 |
5 | 1 | 2009-01-01 | 3 | 4 | not displayed
6 | 1 | 2009-01-01 | 4 | 3 | not displayed
so the result would display only 2 records for record_id=1..
id | record_id| date_changed | old_state | new_state |
1 | 1 | 2009-01-01 | 0 | 1 |
4 | 1 | 2009-01-01 | 1 | 3 |
Here's the code for table creation and data:
IF OBJECT_ID('TempDB..#table','U') IS NOT NULL
DROP TABLE #table
CREATE TABLE #table
(
id INT identity primary key,
record_id INT,
date_changed DATETIME,
old_state INT,
new_state INT
)
INSERT INTO #table(record_id,date_changed,old_state,new_state)
SELECT 1,'2009-01-01',0,1 UNION ALL --displayed
SELECT 1,'2009-01-01',1,2 UNION ALL --not displayed
SELECT 1,'2009-01-01',2,1 UNION ALL --not displayed
SELECT 1,'2009-01-01',1,3 UNION ALL --displayed
SELECT 1,'2009-01-01',3,4 UNION ALL --not displayed
SELECT 1,'2009-01-01',4,3 --not displayed
INSERT INTO #table(record_id,date_changed,old_state,new_state)
SELECT 3,'2009-01-01',0,1 UNION ALL --displayed
SELECT 3,'2009-01-01',1,2 UNION ALL --not displayed
SELECT 3,'2009-01-01',2,3 UNION ALL --not displayed
SELECT 3,'2009-01-01',3,4 UNION ALL --not displayed
SELECT 3,'2009-01-01',4,1 --not displayed
SELECT * FROM #table
I would appreciate any help..
Thanks
For clarity regarding record_id=3.. Given this table:
id | record_id| date_changed | old_state | new_state |
7 | 3 | 2009-01-01 | 0 | 1 |
8 | 3 | 2009-01-01 | 1 | 2 | not displayed
9 | 3 | 2009-01-01 | 2 | 3 | not displayed
10 | 3 | 2009-01-01 | 3 | 4 | not displayed
11 | 3 | 2009-01-01 | 4 | 1 | not displayed
when running the query for record_id=3, the table result will be:
id | record_id| date_changed | old_state | new_state |
7 | 3 | 2009-01-01 | 0 | 1 |
Thanks!
UPDATE (12/2/2009):
Special scenario
id | record_id| date_changed | old_state | new_state |
1 | 4 | 2009-01-01 | 0 | 1 | displayed
2 | 4 | 2009-01-01 | 1 | 2 | displayed
3 | 4 | 2009-01-01 | 2 | 3 | not displayed
4 | 4 | 2009-01-01 | 3 | 2 | not displayed
5 | 4 | 2009-01-01 | 2 | 3 | displayed
6 | 4 | 2009-01-01 | 3 | 4 | not displayed
7 | 4 | 2009-01-01 | 4 | 3 | not displayed
where new_state 3 appears on id 3,5 and 7.. id 3 would not be displayed since it is between id 2 and id 4 which have the same new_state(3).. Then id 5 should be displayed since there is no existing new_state 3 yet..
code snippet:
IF OBJECT_ID('TempDB..#tablex','U') IS NOT NULL
DROP TABLE #tablex
CREATE TABLE #tablex
(
id INT identity primary key,
record_id INT,
date_changed DATETIME,
old_state INT,
new_state INT
)
INSERT INTO #tablex(record_id,date_changed,old_state,new_state)
SELECT 4,'2009-01-01',0,1 UNION ALL --displayed
SELECT 4,'2009-01-01',1,2 UNION ALL --displayed
SELECT 4,'2009-01-01',2,3 UNION ALL --not displayed
SELECT 4,'2009-01-01',3,2 UNION ALL --not displayed
SELECT 4,'2009-01-01',2,3 UNION ALL --displayed
SELECT 4,'2009-01-01',3,4 UNION ALL --not displayed
SELECT 4,'2009-01-01',4,3 --not displayed
I think the sequence in building the result is important..
Thanks!
SELECT A.*
/*
A.ID, A.old_state, a.new_state,
B.ID as [Next], b.old_state, b.new_state,
C.ID as [Prev], c.old_state, c.new_state
*/
FROM #table A LEFT JOIN
#table B ON A.ID = (B.ID - 1)
LEFT JOIN #table C ON (A.ID - 1) = C.ID
-- WHERE A.old_State <> B.new_State AND A.new_State <> C.old_State
WHERE A.record_id = 1
AND A.old_State <> COALESCE(B.new_State, -1)
AND A.new_State <> COALESCE(C.old_State, -1)
EDIT: I guess, what OP needs is that the remaining record should be selected except those where current record's old state is not the same as next record's new state (kind of an undo operation in records) and current record's new state should not be same as previous record's old state.
Following steps to get to the result
select all items that should not appear in the result.
left join these with the original table and select only those records that don't match a should not appear record.
.
;WITH cte_table (master_id, master_state, id, record_id, old_state, new_state, level) AS
(
SELECT id, old_state, id, record_id, old_state, new_state, 1
FROM #table
UNION ALL
SELECT master_id, master_state, #table.id, #table.record_id, #table.old_state, #table.new_state, level + 1
FROM cte_table
INNER JOIN #table ON cte_table.new_state = #table.old_state
AND cte_table.record_id = #table.record_id
AND cte_table.id < #table.id
AND cte_table.master_state < #table.old_state
)
SELECT master_id, t1.*, level
INTO #result
FROM #table t1
INNER JOIN (
SELECT master_id, min_child_id = MIN(id), level
FROM cte_table
GROUP BY master_id, level
) t2 ON t2.min_child_id = t1.id
SELECT t1.*
FROM #table t1
LEFT OUTER JOIN (
SELECT r1.id
FROM #result r1
INNER JOIN (
SELECT r1.master_id
FROM #result r1
INNER JOIN #result r2 ON r2.new_state = r1.old_state
AND r2.master_id = r1.master_id
WHERE r1.level = 1
) r2 ON r2.master_id = r1.master_id
) r1 ON r1.id = t1.id
WHERE r1.id IS NULL
AND t1.old_state < t1.new_state
ORDER BY 1, 2, 3

Resources