Change select with connect by prior from Oracle to SQL Server - sql-server

Hello I have this part of a view in an Oracle database and I must change it on Microsoft Sql Server.
with V_LOCHIERARHY_N
(nr, nivel, location, parent, systemid, siteid, orgid, count_a, count_wo, children)
AS
SELECT LEVEL, LPAD (' ', 2 * (LEVEL - 1)) || l.LOCATION nivel,
LOCATION, PARENT, systemid, siteid, orgid,
(SELECT COUNT (a.ancestor)
FROM locancestor a
WHERE a.LOCATION = l.LOCATION AND a.siteid = l.siteid),
NVL (COUNT (w.wonum), 0)
FROM maximo.workorder w
WHERE ( w.reportdate >
TO_TIMESTAMP ('2006-06-19 00:00:01',
'YYYY-MM-DD HH24:MI:SS.FF'
)
AND w.istask = 0
AND w.worktype <> 'P'
AND w.LOCATION = l.LOCATION
)
AND w.status <> 'CAN'),
l.children
FROM lochierarchy l
START WITH l.LOCATION = 'StartPoint'
CONNECT BY PRIOR l.LOCATION = l.PARENT AND l.siteid = 'SiteTest'
What I need from this script is to return all the children of a given entry (the description of the children which can be found in locations table).
I have a table with next columns:
Location Parent Systemid Children Siteid Origid Lochierarchyid
A001 StartPoint Primary 2 SiteTest X 106372
A002 A001 Primary 2 SiteTest X 105472
A003 A002 Primary 0 SiteTest X 98654
A004 A002 Primary 1 SiteTest X 875543
A004B A004 Primary 0 SiteTest X 443216
B005 StartPoint Primary 0 SiteTest X 544321
For example for given entry A001 will return
A002
A003
A004
A004B
B005
I have made this view below but I don't know how to integrate it with the first one. Also it doesn't return me the list in the corectly order
Parent
Children 1 of parent
Children a of children 1
children b of children 1
children 2 of parent
children a1 of children 2 and so on.
WITH testCTE AS
(
SELECT l.parent, l.location as child, l.location, l.lochierarchyid
FROM lochierarchy l
where location='SecondLocation' --and siteid='SiteTest'
UNION ALL
SELECT c.Parent, l.parent, l.location, l.lochierarchyid
FROM lochierarchy l
INNER JOIN testCTE c ON l.parent = c.location
)
SELECT *
FROM testCTE c
order BY c.parent,child asc
;
Can please someone help me? :)

Following the query proposed by mathguy, modified for MSSQL (2012)
with
inputs ( location, parent ) as (
select 'A001' , 'StartPoint' union all
select 'A002' , 'A001' union all
select 'A003' , 'A002' union all
select 'A004' , 'A002' union all
select 'A004B', 'A004' union all
select 'B005' , 'StartPoint'
),
r (lvl, location, ord ) as (
select 1, location, CAST(location AS VARCHAR(400))
from inputs
where parent = 'StartPoint'
union all
select r.lvl + 1, i.location, CAST(r.location + '/' + i.location AS VARCHAR(400))
from r join inputs i on r.location = i.parent
)
select REPLICATE(' ', 2 * (lvl-1)) + location as location
from r
order by ord
;
Ouput:
location
-------------------------------------------------------------------
A001
A002
A003
A004
A004B
B005

Here is how you can do this (in Oracle, the only flavor I know) using a recursive query. "The web" reports SQL Server implements recursive queries as well, and with the same syntax (I believe all of this is SQL Standard compliant, so that's not surprising). Give it a try.
Instead of creating a table, I put all the test data in the first CTE. When you try this solution, delete the CTE named inputs first, and use your actual table name in the rest of the query.
with
inputs ( location, parent ) as (
select 'A001' , 'Downstream' from dual union all
select 'A002' , 'A001' from dual union all
select 'A003' , 'A002' from dual union all
select 'A004' , 'A002' from dual union all
select 'A004B', 'A004' from dual union all
select 'B005' , 'Downstream' from dual
),
r ( lvl, location ) as (
select 1, location
from inputs
where parent = 'Downstream'
union all
select r.lvl + 1, i.location
from r join inputs i on r.location = i.parent
)
search depth first by lvl set ord
select lpad(' ', 2 * (lvl-1), ' ') || location as location
from r
order by ord
;
LOCATION
--------------------
A001
A002
A003
A004
A004B
B005
6 rows selected.
ADDED: It seems SQL Server doesn't have the search depth/breadth first clause for recursive CTE's (or perhaps the syntax is different). In any case, here is a primitive "manual" implementation of the same:
with ( ......... ),
r ( lvl, location, ord ) as (
select 1, location, location
from inputs
where parent = 'Downstream'
union all
select r.lvl + 1, i.location, r.location || '/' || i.location
from r join inputs i on r.location = i.parent
)
select lpad(' ', 2 * (lvl-1), ' ') || location as location
from r
order by ord
;

Related

T-SQL query to show all the past steps, active and future steps

I have 3 tables in SQL Server:
map_table: (workflow map path)
stepId step_name
----------------
1 A
2 B
3 C
4 D
5 E
history_table:
stepId timestamp author
----------------------------
1 9:00am John
2 9:20am Mary
current_stageTable:
Id currentStageId waitingFor
------------------------------------
12345 3 Kat
I would like to write a query to show the map with the workflow status. Like this result here:
step name time author
----------------------------
1 A 9:00am John
2 B 9:20am Mary
3 C waiting Kat
4 D
5 E
I tried left join
select
m.stepId, m.step_name, h.timestamp, h.author
from
map_table m
left join
history_table h on m.stepId = h.stepId
I thought it will list all the records from the map table, since I am using left join, but somehow it only shows 3 records which is from history table..
So I changed to
select
m.stepId, m.step_name, h.timestamp, h.author
from
map_table m
left join
history_table h on m.stepId = h.stepId
union
select
m.stepId, m.step_name, '' as timestamp, '' as author
from
map_table m
where
m.stageId not in (select stageId from history_table)
order by
m.stepId
Then it list the result almost as I expected, but how do I add the 3rd table in to show the current active stage?
Thank you very much for all your help!! Much appreciated.
Looks like it's what you asked:
with map_table as (
select * from (values (1,'A')
,(2,'B')
,(3,'C')
,(4,'D')
,(5,'E')) t(stepId, step_name)
)
, history_table as (
select * from (values
(1,'9:00am','John')
,(2,'9:20am','Mary')) t(stepId, timestamp, author)
)
, current_stapeTable as (
select * from (values (2345, 3, 'Kat')) t(Id, currentStageId, waitingFor)
)
select
m.stepId, m.step_name
, time = coalesce(h.timestamp, case when c.waitingFor is not null then 'waiting' end)
, author = coalesce(h.author, c.waitingFor)
from
map_table m
left join history_table h on m.stepId = h.stepId
left join current_stapeTable c on m.stepId = c.currentStageId
I think a union fits well with the data and avoids the coalescing the values on multiple joins.
with timeline as (
select stepId, "timestamp" as ts, author from history_table
union all
select currentStageId, 'waiting', waitingFor from current_stageTable
)
select step_id, step_name, "timestamp", author
from
map_table as m left outer join timeline as t
on t.stepId = m.stepId

Update latest record with data from an older record

I have a table with newspaper subscribers:
Subscribers:
==============
ID INT,
Status,
Address,
IndexAddress,
StartDate,
EndDate,
SubscriberID,
PaperID
IndexAddress is a reference to my internal Address table where I keep "correct" addresses (you woulnd't believe how many people don't know where they live). Address is the address supplied by the customer.
Each time a subscriber ends his subscription I save the data and when he renews his subscription I want to re-fetch the old IndexAddress from the old subscrption line in my table.
The data in the database can look like this:
1 1 MyLocalAddress 13455 20160101 20160501 100 5
8 1 MyLocalAddress 13455 20160820 20161201 100 5
14 1 MyLocalAddress 13455 20161228 20170107 100 5
18 0 MyLocalAddress NULL 20170109 NULL 100 5
So ID 1, has status 1, a local address, pointing to address 13455 in my internal system, started 160101 and ended 160501 with customer number 100 and paper number 5.
The last row, ID 18 has just arrived in the database, I want to make sure I automatically find the IndexAddress number so I don't have to match it by hand, but I also want to make absolutlely sure that I fetch the information from the row with ID 14 since the older information in the database MIGHT be wrong (in this case it isn't but it might).
Here is my SQL to fix this:
UPDATE s SET
Status = s2.Status,
IndexAddress = s2.IndexAddress
FROM dbo.Subscribers s
JOIN dbo.Subscribers s2 ON s2.SubscriberID = s.SubscriberID
WHERE 1 = 1
AND s.Status <> s2.Status
AND s2.Status = 1
AND s2.ID IN
(
SELECT
MAX(s3.ID)
FROM dbo.Subscribers s3
WHERE 1 = 1
AND s3.SubscriberID = s.SubscriberID
AND s3.PaperID = s.PaperID
AND s3.Status = 1
AND s3.ID <> s.ID
)
-- Make sure it's the same customer. Customer number is checked in
-- join above.
AND s.PaperID = s2.PaperID
AND s.Address = s2.Address
This works, but I wanted to know if the subquery approach was the best solution or is there a better approach?
I would like to deepen my understand of MS SQL and thus my questions.
I think your query is way over complicated:
with toupdate as (
select s.*,
lag(address) over (partition by subscriberid, paperid order by id) as prev_address,
lag(status) over (partition by subscriberid, paperid order by id) as prev_status
from dbo.Subscribers s
)
update toupdate
set address = prev_address,
status = prev_status
where address is null;
This is not the answer you're looking for but it's not really suitable for a comment. I don't really agree with the design of the tables as you have redundant data. You shouldn't have to repeat data for address and indexaddress in Subscribers or do updates like you are doing.
I would suggest a design something like the below that would avoid you having to do updates like the one you are doing. The below code is re-runnable, so you can run and modify if required to test it.
-- user level information with 1 row per user - address should be linked here
CREATE TABLE #user
(
id INT ,
name NVARCHAR(20) ,
indexAddress INT
)
-- all subscriptions - with calculated status compared to current date
CREATE TABLE #subscription
(
id INT ,
startDate DATETIME ,
endDate DATETIME ,
staus AS CASE WHEN endDate < GETDATE() THEN 1
ELSE 0
END
)
-- table to link users with their subscriptions
CREATE TABLE #userSubscription
(
userId INT ,
subscriptionId INT
)
INSERT INTO #user
( id, name, indexAddress )
VALUES ( 1, N'bob', 13455 ),
( 2, 'dave', 55332 )
INSERT INTO #subscription
( id, startDate, endDate )
VALUES ( 1, '20160101', '20160201' ),
( 8, '20160820', '20161201' ),
( 14, '20161228', '20170107' ),
( 18, '20170109', NULL ),
( 55, '20170101', NULL );
INSERT INTO #userSubscription
( userId, subscriptionId )
VALUES ( 1, 1 ) ,
( 1, 8 ) ,
( 1, 14 ) ,
( 1, 18 ) ,
( 2, 55 );
-- show active users
SELECT u.name ,
u.indexAddress ,
us.userId ,
us.subscriptionId ,
s.startDate ,
s.endDate ,
s.staus
FROM #user u
INNER JOIN #userSubscription us ON u.id = us.userId
INNER JOIN #subscription s ON s.id = us.subscriptionId
WHERE s.staus = 0 -- active
-- show inactive users
SELECT u.name ,
u.indexAddress ,
us.userId ,
us.subscriptionId ,
s.startDate ,
s.endDate ,
s.staus
FROM #user u
INNER JOIN #userSubscription us ON u.id = us.userId
INNER JOIN #subscription s ON s.id = us.subscriptionId
WHERE s.staus = 1 -- inactive
-- tidy up
DROP TABLE #subscription
DROP TABLE #user
DROP TABLE #userSubscription

Adding results from two queries

I'm using MS-SQL 2008 R2.
I have 2 Queries which are returning the required results.
But I need to add the two results from each queries to provide a final value [Enterprise Value]. I'm sure this is very straight forward but I'm going round in circles on this, have tried incorporating SUM which I think is the right approach?
Here is the full query as it currently stands:
declare #d1 datetime='2015-12-22'
(select
c.fs_perm_sec_id,
((c.p_price * s.p_com_shs_out)/1000) as [Enterprise Value]
from fp_v1.fp_basic_bd c
left join edm_v1.edm_security_entity_map e
on e.fs_perm_sec_id= c.fs_perm_sec_id
left join fp_v1.fp_basic_sho s
on s.fs_perm_sec_id = c.fs_perm_sec_id
and c.date=#d1
where s."date" =
(
select MAX(s2."date")
from fp_v1.fp_basic_sho s2
where s2.fs_perm_sec_id=c.fs_perm_sec_id
and s2."date" <= c."date"
)
and c."date"=#d1
and e.termination_date is null
and c.fs_perm_sec_id = 'GPHC8W-S-GB')
UNION ALL
select
ff.fs_perm_sec_id,
((FF_debt + ff_pfd_stk + ff_min_int_accum) - FF.ff_cash_st) as [Enterprise Value]
from ff_v2.ff_basic_af_v2 FF
where FF."date" =
( select MAX(FF2."date")
from ff_v2.ff_basic_af_v2 FF2
where FF2.fs_perm_sec_id=FF.fs_perm_sec_id
and FF.date <= FF2.date
)
and FF.fs_perm_sec_id =('GPHC8W-S-GB')
When inserting a "UNION ALL" between the two queries I get the following results:
fs_perm_sec_id Enterprise Value
GPHC8W-S-GB 9270.5204655
GPHC8W-S-GB 835
What I would like to achieve is a sum of the two values brought onto one row, i.e.:
fs_perm_sec_id Enterprise Value
GPHC8W-S-GB 10105.52
Thanks for your help.
Final SQL:
declare #d1 datetime='2015-12-23'
Select fs_perm_sec_id, SUM([Enterprise Value]) AS 'Enterprise Value'
from
(
(select
c.fs_perm_sec_id,
((c.p_price * s.p_com_shs_out)/1000) as [Enterprise Value]
from fp_v1.fp_basic_bd c
left join edm_v1.edm_security_entity_map e
on e.fs_perm_sec_id= c.fs_perm_sec_id
left join fp_v1.fp_basic_sho s
on s.fs_perm_sec_id = c.fs_perm_sec_id
and c.date=#d1
where s."date" =
(
select MAX(s2."date")
from fp_v1.fp_basic_sho s2
where s2.fs_perm_sec_id=c.fs_perm_sec_id
and s2."date" <= c."date"
)
and c."date"=#d1
and e.termination_date is null
and c.fs_perm_sec_id in ('FT9TC5-S-GB','GPHC8W-S-GB','R85KLC-S-US'))
UNION ALL
select
ff.fs_perm_sec_id,
((FF_debt + ff_pfd_stk + ff_min_int_accum) - FF.ff_cash_st) as [Enterprise Value]
from ff_v2.ff_basic_af_v2 FF
where FF."date" =
( select MAX(FF2."date")
from ff_v2.ff_basic_af_v2 FF2
where FF2.fs_perm_sec_id=FF.fs_perm_sec_id
and FF.date <= FF2.date
)
and FF.fs_perm_sec_id in ('FT9TC5-S-GB','GPHC8W-S-GB','R85KLC-S-US')) t
group by t.fs_perm_sec_id
just use the Derived Table and Group by
Select fs_perm_sec_id,
SUM(Enterprise Value) EnterpriseValue
from (**your whole code**)
GROUP BY fs_perm_sec_id
use group by
How to use group by with union in t-sql
SELECT id,sum(*)
FROM ( SELECT id,
time
FROM dbo.a
UNION
SELECT id,
time
FROM dbo.b
)
GROUP BY id
DECLARE
#d1 DATE = '20151222'
, #fs_perm_sec_id VARCHAR(100) = 'GPHC8W-S-GB'
SELECT #fs_perm_sec_id, SUM([Enterprise Value])
FROM (
SELECT [Enterprise Value]
FROM (
SELECT
c.fs_perm_sec_id
, (c.p_price * s.p_com_shs_out) / 1000 AS [Enterprise Value]
, RowNum = ROW_NUMBER() OVER (ORDER BY s.[date] DESC)
from fp_v1.fp_basic_bd c
join fp_v1.fp_basic_sho s on s.fs_perm_sec_id = c.fs_perm_sec_id
left join edm_v1.edm_security_entity_map e on e.fs_perm_sec_id= c.fs_perm_sec_id
where c.[date] = #d1
and e.termination_date is null
and c.fs_perm_sec_id = #fs_perm_sec_id
) t
WHERE t.RowNum = 1
UNION ALL
SELECT FF_debt + ff_pfd_stk + ff_min_int_accum - ff_cash_st
FROM (
SELECT
ff.fs_perm_sec_id
, FF_debt
, ff_pfd_stk
, ff_min_int_accum
, FF.ff_cash_st
, RowNum = ROW_NUMBER() OVER (ORDER BY FF.[date] DESC)
FROM ff_v2.ff_basic_af_v2 FF
WHERE FF.[date] =
AND FF.fs_perm_sec_id = #fs_perm_sec_id
) t
WHERE t.RowNum = 2
) t

SQL Server 2008 Flatten Rows

I need to flatten a record set that originally has 4 rows per user to be 1 row. I started using the PIVOT functionality but there are 2 fields that need to be flatten (and the pivot wasn't working as expected ).
The current data structure:
ContactEmail Scenario TRE Updated_On
-------------------------------------------------------------
email#email.com WTD 0.9785 2015-05-12 22:35:14.993
email#email.com MTD 0.9817 2015-05-12 22:35:57.780
email#email.com QTD 0.9542 2015-05-12 23:16:35.227
email#email.com YTD 0.9522 2015-05-12 23:39:56.533
The result should be:
ContactEmail WTD_TRE WTD_TRE_Updated MTD_TRE MTD_TRE_Updated QTD_TRE QTD_TRE_Updated YTD_TRE YTD_TRE_Updated
--------------------------------------------------------------------------------------------------------------------
email#email.com 0.9785 2015-05-12 0.9817 2015-05-12 0.9542 2015-05-12 0.9522 2015-05-12
If you're curious about the PIVOT I tried, here it is (the TRE_* were all null)
SELECT *
FROM (SELECT ContactEmail, Round((ISNULL(TRE, 0) * 100), 1) AS "TRE", Scenario, Updated_On
FROM [server].[db].[schema].[table]) AS TREData
PIVOT
(
SUM(TRE)
FOR Scenario
IN ([TRE_WTD],[TRE_MTD],[TRE_QTD],[TRE_YTD])
) AS PivotTable;
Create two pivot queries and join them...
WITH CurrentDataStructure as (
SELECT 'email#email.com' ContactEmail,
'WTD' Scenario,
0.9785 TRE,
'2015-05-12 22:35:14.993' Updated_On
UNION SELECT 'email#email.com', 'MTD', 0.9817, '2015-05-12 22:35:57.780'
UNION SELECT 'email#email.com', 'QTD', 0.9542, '2015-05-12 23:16:35.227'
UNION SELECT 'email#email.com', 'YTD', 0.9522, '2015-05-12 23:39:56.533'
), TREData as (
SELECT ContactEmail, Round((ISNULL(TRE, 0) * 100), 1) TRE, Scenario, Updated_On
FROM CurrentDataStructure
), TREs AS (
SELECT *
FROM (SELECT ContactEmail, Scenario, TRE FROM TREData) TREData
PIVOT
(
SUM(TRE)
FOR Scenario
IN ([WTD],[MTD],[QTD],[YTD])
) PivotTable
), TREsUpdated AS (
SELECT *
FROM (SELECT ContactEmail, Scenario, Updated_On FROM TREData) TREData
PIVOT
(
MAX(Updated_On)
FOR Scenario
IN ([WTD],[MTD],[QTD],[YTD])
) PivotTable
)
SELECT t.ContactEmail,
t.WTD WTD_TRE, u.WTD WTD_TRE_Updated,
t.MTD MTD_TRE, u.MTD MTD_TRE_Updated,
t.QTD QTD_TRE, u.QTD QTD_TRE_Updated,
t.YTD YTD_TRE, u.YTD YTD_TRE_Updated
FROM TREs as t
INNER JOIN
TREsUpdated as u
ON t.ContactEmail = u.ContactEmail
This is tested through and should run just like it is.

SQL Server 2008 - Conditional Range

I have a database that has two tables. These two tables are defined as:
Movie
-----
ID (int)
Title (nvchar)
MovieReview
-----------
ID (int)
MovieID (int)
StoryRating (decimal)
HumorRating (decimal)
ActingRating (decimal)
I have a stored procedure that allows the user to query movies based on other user's reviews. Currently, I have a temporary table that is populated with the following query:
SELECT
m.*,
(SELECT COUNT(ID) FROM MovieReivew r WHERE r.MovieID=m.ID) as 'TotalReviews',
(SELECT AVG((r.StoryRating + r.HumorRating + r.ActingRating) / 3)
FROM MovieReview r WHERE r.MovieID=m.ID) as 'AverageRating'
FROM
Movie m
In a later query in my procedure, I basically want to say:
SELECT
*
FROM
MyTempTable t
WHERE
t.AverageRating >= #lowestRating AND
t.AverageRating <= #highestRating
My problem is, sometimes AverageRating is zero. Because of this, I'm not sure what to do. How do I handle this scenario in SQL?
First, I'd used a derived table to calculate the statistics. This makes it easier to convert nulls to zero if you want (although if we're using an Inner Join as I am here, you couldn't need to use Coalesce on the TotalReviews).
Select m.*
, Coalesce(MovieStats.TotalReviews, 0) As TotalReviews
, Coalesce(MovieStats.AverageRating, 0) As AverageRating
From Movie As m
Join (
Select R1.MovieId
, Count(*) As TotalReviews
, AVG( (r.StoryRating + r.HumorRating + r.ActingRating) / 3 ) As AverageRating
From MovieReview As r1
Group By R1.MovieId
) As MovieStats
On MovieStats.MovieId = m.Id
Where MovieStats.AverageRating Between #LowestRating And #HighestRating
The only issue here is that by putting our filter in the Where clause, it means we must have MovieReview records with AverageRating values in given range.
Select m.*
, Coalesce(MovieStats.TotalReviews, 0) As TotalReviews
, Coalesce(MovieStats.AverageRating, 0) As AverageRating
From Movie As m
Left Join (
Select R1.MovieId
, Count(*) As TotalReviews
, AVG( (r.StoryRating + r.HumorRating + r.ActingRating) / 3 ) As AverageRating
From MovieReview As r1
Group By R1.MovieId
) As MovieStats
On MovieStats.MovieId = m.Id
And MovieStats.AverageRating Between #LowestRating And #HighestRating
This will return zero in those instances where there are no movie ratings or the movie ratings that do exist are outside the passed range.
Yet another possibility is to filter on values that have Reviews and force those to have average ratings in the passed range:
Select m.*
, Coalesce(MovieStats.TotalReviews, 0) As TotalReviews
, Coalesce(MovieStats.AverageRating, 0) As AverageRating
From Movie As m
Left Join (
Select R1.MovieId
, Count(*) As TotalReviews
, AVG( (r.StoryRating + r.HumorRating + r.ActingRating) / 3 ) As AverageRating
From MovieReview As r1
Group By R1.MovieId
) As MovieStats
On MovieStats.MovieId = m.Id
Where MovieStats.MovieId Is Null
Or ( MovieStats.AverageRating Between #LowestRating And #HighestRating )
I'm assuming you want to movies with zero reviews included in your result.
SELECT
*
FROM
MyTempTable t
WHERE
(t.AverageRating >= #lowestRating AND
t.AverageRating <= #highestRating)
OR t.TotalReviews = 0
It's safe to test TotalReviews, because it will never be NULL.

Resources