Pass Value To Derived Table - sql-server

Is there a way to pass a value to a derived table query?
In the derived table I want reference a value ([docSVsys].[sID]) from the outer query.
I get an error:
Msg 4104, Level 16, State 1, Line 7 The multi-part identifier
"docSVsys.sID" could not be bound.
Yes I know this query can be simplified to no loop.
Have a cursor that has to loop and trying to convert it so set.
select top 10 [docSVsys].[sID], [sI].[count]
from docSVsys
join
(
select count(*) as [count]
from docSVenum1 as [sIt]
where [sIt].[sID] = [docSVsys].[sID]
) as [sI]
on '1' = '1'
order by [docSVsys].[sID]
Cross apply seemed to do the trick.
And it is exactly 1/3 faster than the cursor version.
Real query using cross apply below.
SELECT [sO].[sID], [sI].[max], [sI].[avg], [sI].[stdev]
FROM docSVsys as [sO] with (nolock)
cross apply
(
select [sO].[sID], max(list.match) as 'max', avg(list.match) as 'avg', stdev(list.match) as 'stdev'
from
(
select #SampleSet.[sID], [match] = 200 * count(*) / CAST ( #SampleSetSummary.[count] + [sO].[textUniqueWordCount] as numeric(8,0) )
from #SampleSet with (nolock)
join FTSindexWordOnce as [match] with (nolock) -- this is current #sID
on match.wordID = #SampleSet.wordID
and [match].[sID] = [sO].[sID]
join #SampleSetSummary with (nolock) -- to get the word count from the sample set
on #SampleSetSummary.[sID] = #SampleSet.[sID]
group by #SampleSet.[sID], #SampleSetSummary.[count]
) as list
having max(list.match) > 60
) as [sI]
where [textUniqueWordCount] is not null and [textUniqueWordCount] > 4 and [sO].[sID] <= 10686
order by [sO].[sID]

You can do what you want with a CROSS APPLY rather than a JOIN:
select top 10 [docSVsys].[sID], [sI].[count]
from docSVsys
cross apply
(
select count(*) as [count]
from docSVenum1 as [sIt]
where [sIt].[sID] = [docSVsys].[sID]
) as [sI]
order by [docSVsys].[sID]

Add the ID to the derived table, and join on that:
select top 10 [docSVsys].[sID], [sI].[count]
from docSVsys
join
(
select [sIt].[sID], count(*) as [count]
from docSVenum1 as [sIt]
group by [sIt].[sID]
) as [sI]
on [sI].[sID] = [docSVsys].[sID]
order by [docSVsys].[sID]

Related

Postgresql - Update CTE result from CTE Output?

I need to update CTE o/p one of the column value (top 1 record) based on the latest timestamp & then return.
Query
WITH cte AS (
select
dt_zone.zone_name,
dt_material_status.mtstatus_name,
dt_historicalzone.visit_time_in
FROM ((public.dt_historicalzone
INNER JOIN dt_material_status
ON dt_historicalzone.mtstatus_id = dt_material_status.mtstatus_id)
INNER JOIN dt_zone ON dt_historicalzone.zone_id = dt_zone.zone_id)
WHERE material_id = 'ELS46885'
ORDER BY dt_historicalzone.zone_id DESC)
UPDATE cte SET cte.mtstatus_name = true WHERE SELECT * FROM cte LIMIT 1;
SELECT * FROM cte
You may try using an update join, with Postgres' syntax, including a CTE for the limit portion of the query:
WITH cte AS (
SELECT dh.mtstatus_id
FROM dt_historicalzone dh
INNER JOIN dt_zone dz
ON dh.zone_id = dz.zone_id
ORDER BY zone_id DESC
LIMIT 1
)
UPDATE dt_material_status d
SET mtstatus_name = true
FROM cte t
WHERE d.mtstatus_id = t.mtstatus_id AND
d.material_id = 'ELS46885';
when you update a CTE, the background table is getting updated. You can have only one statement below CTE. Post that CTE loses its scope. You can only go for UPDATE statement, post CTE. I have modified the CTE and updated the top 1 row.
The above statement is applicable for SQL Server. In Postgres, the CTE cannot be target of UPDATE statements. See the below error in Postgres.
Query Error: error: relation "cte" cannot be the target of a modifying
statement
WITH cte AS (
select top 1
dt_zone.zone_name,
dt_material_status.mtstatus_name,
dt_historicalzone.visit_time_in
FROM public.dt_historicalzone
INNER JOIN dt_material_status
ON dt_historicalzone.mtstatus_id = dt_material_status.mtstatus_id
INNER JOIN dt_zone ON dt_historicalzone.zone_id = dt_zone.zone_id
WHERE material_id = 'ELS46885'
ORDER BY dt_historicalzone.zone_id DESC)
UPDATE cte
SET mtstatus_name = true
I have tried with sample data for a CTE update. Below works fine in SQL Server.
create table #test(a int)
create table #test2(a int, b int)
insert into #test values (1)
insert into #test2 values (1,1)
;WITH CTE as
(
select top 1 t.a, t2.b
FROM #test as t
join #test2 as t2
on t.a = t2.a
order by t.a desc
)
update cte set b = 0
select * from #test2

SQL Server UNION ALL Merge Join (Concatenation) too slow

I have a select query which utilizes UNION ALL keyword on two tables with same structure (columns and primary key, they have different non-clustered indexes). These two tables contain 39 million rows, a million in one and a 38 million in the other. When running a query just on a table1 which has a million rows, it takes approximately 0.2 seconds, on table2 we have a different situation it takes from 0.5 to 1.2 seconds maximum, depending on the stress of DB.
In reality, for displaying I need to union those two tables, but the problem is that the union query takes a whopping 8 seconds to run. When taking a look at execution plan, the most heavy operation is Merge Join (Concatenation) with the cost of 91%, I am little bit concerned as the WHERE clause I'm running selects 51 entries from table1 and 0 entries from the table2 (larger table).
I can't get my head around it, I've been trying to find any solutions to my problem for last two days now and all I found was either UNION being used instead of UNION ALL or unnecessary clauses being in place like GROUP BY or LEFT/INNER JOINS. Query also does paging, using this commands ORDER BY [Id] DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;, all tests (on single tables and with UNION ALL) were performed with paging (OFFSET and FETCH NEXT) keywords in place.
If required, I can provide the table details and query details. It a simple select query with 2 INNER JOINS and 2 LEFT JOINS all of the joined tables contain really small amount of data (ranging from 50 entries to 20K entries).
Here's the query
SELECT *
FROM (SELECT tr.Id,
tr.Amount,
tr.TypeId,
t.Name AS [Type],
tr.Date,
tr.ExternalKey,
tr.ExternalDescription,
tr.GameId,
tr.GameProviderId,
gp.Name AS GameProvider,
u.Username,
u.Pincode,
gp.Name,
g.GameName,
u.OperatorId,
tr.BalanceBefore,
tr.BalanceAfter,
tr.UserId
FROM (
SELECT *
FROM dbo.ActiveTransactions at
WHERE ( 1 = 1 )
AND ( [Date] >= '2017-07-17 20:00:00' )
AND ( [TypeId] != 10 )
AND ( [UserId] = 29041 )
UNION ALL
SELECT *
FROM dbo.TransactionHistory th --WITH(INDEX(IX_TransactionHistory_DateType_UserId))
WHERE ( 1 = 1 )
AND ( [Date] >= '2017-07-17 20:00:00' )
AND ( [TypeId] != 10 )
AND ( [UserId] = 29041 )
) AS tr
INNER JOIN dbo.Users u ON tr.UserId = u.Id
LEFT JOIN dbo.GameProviders gp ON tr.GameProviderId = gp.Id
LEFT JOIN dbo.Games g ON tr.GameId = g.GameId AND tr.GameProviderId = g.ProviderId
INNER JOIN dbo.Types t ON tr.TypeId = t.Id ) AS t
ORDER BY [Id] DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;
This is an educated guess: without knowledge of indexes or execution plan on what the engine is actually getting caught up on.
Consider: Doing the union after the joins/filters instead of before: you have to repeat the joins but you may gain some index efficiency's lost in a unioned set processing.
In this case I created two CTE's and then unioned them.
as I'm unable to really test this, I may have some syntax errors.
WITH cte1 AS
(SELECT tr.Id,
tr.Amount,
tr.TypeId,
t.Name AS [Type],
tr.Date,
tr.ExternalKey,
tr.ExternalDescription,
tr.GameId,
tr.GameProviderId,
gp.Name AS GameProvider,
u.Username,
u.Pincode,
gp.Name,
g.GameName,
u.OperatorId,
tr.BalanceBefore,
tr.BalanceAfter,
tr.UserId
FROM (
SELECT *
FROM dbo.ActiveTransactions at
WHERE ( 1 = 1 )
AND ( [Date] >= '2017-07-17 20:00:00' )
AND ( [TypeId] != 10 )
AND ( [UserId] = 29041 )) AS tr
INNER JOIN dbo.Users u ON tr.UserId = u.Id
LEFT JOIN dbo.GameProviders gp ON tr.GameProviderId = gp.Id
LEFT JOIN dbo.Games g ON tr.GameId = g.GameId AND tr.GameProviderId = g.ProviderId
INNER JOIN dbo.Types t ON tr.TypeId = t.Id ) AS t),
CTE2 as (
SELECT tr.Id,
tr.Amount,
tr.TypeId,
t.Name AS [Type],
tr.Date,
tr.ExternalKey,
tr.ExternalDescription,
tr.GameId,
tr.GameProviderId,
gp.Name AS GameProvider,
u.Username,
u.Pincode,
gp.Name,
g.GameName,
u.OperatorId,
tr.BalanceBefore,
tr.BalanceAfter,
tr.UserId
FROM (SELECT *
FROM dbo.TransactionHistory th --WITH(INDEX(IX_TransactionHistory_DateType_UserId))
WHERE ( 1 = 1 )
AND ( [Date] >= '2017-07-17 20:00:00' )
AND ( [TypeId] != 10 )
AND ( [UserId] = 29041 ) as tr
INNER JOIN dbo.Users u ON tr.UserId = u.Id
LEFT JOIN dbo.GameProviders gp ON tr.GameProviderId = gp.Id
LEFT JOIN dbo.Games g ON tr.GameId = g.GameId AND tr.GameProviderId = g.ProviderId
INNER JOIN dbo.Types t ON tr.TypeId = t.Id) AS t
)
SELECT * from CTE1
UNION ALL
SELECT * from CTE2
ORDER BY [Id] DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;

TSQL syntax for updating with inner join

I'm trying to convert this mysql query to run on SQL but having some issues with the syntax on join. Basically what I want to do is set the DWH_HISTO to 1 on duplicate rows based on the DWH_DATE. So the older duplicates should be marked.
This is what I've tried
UPDATE MAG_L_D3.dbo.INSCRIPT
INNER JOIN
(SELECT MAX(DWH_DATE) as lastId, CODINS
FROM INSCRIPT
WHERE DWH_HISTO=0
GROUP BY CODINS
HAVING COUNT(*) > 1) duplic on duplic.CODINS = MAG_L_D3.dbo.INSCRIPT.CODINS
SET DWH_HISTO = 1
WHERE MAG_L_D3.dbo.INSCRIPT.DWH_DATE < duplic.lastId
I believe this is the syntax you are looking for:
;WITH CTE as
(
SELECT *, row_number() over (partition by CODINS order by DWH_DATE desc)rn
)
UPDATE CTE
SET DWH_HISTO = 1
WHERE
rn > 1
and DWH_HISTO=0
Update using inner join:
UPDATE I
SET DWH_HISTO = 1 FROM MAG_L_D3.dbo.INSCRIPT I
INNER JOIN
(SELECT MAX(DWH_DATE) as lastId, CODINS
FROM INSCRIPT
WHERE DWH_HISTO=0
GROUP BY CODINS
HAVING COUNT(*) > 1) duplic on duplic.CODINS = I.CODINS
WHERE I.DWH_DATE < duplic.lastId

How to join first two child records to parent?

I am trying to select the top 25 parent records and join to it the first two child records ordered by date. The parent record can have 0 to n children.
The end result would be something like:
P1, C1, C2
P2, C1, C2
...
P25, C1, C2
I have found an example using max date, but I am having trouble getting a specific row number
select top 25 *
from parentTable p
left join childTable c
on p.Key = c.Key
and c.dateColumn = (
select Max(c.dateColumn)
from c
where p.Key = c.Key
)
You should be able to get what you are looking for using ROW_NUMBER():
WITH c AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY [key] ORDER BY [date] asc) as rn
,[key]
,[date]
from child
)
SELECT top 25 p.[key], c1.[key], c1.[date], c2.[key], c2.[date]
FROM parent p
LEFT JOIN c c1
ON p.[key] = c1.[key]
AND c1.rn = 1
LEFT JOIN c c2
ON p.[key] = c2.[key]
AND c2.rn = 2
Check SQLFiddle for test data/results
You can use CTE.
;with cte as (
select top (25) *
from parentTable
)
select *
from cte p
left join childTable c
on p.[Key] = c.[Key];
First solution (because data set seems to be small) one solution is to use OUTER APPLY:
select top (25) ... columns ...
from parentTable p
outer apply (
select top (2) ... columns ...
from childTable c
where p.[Key] = c.[Key]
order by c.dateColumn desc -- asc ?
) a
-- Most of the times, when top filter is used order by clause should be also used
order by p.dateColumn desc -- asc ?
-- order by p.idColumn desc -- asc ?
Second solution (could be less eficient):
select top (25) ... columns ...
from parentTable p
left join (
select top (2) ... columns ..., ROW_NUMBER() over(partition by c.[Key] order by c.dateColumn desc) as rn -- asc ?
from childTable c
) a on p.[Key] = a.[Key] and a.rn < 3
-- Most of the times, when top filter is used order by clause should be also used
order by p.dateColumn desc -- asc ?
-- order by p.idColumn desc -- asc ?
Note: at least for the first solution one of following indices could help from the point of view of performance:
create index ix_name on dbo.childTable ([Key], [dateColumn])
--or
create index ix_name on dbo.childTable ([Key], [dateColumn])
include (... columns from select top(2) clause ...)

How do i get the next value and previous in a column

Suppose i have an ID column that has the values 1 , 5 , 7 .What SQL statement can i use to get the next value of in the column based on another.
Example : The next value after 1 is 5.
Example 2 : the value before 7 is 5
Without any window functions or CTEs:
select
t.id,
(select max(t1.id) from tbl t1 where t1.id < t.id) as previd,
(select min(t2.id) from tbl t2 where t2.id > t.id) as nextid
from tbl t
There are some great Windowed functions for working across records in later versions of SQL Server (2012+). But in 2008 these aren't available. Instead you could use an OUTER APPLY. This will allow you to filter a sub query using values from your main query.
Outer Apply Query Example
WITH SampleValues AS
(
/* This CTE creates some sample values.
*/
SELECT
r.n
FROM
(
VALUES
(1),
(5),
(7)
) AS r(n)
)
SELECT
o.n,
prev.Previous_n,
[next].Next_n
FROM
SampleValues AS o
OUTER APPLY
(
-- Find the previou value by looking for the TOP 1 before the current.
SELECT TOP 1
o.n AS Original_n,
p.n AS Previous_n
FROM
SampleValues AS p
WHERE
p.n < o.n
ORDER BY
p.n DESC
) AS prev
OUTER APPLY
(
-- Find the next value by looking for the TOP 1 after the current.
SELECT TOP 1
n.n AS Original_n,
n.n AS Next_n
FROM
SampleValues AS n
WHERE
n.n > o.n
ORDER BY
n.n ASC
)AS [next]
;

Resources