Scope of table when using with clause - sql

Below is a piece of my stored proc.
I am getting error as invalid object MyCount, Please let me know where am going wrong
;With MyCount AS
(
Select DispatchToRegionId ,FolderNo, row_number() OVER(ORDER BY FolderNo DESC) as Row
from tblTransite where FolderNo = #VAL group by DispatchToRegionId,FolderNo
)
select #cnt = COUNT(*) from MyCount
if #cnt = 0
begin
set #InvalidFolderNo = #VAL
print 'cnt -' + cast(#cnt as varchar(max) ) + 'invalid folder - ' + cast(#InvalidFolderNo as varchar(max) )
return
end
select #Region =( Select top 1 DispatchToRegionId from MyCount
order by Row desc )

MSDN clearly states the following about the scope of a Common Table Expression (CTE):
A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement
Once you have run the first select query, you can no longer use the CTE for your next one. You may want to consider storing the data in a temporary table or table variable if you want to access it in multiple queries.

MyCount is available only for the first query after it. Try to select all you need in this one query:
;With MyCount AS
(
Select DispatchToRegionId ,FolderNo, row_number() OVER(ORDER BY FolderNo DESC) as Row
from tblTransite where FolderNo = #VAL group by DispatchToRegionId,FolderNo
)
SELECT
#cnt = (select COUNT(*) from MyCount),
#Region =( Select top 1 DispatchToRegionId from MyCount
order by Row desc )
if #cnt = 0
begin
set #InvalidFolderNo = #VAL
print 'cnt -' + cast(#cnt as varchar(max) ) + 'invalid folder - ' + cast(#InvalidFolderNo as varchar(max) )
return
end

Related

SQL return values if row count > X

DECLARE #sql_string varchar(7000)
set #sql_string = (select top 1 statement from queries where name = 'report name')
EXECUTE (#sql_string)
#sql_string is holding another SQL statement. This query works for me. It returns all the values from the query from the statement on the queries table. From this, I need to figure out how to only return the results IF the number of rows returned exceeds a threshold (for my particular case, 25). Else return nothing. I can't quite figure out how to get this conditional statement to work.
Much appreciated for any direction on this.
If all the queries return the same columns, you could simply store the data in a temporary table or table variable and then use logic such as:
select t.*
from #t t
where (select count(*) from #t) > 25;
An alternative is to try constructing a new query from the existing query. I don't recommend trying to parse the existing string, if you can avoid that. Assuming that the query does not use CTEs or have an ORDER BY clause, for instance, something like this should work:
set #sql = '
with q as (
' + #sql + '
)
select q.*
from q
where (select count(*) from q) > 25
';
That did the trick #Gordon. Here was my final:
DECLARE #report_name varchar(100)
DECLARE #sql_string varchar(7000)
DECLARE #sql varchar(7000)
DECLARE #days int
set #report_name = 'Complex Pass Failed within 1 day'
set #days = 5
set #sql_string = (select top 1 statement from queries where name = #report_name )
set #sql = 'with q as (' + #sql_string + ') select q.* from q where (select count(*) from q) > ' + convert(varchar(100), #days)
EXECUTE (#sql)
Worked with 2 nuances.
The SQL returned could not include an end ";" charicter
The statement cannot include an "order by" statement

SQL Server 2008 R2 : Conversion failed when converting date and/or time from character string

I'm getting the following error
Conversion failed when converting date and/or time from character string
I can't get it to run properly please help me, here is my code (don't shame my loop i just need it to work).
The thing is i have a closed list with contracts IDs and a table with different dates and dates called #PERIODO it contains datetime type.
WHILE (SELECT TOP 1 FECHA1 FROM #PERIODO) > 0
BEGIN
SET #MES_ANTERIOR = (SELECT TOP 1 FECHA1 FROM #PERIODO ORDER BY FECHA1 ASC)
SET #MES_EN_CURSO = (SELECT TOP 1 B.FECHA1 FROM (SELECT TOP 2 A.FECHA1 FROM #PERIODO A ORDER BY FECHA1 ASC ) B ORDER BY B.FECHA1 DESC)
SET #MES_3 = (SELECT TOP 1 FECHA3 FROM #PERIODO ORDER BY FECHA1 ASC)
SET #MES_4 = (SELECT TOP 1 B.FECHA3 FROM (SELECT TOP 2 A.FECHA3 FROM #PERIODO A ORDER BY FECHA3 ASC ) B ORDER BY B.FECHA3 DESC)
SET #MES_ANT = LEFT(CONVERT(VARCHAR(8),#MES_ANTERIOR,112),8) -- SELECT #MES_ANT
SET #MES_CURR = LEFT(CONVERT(VARCHAR(8),#MES_EN_CURSO,112),8) -- SELECT #MES_CURR
EXEC('IF OBJECT_ID(''WORK.DBO.OPERACIONES_ABIF_'+#MES_CURR+''', ''U'') IS NOT NULL
DROP TABLE WORK.DBO.OPERACIONES_ABIF_'+#MES_CURR+' ')
SET #SQL1 = 'SELECT DISTINCT
A.*
INTO WORK.DBO.OPERACIONES_ABIF_'+#MES_CURR+'
FROM BDGESTION.DBO.BASE_RIESGOS_PROD_ALTAIR_'+#MES_CURR+' A WHERE '
SET #SQL2 = ' A.NUM_OPERACION IN (
SELECT
B.NUM_CONTRATO
FROM (
SELECT NUM_CONTRATO
FROM
( SELECT * FROM #OPER WHERE FECHA_INICIO IS NOT NULL) H
WHERE H.FECHA_INICIO < CAST(CONVERT(VARCHAR(12),'+#MES_EN_CURSO+',23) AS VARCHAR )
AND H.FECHA_INICIO >= CAST(CONVERT(VARCHAR(12),'+#MES_ANTERIOR+',23) AS VARCHAR )
) B
WHERE
B.NUM_CONTRATO IS NOT NULL ) '
EXEC (#SQL1 + #SQL2)
DELETE FROM #PERIODO
WHERE FECHA1 = #MES_ANTERIOR
END
The problem would appear to be these lines:
WHERE H.FECHA_INICIO < CAST(CONVERT(VARCHAR(12), ' + #MES_EN_CURSO + ', 23) AS VARCHAR)
Presumably, you want the cast() outside the aggregation:
WHERE H.FECHA_INICIO < ' + CONVERT(VARCHAR(12), #MES_EN_CURSO + ', 23) + '
However, I don't recommend this as a solution. The correct solution is to pass the value in as a parameter. Your query is too complex (and messy) for me to attempt that. You should look at the documentation for sp_executesql on how to properly construct dynamic SQL with parameters.
I think you should print #SQL2 as you go through the loop so you can see the incorrect value.
It would be helpful to show how you declare #MES_EN_CURSO and declare #MES_ANTERIOR.

Selecting data from table where sum of values in a column equal to the value in another column

Sample data:
create table #temp (id int, qty int, checkvalue int)
insert into #temp values (1,1,3)
insert into #temp values (2,2,3)
insert into #temp values (3,1,3)
insert into #temp values (4,1,3)
According to data above, I would like to show exact number of lines from top to bottom where sum(qty) = checkvalue. Note that checkvalue is same for all the records all the time. Regarding the sample data above, the desired output is:
Id Qty checkValue
1 1 3
2 2 3
Because 1+2=3 and no more data is needed to show. If checkvalue was 4, we would show the third record: Id:3 Qty:1 checkValue:4 as well.
This is the code I am handling this problem. The code is working very well.
declare #checkValue int = (select top 1 checkvalue from #temp);
declare #counter int = 0, #sumValue int = 0;
while #sumValue < #checkValue
begin
set #counter = #counter + 1;
set #sumValue = #sumValue + (
select t.qty from
(
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
id,qty,checkvalue
FROM #temp
) AS foo
WHERE rownumber = #counter
) t
)
end
declare #sql nvarchar(255) = 'select top '+cast(#counter as varchar(5))+' * from #temp'
EXECUTE sp_executesql #sql, N'#counter int', #counter = #counter;
However, I am not sure if this is the best way to deal with it and wonder if there is a better approach. There are many professionals here and I'd like to hear from them about what they think about my approach and how we can improve it. Any advice would be appreciated!
Try this:
select id, qty, checkvalue from (
select t1.*,
sum(t1.qty) over (partition by t2.id) [sum]
from #temp [t1] join #temp [t2] on t1.id <= t2.id
) a where checkvalue = [sum]
Smart self-join is all you need :)
For SQL Server 2012, and onwards, you can easily achieve this using ROWS BETWEEN in your OVER clause and the use of a CTE:
WITH Running AS(
SELECT *,
SUM(qty) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty
FROM #temp t)
SELECT id, qty, checkvalue
FROM Running
WHERE RunningQty <= checkvalue;
One basic improvement is to try & reduce the no. of iterations. You're incrementing by 1, but if you repurpose the logic behind binary searching, you'd get something close to this:
DECLARE #RoughAverage int = 1 -- Some arbitrary value. The closer it is to the real average, the faster things should be.
DECLARE #CheckValue int = (SELECT TOP 1 checkvalue FROM #temp)
DECLARE #Sum int = 0
WHILE 1 = 1 -- Refer to BREAK below.
BEGIN
SELECT TOP (#RoughAverage) #Sum = SUM(qty) OVER(ORDER BY id)
FROM #temp
ORDER BY id
IF #Sum = #CheckValue
BREAK -- Indicating you reached your objective.
ELSE
SET #RoughAverage = #CheckValue - #Sum -- Most likely incomplete like this.
END
For SQL 2008 you can use recursive cte. Top 1 with ties limits result with first combination. Remove it to see all combinations
with cte as (
select
*, rn = row_number() over (order by id)
from
#temp
)
, rcte as (
select
i = id, id, qty, sumV = qty, checkvalue, rn
from
cte
union all
select
a.id, b.id, b.qty, a.sumV + b.qty, a.checkvalue, b.rn
from
rcte a
join cte b on a.rn + 1 = b.rn
where
a.sumV < b.checkvalue
)
select
top 1 with ties id, qty, checkvalue
from (
select
*, needed = max(case when sumV = checkvalue then 1 else 0 end) over (partition by i)
from
rcte
) t
where
needed = 1
order by dense_rank() over (order by i)

T-SQL Pivot Row to Column

I am using SQL Server 2012 and have a table that has the following columns:
ID, Date, CustomFieldName, CustomFieldValue
The CustomFieldName column has 100 values (I know how stupid this sounds) but for the sake of simplicity lets say they are CustomField1, CustomField2, CustomField3
I would like to create a pivot where the out put looks like
ID, Date, CustomField1, CustomField2, CustomField3 where the Max date of CustomFieldVaue's is aggregated.
I have failed horribly in this, but have some progress (though my max isnt right and getting a lot of wrong data)
Any help would be appreciated!
SELECT [date],[id], [CustomField1], [CustomField2], [CustomField3]
from
(
SELECT [date], [id], [CustomFieldValue], [CustomFieldName],
row_number() over(partition by [CustomFieldName] order by [CustomFieldValue]) rn
from CustomTable
) as st
pivot
(
max([CustomFieldValue])
FOR [CustomFieldName] in ([CustomField1], CustomField2, [CustomField3])
) as pivottable
order by [id]
Hope I got it right, you want to pivot the rows (COlumnName1,2,...etc) as columns, so I've made a little script that's ready to run.
I recommend CTE's when it comes to pivoting, makes it easier, if you want to see the whole structure of the query just do a select #xSqlString
set nocount on;
create table
#testTable
(
ID int identity(1,1),
[Date] datetime default getdate(),
CustomFieldName nvarchar(50),
CustomFieldValue date
);
declare
#i int = 0,
#xSqlStringPivot nvarchar(max) = '',
#xSqlString nvarchar(max) = '';
while(#i<=100)
begin
set
#xSqlStringPivot += concat('CustomFieldName',cast(#i as nvarchar(50)),char(13), case when #i<100 then ', ' else '' end);
insert into #testTable
(
CustomFieldName,
CustomFieldValue
)
values
(
concat('CustomFieldName', cast(#i as nvarchar(50))),
dateAdd(day,-#i,getdate())
);
set
#i += 1;
end;
select * from
#testTable
set
#xSqlString =
(
'with ctePiv as
(
select
t.CustomFieldName,
t.CustomFieldValue
from
#testTable t
)
select
*
from
ctePiv
pivot
(
max(customFieldValue) for customFieldName in
(
'+ #xSqlStringPivot +'
)
)p'
);
exec sp_executeSQL #xSqlString
drop table #testTable;
Edit 1
I am referencing the custom table on the while block, basically I'm iterating 100 times to populate the table with 100 rows. This is just to simulate your case.
while(#i<=100)
begin
set
#xSqlStringPivot += concat('CustomFieldName',cast(#i as nvarchar(50)),char(13), case when #i<100 then ', ' else '' end);
insert into #testTable
(
CustomFieldName,
CustomFieldValue
)
values
(
concat('CustomFieldName', cast(#i as nvarchar(50))),
dateAdd(day,-#i,getdate())
);
set
#i += 1;
end;
#xSqlStringPivot is just a small trick to make a list of elements (CustomFieldName0, CustomFieldName1, etc) and to concatenate it to a dynamic SQL string, notice that I'm doing this in the while block, I just concatenate 'CustomField' with the current iteration number and with a carry feed (space).

Select Columns based on value of another variable?

Suppose I have this code:
DECLARE #IncludeDuplicateAddressIDs tinyint
-- Value of #IncludeDuplicateAddressIDs set here ! --
IF(#IncludeDuplicateAddressIDs = 1)
SELECT AddressIds FROM RTS.ADDRESSES
ELSE
SELECT DISTINCT(AddressIds) FROM RTS.ADDRESSES
Can I combine the two SELECT statements into one ? That is, using just one SELECT, I show either all AddressIDs, or just one of each ?
You can do it like this:
SELECT AddressIds FROM RTS.ADDRESSES WHERE #IncludeDuplicateAddressIDs = 1
UNION ALL
SELECT DISTINCT(AddressIds) FROM RTS.ADDRESSES WHERE #IncludeDuplicateAddressIDs <> 1
Since the WHERE clauses are mutually exclusive, only one of the UNION-ed queries would return some rows; the other query will return nothing.
If the table contains a unique column then you can use this option (assuming the unique column's name to be Id):
DECLARE #IncludeDuplicateAddressIDs tinyint = 1
SELECT AddressIds
FROM RTS.ADDRESSES
GROUP BY CASE WHEN #IncludeDuplicateAddressIDs = 1
THEN Id ELSE AddressIds END, AddressIds
Demo on SQLFiddle
You could include the number of duplicate addresses.
SELECT AddressIDs, count(*) as cnt
FROM RTS.ADDRESSES
GROUP BY AdressIDs
Try this one -
DECLARE #IncludeDuplicateAddressIDs TINYINT
SELECT #IncludeDuplicateAddressIDs = 0
SELECT t.AddressIds
FROM (
SELECT
AddressIds
, cnt = ROW_NUMBER() OVER (PARTITION BY AddressIds ORDER BY AddressIds)
FROM RTS.ADDRESSES
) t
WHERE #IncludeDuplicateAddressIDs = 1
OR (#IncludeDuplicateAddressIDs != 1 AND cnt = 1)
In this query, the UNION or DISTINCT construction is not used. Therefore, SQL can generate a more efficient query plan.
You can use sp_executesql dynamic sql to form your query as a string (and set as many columns as you want) and then execute it:
DECLARE #IncludeDuplicateAddressIDs tinyint
DECLARE #Columns varchar(500);
IF(#IncludeDuplicateAddressIDs = 1) SET #Columns='AddressIds'
ELSE SET #Columns='DISTINCT(AddressIds)'
EXECUTE sp_executesql N'SELECT ' + #Columns + 'AddressIds FROM RTS.ADDRESSES';