How to rewrite this query (PostgreSQL) in SQL Server? - sql

Few days ago I have asked a question about 1,2 and 3. degree connections. Question Link and #Snoopy gave an article link which can fix all my problems. Article Link
I have carefully examined this article but I was unable to use With Recursive query with SQL Server.
PostgreSQL Query:
SELECT a AS you,
b AS mightknow,
shared_connection,
CASE
WHEN (n1.feat1 = n2.feat1 AND n1.feat1 = n3.feat1) THEN 'feat1 in common'
WHEN (n1.feat2 = n2.feat2 AND n1.feat2 = n3.feat2) THEN 'feat2 in common'
ELSE 'nothing in common'
END AS reason
FROM (
WITH RECURSIVE transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
a || '.' || b || '.' AS path_string,
b AS direct_connection
FROM edges2
WHERE a = 1 -- set the starting node
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
tc.path_string || e.b || '.' AS path_string,
tc.direct_connection
FROM edges2 AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' || e.b || '.%'
AND tc.distance < 2
)
SELECT a,
b,
direct_connection AS shared_connection
FROM transitive_closure
WHERE distance = 2
) AS youmightknow
LEFT JOIN nodes AS n1 ON youmightknow.a = n1.id
LEFT JOIN nodes AS n2 ON youmightknow.b = n2.id
LEFT JOIN nodes AS n3 ON youmightknow.shared_connection = n3.id
WHERE (n1.feat1 = n2.feat1 AND n1.feat1 = n3.feat1)
OR (n1.feat2 = n2.feat2 AND n1.feat2 = n3.feat2);
or just
WITH RECURSIVE transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
a || '.' || b || '.' AS path_string
FROM edges
WHERE a = 1 -- source
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
tc.path_string || e.b || '.' AS path_string
FROM edges AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' || e.b || '.%'
)
SELECT * FROM transitive_closure
WHERE b=6 -- destination
ORDER BY a, b, distance;
As I said, I don't know how to write a recursive query with SQL Server using CTEs. Made a search and examined this page but still no luck. I couldn't run the query.

If someone interested, here is the answer;
I managed to convert the query in question to SQL by;
converting integer values to varchar(MAX). If you don't specify the length of varchar as MAX, you'll get "Types don't match between the anchor and the recursive part in column..."
I replaced || to +
I added ; to the beginning of query
Finally as #a_horse_with_no_name proposed I removed RECURSIVE from query.
Result;
;WITH transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
CAST(a as varchar(MAX)) + '.' + CAST(b as varchar(MAX)) + '.' AS path_string
FROM edges
WHERE a = 1 -- source
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
CAST(tc.path_string as varchar(MAX)) + CAST(e.b as varchar(MAX)) + '.' AS path_string
FROM edges AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' + CAST(e.b as varchar(MAX)) + '.%'
)
SELECT * FROM transitive_closure
WHERE b=6 -- destination
ORDER BY a, b, distance;

The recursive CTE should be the same on SQL Server (at least on a recent version, this was introduced with SQL Server 2005 if I'm not mistaken), just leave out the recursive keyword.
Note that SQL Server does not comply with the SQL standard and therefor you need to replace the || concatenation with +

Related

Optimize query using Concat method

I have a query which uses join, then group by caseId and then a concat-like function using STUFF.
SELECT distinct [CaseID], STUFF((SELECT ';' +space(1)+ A.[AssignedPathologist]+' ' FROM CTE1 A
WHERE A.[CaseID]=B.[CaseID] FOR XML PATH('')),1,1,'') As [AssignedPathologist]
From CTE1 B
Group By [CaseID]
The problem is that this query is super, super-slow and I tried to optimize it using CONCAT instead.
SELECT distinct A.[CaseID], [AssignedPathologist] = CASE A.AssignedPathologist = B.AssignedPathologist
WHEN 1 THEN A.AssignedPathologist
ELSE CONCAT(A.AssignedPathologist, ' ', B.AssignedPathologist)
END
FROM CTE1 A
INNER JOIN CTE1 B ON A.[CaseID]=B.[CaseID]
END
but it gives me syntax error here
[AssignedPathologist] = CASE A.AssignedPathologist = B.AssignedPathologist
which is logic because I used twice = here.
Is there any method to optimize my query using CONCAT or another methods ?
Thank you
I would try with this :
SELECT [CaseID],
STUFF( (SELECT CONCAT('; ', A.[AssignedPathologist])
FROM CTE1 A
WHERE A.[CaseID] = B.[CaseID]
FOR XML PATH('')
),1, 1, ''
) As [AssignedPathologist]
FROM (SELECT DISTINCT CaseID CTE1 B) B;
For newer versions you can use string_agg() :
SELECT CASEID, STRING_AGG(AssignedPathologist, '; ') AS AssignedPathologist
FROM CTE1 C1
GROUP BY CASEID;

Create view with with statement

How to create view with a with statement?
I'm getting on error on it:
WITH temp as (
select uu.email, u.logintime, u.region, p.id as panelid, p.panelname, p.numberofdownloads, dimensionType + ' (' + dimensionValue + ')' as filter
from stat_users u
left join stat_panels p
on u.id=p.sessionid
left join stat_filters f
on p.id=f.panelid
left join users uu
on uu.id=u.userid
where uu.Organization = 'name' AND
year(logintime) between 2015 and 2017
and panelname is not null
)
CREATE VIEW final as(
select aa.email, aa.logintime, aa.region, aa.panelname, aa.numberofdownloads as downloads, case when len(aa.filters) > 0 then left(aa.filters, len(aa.filters)-1) else '' end as filters
from (
Select distinct a.email, a.logintime, a.region, a.panelname, a.numberofdownloads,
(
Select b.filter + ', ' AS [text()]
From temp b
Where b.panelid=a.panelid
ORDER BY b.panelid
For XML PATH ('')
) filters
from temp a
) aa
)
I'm getting such error :
> Incorrect syntax near the keyword 'CREATE'. 'CREATE VIEW' must be the
> first statement in a query batch.
So, I need just to use Create view using select which based on WITH statement on Sql server 2014
Yes always the CREATE has to be the first statement in a query batch
CREATE VIEW vFinal AS
WITH Temp AS (
SELECT uu.email, u.logintime, u.region, p.id AS panelid, p.panelname, p.numberofdownloads, dimensionType + ' (' + dimensionValue + ')' AS Filter
FROM stat_users u
LEFT JOIN stat_panels p ON u.id=p.sessionid
LEFT JOIN stat_filters f ON p.id=f.panelid
LEFT JOIN users uu ON uu.id=u.userid
WHERE uu.Organization = 'name' AND
YEAR(logintime) BETWEEN 2015 AND 2017
AND panelname IS NOT NULL
)
SELECT aa.email, aa.logintime, aa.region, aa.panelname, aa.numberofdownloads AS downloads, CASE WHEN LEN(aa.filters) > 0 THEN LEFT(aa.filters, LEN(aa.filters)-1) else '' end as filters
FROM (
SELECT DISTINCT a.email, a.logintime, a.region, a.panelname, a.numberofdownloads,
(
SELECT b.filter + ', ' AS [text()]
FROM temp b
WHERE b.panelid=a.panelid
ORDER BY b.panelid
FOR XML PATH ('')
) filters
FROM temp a
) aa
GO
Syntax to create a view table using CTE
CREATE VIEW View_Name AS
WITH CTE_Name (Columns) AS (SELECT QUERY)
SELECT QUERY using the CTE Table
GO
The with clause is an optional prefix for select:
WITH query_name (column_name1, ...) AS
(SELECT ...)
SELECT ...
This is also true when with is used in a view:
CREATE VIEW ...
WITH ...
SELECT ...
;
See also: http://modern-sql.com/feature/with
CREATE or replace VIEW final as
select aa.email, aa.logintime, aa.region, aa.panelname, aa.numberofdownloads as downloads, case when len(aa.filters) > 0 then left(aa.filters, len(aa.filters)-1) else '' end as filters
from (
Select distinct a.email, a.logintime, a.region, a.panelname, a.numberofdownloads,
(
Select b.filter + ', ' AS [text()]
From temp b
Where b.panelid=a.panelid
ORDER BY b.panelid
For XML PATH ('')
) filters
from temp a )

SQL - Select A only if B doesn't exist

I have this SQL statement (modified, because the real query is huge):
select tblInfo.IDNum, tblAddress.PrimaryAddress
from tblInfo
join tblAddress
on tblInfo.Agent = tblAddress.Agent
where (some stuff)
And I get a table that looks roughly like this:
|| IDNum || PrimaryAddress ||
-----------------------------
|| 01234 || 1 ||
|| 23456 || 1 ||
|| abcde || 0 ||
|| abcde || 1 ||
|| zyxwv || 0 ||
I need a way to return all records that have a PrimaryAddress of 1, as well as all records that have a PrimaryAddress of 0 and don't have an IDNum already returning the PrimaryAddress of 1. i.e. In the above example, (abcde || 0) should be excluded because (abcde || 1) exists.
Use NOT EXISTS
SELECT tblInfo.IDNum, tblAddress.PrimaryAddress
FROM tblInfo
INNER JOIN tblAddress
ON tblInfo.Agent = tblAddress.Agent
WHERE tblAddress.PrimaryAddress = 1
OR ( tblAddress.PrimaryAddress = 0 AND NOT EXISTS
(
SELECT 1 FROM tblInfo t2 INNER JOIN tblAddress a2 ON t2.Agent = a2.Agent
WHERE t2.IDNum = tblInfo.IDNum AND a2.PrimaryAddress = 1
)
)
In this case, a simple GROUP BY should work for what you are trying to do. Effectively you are saying you want all IDNum values to appear once, with the PrimaryAddress value corresponding to the highest value (1 if it exists, 0 if it doesn't).
Assuming you need to preserve your original query because you're doing other work with it, you could use:
SELECT IDNum, MAX(PrimaryAddress) AS PrimaryAddress
FROM
(
select tblInfo.IDNum, tblAddress.PrimaryAddress
from tblInfo
join tblAddress
on tblInfo.Agent = tblAddress.Agent
where (some stuff)
)
GROUP BY IDNum
This should work in MS SQL Server and Oracle, not sure about other DBMSs. If the nested query doesn't work in the DBMS you're using, you should be able to populate a temporary table with the results of your first query, then perform the grouping against that table.
I hope this helps
select * from tblAddress mainTBL
where mainTBL.primaryaddress = (Case when EXISTS(select t1.IDNUM from tblAddress t1 where
mainTBL.IDNUM = t1.IDNUM AND t1.primaryAddress = 1) THEN 1 ELSE 0 END)
Check the SQL fiddle

Why does this NOT IN query work as intended, but not this NOT EXISTS query?

Working (NOT IN) retrieves 3 rows:
select DISTINCT d.* from Device d , Company c3
WHERE d.deviceid NOT IN
(
Select d1.deviceid from Device d1, Clone x1
WHERE d1.deviceid = x1.deviceID
AND
(
x1.XPath = 'hi'
OR x1.XPath = 'bye'
)
AND
(
EXISTS ( select * from (SELECT * FROM [dbo].[Split] ('T130SF0W2050', ',')) as s
WHERE x1.Value like '%' + s.items + '%' )
)
)
AND
d.companyid = c3.companyid and c3.companynumber in (SELECT * FROM [dbo].[Split] ('00223200', ','))
Not Working(not exists):
select DISTINCT d.* from Device d , Company c3
WHERE NOT EXISTS
(Select * from Device d1, Clone x1
WHERE d1.deviceid = x1.deviceID
AND
(
x1.XPath = 'hi'
OR x1.XPath = 'bye'
)
AND
(
EXISTS ( select * from (SELECT * FROM [dbo].[Split] ('T130SF0W2050', ',')) as s
WHERE x1.Value like '%' + s.items + '%' )
)
)
AND
d.companyid = c3.companyid and c3.companynumber in (SELECT * FROM [dbo].[Split] ('00223200', ','))
I'm unsure I'm using the exists syntax correct, what should I select from the subquery? I've tried a few different combinations. It won't run if I put WHERE d.deviceid NOT EXISTS
Solution (thanks to Nikola):
add AND d1.deviceid = d.deviceid inside the Exists subquery.
The difference is that the NOT IN query returns devices that match the company and don't match the inner query specification.
For the NOT EXIST query to work as written (where "work as written" refers to returning the same result as the top query), there can't be any devices that exist matching the inner query. If any devices match the inner query at all, the query won't return any results.

how to reference an alias in an oracle nested query?

I have a couple of nested queries like this:
(SELECT "impartidas"."idUsuarioProf"
FROM "impartidas"
WHERE "impartidas"."periodo" = "periodoPlanGrado"."periodo" and
"impartidas"."idMateria" = "materiasPlan"."idMateria") T,
(SELECT "usuarios"."apellidoPaterno" || ' , ' || "usuarios"."nombres"
FROM "usuarios"
WHERE "usuarios"."idUsuario" = 36) as "nomprofesor"
The first one outputs the teacher ID in a column named T.
What do I need to change in the second query, just so that instead of 36, it uses the value that was shown in column aliased T?
In short I need to perform the second query, based on the output ID value of the first query.
r In the absence of any context it's difficult to understand why you're taken such a convoluted approach. The obvious approach is just a straightforward join:
SELECT "impartidas"."idUsuarioProf"
, "usuarios"."apellidoPaterno" || ' , ' || "usuarios"."nombres" "nomprofesor"
FROM "impartidas"
, "periodoPlanGrado"
, "materiasPlan"
, "usuarios"
WHERE "impartidas"."periodo" = "periodoPlanGrado"."periodo"
and "impartidas"."idMateria" = "materiasPlan"."idMateria") T
and "usuarios"."idUsuario" = "impartidas"."idUsuarioProf"
/
But if you really need the inlining then you would need to do the joining externally, something like this:
select P."nomprofesor"
from
(SELECT "impartidas"."idUsuarioProf"
FROM "impartidas"
, "periodoPlanGrado"
, "materiasPlan"
WHERE "impartidas"."periodo" = "periodoPlanGrado"."periodo"
and "impartidas"."idMateria" = "materiasPlan"."idMateria") T,
(SELECT "usuarios"."apellidoPaterno" || ' , ' || "usuarios"."nombres" as "nomprofesor"
, "usuarios"."idUsuario"
FROM "usuarios" ) P
WHERE P."idUsuario" = T."idUsuarioProf"
Note that you need to include all the joining columns in the projection of each sub-query. As, you need to use an aliases to reference a derived column in the outer query.
What about this:
SELECT "usuarios"."apellidoPaterno" || ' , ' || "usuarios"."nombres" AS "nomprofesor"
FROM "usuarios"
WHERE "usuarios"."idUsuario" = (
SELECT "impartidas"."idUsuarioProf"
FROM "impartidas", "periodoPlanGrando", "materiasPlan"
WHERE "impartidas"."periodo" = "periodoPlanGrado"."periodo"
AND "impartidas"."idMateria" = "materiasPlan"."idMateria"
)
or maybe
SELECT "usuarios"."apellidoPaterno" || ' , ' || "usuarios"."nombres" AS "nomprofesor"
FROM "usuarios"
WHERE "usuarios"."idUsuario" IN (
SELECT "impartidas"."idUsuarioProf"
FROM "impartidas", "periodoPlanGrando", "materiasPlan"
WHERE "impartidas"."periodo" = "periodoPlanGrado"."periodo"
AND "impartidas"."idMateria" = "materiasPlan"."idMateria"
)
if multiple rows might be generated by the subquery (I do not know the schema and my Spanish is not very good (IS NULL) to understand what might be in the "impartidas" table).
For code maintenance and readability reasons I would write this:
SELECT u.apellidoPaterno || ' , ' || u.nombres AS nomprofesor
FROM usuarios u
WHERE u.idUsuario = (
SELECT i.idUsuarioProf
FROM impartidas i
INNER JOIN periodoPlanGrando p USING ( periodo )
INNER JOIN materiasPlan m USING ( idMateria )
-- WHERE (other condifitions)
)
or even this:
SELECT u.apellido_paterno || ' , ' || u.nombres AS nom_profesor
FROM usuarios u
WHERE u.id_usuario = (
SELECT i.id_usuario_prof
FROM impartidas i
INNER JOIN periodo_plan_grado p USING ( periodo )
INNER JOIN materias_plan m USING ( id_materia )
-- WHERE (other condifitions)
)
but this would require refractoring table and column names to be more Oracle identifier like.