Possibility of reusing nested select value in other nested selects - sql-server

Is it possible to create a reusable nested select statement in TSQL? Something like this?
Select(
Select1(reusable)
Select2(where Select1)
Select3(where Select1)
Select4(where Select1)
)
or do I need to rewrite the same select statement to get the value I need to use in other selects in every lower statement? I tried to use it as below but I get always errors.
(SELECT TOP (1) LOCATION FROM Address AS d
WHERE(RECID = trans.Address) ) as loc,
(SELECT TOP (1) CITY WHERE
(loc = other.recid)) as CITY

Related

Update table from same table

I have a data set in which I would like to update a column PREVACCEPTID.
The update is based on the contents of the same table, a sample data is shown below:
The column should be updated after a search to see if station has had previous acceptances and what was this?
If we SELECT all DISTINCT 'ACCEPTID' for station A we would get the below.
I want to use this DISTINCT ACCEPTID to populate 'PREVACCEPTID'.
So whereever I have an entry with for e.g. '142692', I would lookup the sub-table and check if there are exists any previous ACCEPTID s, if that is the case populate with the previous one, in this case '142691' (see after results table as they are populated)
I have tried a few things now, I am getting an error for the below:
UPDATE a
SET a.PREVACCEPTID = (CASE
WHEN COUNT(DISTINCT b.ACCEPTID) = 1
THEN b.ACCEPTID
WHEN COUNT(DISTINCT b.ACCEPTID) > 1
AND b.ACCEPTID <> MIN(a.ACCEPTID)
THEN b.ACCEPTID - 1
END)
FROM dbo.table a
RIGHT JOIN dbo.table b ON b.STATION = a.STATION
AND b.PERIOD = a.PERIOD
AND b.ACCEPTID = a.ACCEPTID
I get this error:
Msg 157, Level 15, State 1, Line 326
An aggregate may not appear in the set list of an UPDATE statement.
The end result is per below:
I think a cte would be better option, but i have never used one.
Thanks in advance.
If I interpret your question and subsequent comments correctly, I assume you want the previous AcceptID to be populated to be last AcceptID for a given set of rows sharing the same Station and Period. Last I assume would be defined by the time components (StackDate and QTime). And, in the case where there is only one row for a given Station and Period, you'd want the Previous AcceptID to be set to be the same as AcceptID for that row.
Under the above conditions, below is a query that will work. Note: Replace table 'Test' with your own table name.
UPDATE t SET PrevAcceptID =
ISNULL(
(SELECT TOP 1 AcceptID
FROM Test t2
WHERE t2.Station = t.Station AND t2.Period = t.Period AND t2.AcceptID < t.AcceptID ORDER BY StackDate DESC, QTime DESC),
AcceptID)
FROM Test AS t

Select IDs which belongs ONLY to the list passed as parameter

Let's start from data:
DECLARE #Avengers TABLE ([Hero] varchar(32), [Preference] varchar(32));
INSERT INTO #Avengers VALUES
('Captain_America','gingers'),('Captain_America','blondes'),
('Captain_America','brunettes'),('Hulk','gingers'),('Hulk','blondes'),
('Hawkeye','gingers'),('Hawkeye','brunettes'),('Iron_Man','blondes'),
('Iron_Man','brunettes'),('Thor','gingers'),('Nick_Fury','blondes');
Now I would like to pass a #Preferences as a list of [Preference] (either comma separated or single column table parameter) without knowing how many parameters I am going to get and based on this to select [Hero] who prefers exactly these #Preferences as provided in parameter (list), by that I mean if I am after 'blondes' and 'gingers' then I am after 'Hulk' only
(NOT 'Captain_America' who prefers 'blondes', 'gingers' and 'brunettes').
I would like to get something like:
SELECT [Hero]
FROM #Avengers
WHERE *IS_ASSIGNED_ONLY_TO_THE_LIST*([Preference]) = #Preference
Well, I think I overcomplicated my code, but it works.
SELECT a.Hero, COUNT(*), MIN(p.N)
FROM #Avengers a
LEFT JOIN ( SELECT *, COUNT(*) OVER() N
FROM #Preferences) p
ON a.Preference = p.Preference
GROUP BY a.Hero
HAVING COUNT(*) = MIN(p.N)
AND COUNT(*) = COUNT(p.Preference)
;
I'm using #Preferences as a table.

Is the data in a common table expression static?

I'm wondering if the data in a CTE is static (does it stay the same when changes are made to the original tables it was created from) - I think the answer is yes, but I want to make sure. Example:
DECLARE #TSCourseID as INT = 123456789;
WITH CTE as
(
SELECT
TSRegistrants.TSRegistrantID
,TSRegistrants.Name
,TSRegistrants.Email
,TSRegistrants.PhoneNumber
FROM TSRegCourseDetail
JOIN TSRegistrants
ON TSRegCourseDetail.TSRegistrantID = TSRegistrants.TSRegistrantID
WHERE TSRegCourseDetail.TSCourseID = #TSCourseID
AND TSRegistrants.Name in ('User List')
)
UPDATE TSRegCourseDetail
SET TSCourseID = 987654321
WHERE TSRegistrantID in (select TSRegistrantID from CTE)
1) Would this change the data in the CTE? This query would empty it if so; I'm hoping it doesn't
2) Also, would update/set from select TSRegistrantID from CTE work/be any better?
I am not a programmer, just got stuck with the hat for the time being >.<
Thanks!
The CTE is evaluated before the UPDATE takes place, so in the way that you mean, no the data doesn't change.
Also, a CTE can only be used with one select statement (or update or insert). If you want it to be available for multiple statements you need either a temp table or table variable instead.
You don't need to use a CTE for this at all:
DECLARE #TSCourseID AS INT = 123456789
UPDATE cd
SET cd.TSCourseID = 987654321
FROM TSCourseDetail cd
INNER JOIN TSRegistrants r ON cd.TSRegistrantID = r.TSRegistrantID
WHERE cd.TSRegistrantID = #TSCourseID
AND TSRegistrants.Name IN ( 'User List' )

Apply SQL Function to a Table Column Values

I have a Transact SQL function SimpleSplit, which splits a string according to the delimiter. I can use it as follows:
DECLARE #DelimitedString NVARCHAR(128)
SET #DelimitedString = 'Processor,RAM,SSD,Ethernet'
SELECT * FROM [dbo].[SimpleSplit](#DelimitedString, ',')
This results in:
Processor
RAM
SSD
Ethernet
As expected.
I now have a Table called PROD_TABLE with a column Descr. I would like to apply this function to each value in column Descr. I attempted the following and it does not work:
SELECT p.[ID], p.[Descr]
FROM [Amazon].[dbo].[PROD_TABLE] p
OUTER APPLY [dbo].[SimpleSplit](p.Descr, '-') d
In the output I only see the ID and the Descr Columns i.e. no results from the SimpleSplit function. However, if I attempt
SELECT *
FROM [Amazon].[dbo].[PROD_TABLE] p
OUTER APPLY [dbo].[SimpleSplit](p.Descr, '-') d
I see the results of the SimpleSplit function in the last column. Why does this query apply the function, but the previous query does not?
Answer
Thanks to mr.Rebands answer below, I realized that I needed to name the results. Hence * worked, but to explicitly name the columns I needed to do something like:
SELECT p.[ID], p.[Descr], d.[Data]
FROM [Amazon].[dbo].[PROD_TABLE] p
OUTER APPLY [dbo].[SimpleSplit](p.[Descr], '-') d
Your function returns a table - what is the column name of the SimpleSplit result table? You will have to include that column name in your select statement.
OUTER APPLY is applied but the results are not selected.

SQL Case statements, making sub selections on a condition?

I've come across a scenario where I need to return a complex set of calculated values at a crossover point from "legacy" to current.
To cut a long story short I have something like this ...
with someofit as
(
select id, col1, col2, col3 from table1
)
select someofit.*,
case when id < #lastLegacyId then
(select ... from table2 where something = id) as 'bla'
,(select ... from table2 where something = id) as 'foo'
,(select ... from table2 where something = id) as 'bar'
else
(select ... from table3 where something = id) as 'bla'
,(select ... from table3 where something = id) as 'foo'
,(select ... from table3 where something = id) as 'bar'
end
from someofit
No here lies the problem ...
I don't want to be constantly doing that case check for each sub selection but at the same time when that condition applies I need all of the selections within the relevant case block.
Is there a smarter way to do this?
if I was in a proper OO language I would use something like this ...
var common = GetCommonSuff()
foreach (object item in common)
{
if(item.id <= lastLegacyId)
{
AppendLegacyValuesTo(item);
}
else
{
AppendCurrentValuesTo(item);
}
}
I did initially try doing 2 complete selections with a union all but this doesn't work very well due to efficiency / number of rows to be evaluated.
The sub selections are looking for total row counts where some condition is met other than the id match on either table 2 or 3 but those tables may have millions of rows in them.
The cte is used for 2 reasons ...
firstly it pulls only the rows from table 1 i am interested in so straight away im only doing a fraction of the sub selections in each case.
secondly its returning the common stuff in a single lookup on table 1
Any ideas?
EDIT 1 :
Some context to the situation ...
I have a table called "imports" (table 1 above) this represents an import job where we take data from a file (csv or similar) and pull the records in to the db.
I then have a table called "steps" this represents the processing / cleaning rules we go through and each record contains a sproc name and a bunch of other stuff about the rule.
There is then a join table that represents the rule for a particular import "ImportSteps" (table 2 above - for current data), this contains a "rowsaffected" column and the import id
so for the current jobs my sql is quite simple ...
select 123 456
from imports
join importsteps
for the older legacy stuff however I have to look through table 3 ... table 3 is the holding table, it contains every record ever imported, each row has an import id and each row contains key values.
on the new data rowsaffected on table 2 for import id x where step id is y will return my value.
on the legacy data i have to count the rows in holding where col z = something
i need data on about 20 imports and this data is bound to a "datagrid" on my mvc web app (if that makes any difference)
the cte i use determines through some parameters the "current 20 im interested in" those params represent start and end record (ordered by import id).
My biggest issue is that holding table ... it's massive .. individual jobs have been known to contain 500k + records on their own and this table holds years of imported rows so i need my lookups on that table to be as fast as possible and as few as possible.
EDIT 2:
The actual solution (suedo code only) ...
-- declare and populate the subset to reduce reads on the big holding table
declare table #holding ( ... )
insert into #holding
select .. from holding
select
... common stuff from inner select in "from" below
... bunch of ...
case when id < #legacy then (select getNewValue(id, stepid))
else (select x from #holding where id = ID and ... ) end as 'bla'
from
(
select ROW_NUMBER() over (order by importid desc) as 'RowNum'
, ...
) as I
-- this bit handles the paging
where RowNum >= #StartIndex
and RowNum < #EndIndex
i'm still confident i can clean it up more but my original query that looked something like bills solution was about 45 seconds in execution time, this is about 7
I take it the subqueries must return a single scalar value, correct? This point is important because it is what ensures the LEFT JOINs will not multiply the result.
;with someofit as
(
select id, col1, col2, col3 from table1
)
select someofit.*,
bla = coalesce(t2.col1, t3.col1),
foo = coalesce(t2.col2, t3.col2),
bar = coalesce(t2.bar, t3.bar)
from someofit
left join table2 t2 on t2.something=someofit.id and somefit.id < #lastLegacyId
left join table3 t3 on t3.something=someofit.id and somefit.id >= #lastLegacyId
Beware that I have used id >= #lastLegacyId as the complement of the condition, by assuming that id is not nullable. If it is, you need an IsNull there, i.e. somefit.id >= isnull(#lastLegacyId,somefit.id).
Your edit to the question doesn't change the fact that this is an almost literal translation of the O-O syntax.
foreach (object item in common) --> "from someofit"
{
if(item.id <= lastLegacyId) --> the precondition to the t2 join
{
AppendLegacyValuesTo(item); --> putting t2.x as first argument of coalesce
}
else --> sql would normally join to both tables
--> hence we need an explicit complement
--> condition as an "else" clause
{
AppendCurrentValuesTo(item); --> putting t3.x as 2nd argument
--> tbh, the order doesn't matter since t2/t3
--> are mutually exclusive
}
}
function AppendCurrentValuesTo --> the correlation between t2/t3 to someofit.id
Now, if you have actually tried this and it doesn't solve your problem, I'd like to know where it broke.
Assuming you know that there are no conflicting ID's between the two tables, you can do something like this (DB2 syntax, because that's what I know, but it should be similar):
with combined_tables as (
select ... as id, ... as bla, ...as bar, ... as foo from table 2
union all
select ... as id, ... as bla, ...as bar, ... as foo from table 3
)
select someofit.*, combined_ids.bla, combined_ids.foo, combined_ids.bar
from someofit
join combined_tables on someofit.id = combined_tables.id
If you had cases like overlapping ids, you could handle that within the combined_tables() section

Resources