How to make view from this query or CTE? - tsql

Here is the code I'm trying to make a view with:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME( stsd.TRM_CDE + stsd.YR_CDE) as termyear
FROM STUD_TERM_SUM_DIV stsd
WHERE stsd.TRANSACTION_STS IN ('C','H','R','P') AND stsd.YR_CDE IN ('1415','1516','1617','1718') AND stsd.TRM_CDE IN ('FA','LF','SP','LS','SU','LU') and stsd.DIV_CDE = 'GR'
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');`

Transact-SQL (Microsoft SQL Server) does not allow variable declarations inside a view definition.
Ditch the variable declarations and the #cols =.
Add CREATE VIEW myviewname (mycolname) AS before the SELECT
We could assign an alias (column name) to teh expression returned by the outer query. That would allow us to omit the column name list in the CREATE VIEW.
CREATE VIEW myviewname (foo)
AS
SELECT STUFF(
...
) AS foo

Related

The name "xxx" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables

I want to separate a table's columns into two set (1st set = bottom 50%, 2nd set = top 50%, there is a reason why I am not using a median formula in this case and I know that there will be a case when the count([ORDINAL_POSITION]) will be an odd number, then I won't get accurate result.) to achieve this I am trying to use INFORMATION_SCHEMA.COLUMNS, but I can't figure it out why I got the following error message:
The name "sometable" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables. Column names are not permitted.
DECLARE #table2 NVARCHAR(MAX)
DECLARE #table_op_mid INT
SET #table2 = 'sometable'
SELECT #table_op_mid = 'SELECT ROUND(MAX([ORDINAL_POSITION])/2,0) AS OP FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '+#table2+''
PRINT (table_op_mid)
EXEC (#table_op_mid)
1st problem is that #table_op_mid is declared as INT instead of VARCHAR
2nd problem is that #table2 need extra quotes when used in TABLE_NAME comparision
3rd problem is that table_op_mid is missing # symbol, should be PRINT(#table_op_mid)
DECLARE #table2 NVARCHAR(MAX)
DECLARE #table_op_mid NVARCHAR(MAX)
SET #table2 = 'sometable'
SELECT #table_op_mid = 'SELECT ROUND(MAX([ORDINAL_POSITION])/2,0) AS OP FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '''+#table2+''''
PRINT (#table_op_mid)
EXEC (#table_op_mid)
EDIT
After your comment.. It is the same problem as before.. #SQL_columnnull_part_2 should be VARCHAR instead of INT
declare #db2 varchar(max) = 'MyDb'
declare #table2 varchar(max) = 'sometable'
declare #SQL_columnnull_part_2 varchar(max) = ''
Also, your new query will not work because STRING_AGG doesn't add last separator, so you should move the comparision term in the 1st parameter and keep in separator only the ';'
SELECT #SQL_columnnull_part_2 = STRING_AGG(
'UPDATE ' + #db2 + '.[dbo].' + #table2 + ' WITH (TABLOCK) SET ' + QUOTENAME(COLUMN_NAME,'['']') + ' = NULL WHERE ' + QUOTENAME(COLUMN_NAME,'['']') + ' = ''''',
'; '
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND [ORDINAL_POSITION] > #table_op_mid

Why does setting a varible like this make a difference?

I have a scalar value function that returns a VarChar(MAX) In my stored procedure I do this
declare #p_emailAddr varchar(MAX) = (select db.dbo.GetEmails(10))
If I do print #p_emailAddr it shows me it was populated with the correct information but the rest of the code doesn't work correctly using it. (I have no clue why, it doesn't make sense!)
Now if I change it like this
declare #p_emailAddr varchar(MAX) = 'test#email.com;'
The rest of my code works perfect as it should!
What is the difference between the two methods of setting #p_emailAddr that is breaking it?
This is get emails code
ALTER FUNCTION [dbo].[GetEmails](#p_SubID int)
RETURNS varchar(max)
AS
BEGIN
DECLARE #p_Emails varchar(max)
SELECT #p_Emails = COALESCE(#p_Emails + ';', '') + E.EmailAddress
FROM db.dbo.UserEmailAddr E JOIN
db.dbo.EmailSubscriptionUsers S on e.ClockNumber = s.Clock AND S.SubID = #p_SubID
SET #p_Emails = #p_Emails + ';'
RETURN #p_Emails
END
What's coming back from GetEmails(10)? varchar(max) is a string value and is expecting a single value. you could have a table variable or if dbo.getemails(10) is a table just join it where you're expecting to use #p_emailaddr
best
select *
from table1 t1
join dbo.GetEmails(10) e
on e.email = t1.email
alternative
create table #GetEmails (emails varchar(max))
insert into #GetEmails values ('email#test.com'), ('test#email.com')
declare #p_emailAddr table (emails varchar(max))
insert into #p_emailAddr(emails)
select *
from #GetEmails
select *
from #p_emailAddr

comma separated value in sql statement from a variable

I am getting comma separated value like this in a variable (let say variable name #listobj)
'abc' , 'xyz'
but when I am using below statement it is not giving me the correct result
SELECT * FROM someTable
Where column1 IN (#listobj)
but abc is present in the table.
Where I am doing it wrong?
create a function that split the string to
CREATE FUNCTION dbo.Split(#String varchar(8000), #Delimiter char(1))
returns #temptable TABLE (items varchar(8000))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end
then make call to the function SELECT * FROM someTable
Where column1 IN (dbo.Split(#listobj))
enter link description here
SQLFiddle demo
select * from someTable
where ','+LTRIM(RTRIM(#listobj))+',' LIKE '%,'+LTRIM(RTRIM(column1))+',%'
A classic question and the answer is no, you cannot use a parameter in this way. There are several workarounds though
One of which is to parse the value inside the stored procedure and dynamically generate sql inside the procedure to be execute later. However, this is not a good practice.
Refer to this question
How to pass a comma separated list to a stored procedure?
and also some good discussion on it here
http://social.msdn.microsoft.com/Forums/en-US/sqlintegrationservices/thread/1ccdd39e-8d58-45b2-9c21-5c4dbd857f95/

T-SQL passing a table name dynamically inside WITH statement

I have the following code in T-SQL that reads table names from a cursor.
But I have problem with the scoping table name variable inside the WITH statement.
I can run this code when I explicitly set dbo.#sys_name to a synonym name like dbo.mysysnonym but when I put it as variable name like dbo.#syn_name it does not work.
-- drop duplicates records from synonyms
DECLARE #syn_name varchar(50)
DECLARE s_cursor CURSOR FOR
SELECT name
FROM sys.synonyms
WHERE base_object_name LIKE 'xyz%'
OPEN s_cursor;
FETCH NEXT FROM s_cursor INTO #syn_name;
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM s_cursor INTO #syn_name;
WITH dedupTable AS
(
SELECT
sys_id,
row_number() OVER (PARTITION BY sys_id ORDER BY sys_id) AS nr
FROM
dbo.#syn_name
)
DELETE FROM dedupTable
WHERE nr > 1
END;
CLOSE s_cursor
DEALLOCATE s_cursor
As far as I know, you cannot use variables as table names, so dbo.#syn_name will not work in a FROM clause. Instead, you will have to use Dynamic SQL.
Something like:
...
FETCH NEXT FROM s_cursor INTO #syn_name;
DECLARE #sql nvarchar(4000)
SET #sql = N'
WITH dedupTable
AS (
SELECT sys_id, row_number()
OVER ( PARTITION BY sys_id ORDER BY sys_id ) AS nr
FROM dbo.' + #syn_name + '
)
DELETE FROM dedupTable
WHERE nr > 1'
EXEC sp_executesql #sql

ASP - SQL injection protection in the SELECT clause

After getting great help in securing against SQL injection from classic ASP protection against SQL injection, I've encountered a major issue which cannot be solved using parameterized queries.
name = Trim(Request.QueryString("name"))
flds = Trim(Request.QueryString("flds"))
sql = "set rowcount 0 select " & flds & " from [TABLE] where Name = '" & name & "'"
From what I understand, a parameterized query will protect against SQL injection in the WHERE clause (in this case, the name field.
flds is a comma-separated list of parameters that the users wants returned. As it is obvious, it is very vulnerable to SQL injection.
One idea I have to secure my code is to have a statically generated dict of valid fields, split the flds string by ",", verify each one of the values against the dict, and construct the SQL query that will consist of all the fields that are present in the dict.
It seems to me that although this method will work for security, it will require me to modify the static list at every change in the database (however rare those are).
Are there better/proper ways of securing this code against SQL injection attacks?
Create a split function in SQL Server (there are better ones for newer versions, but this is what you get in SQL Server 2000):
CREATE FUNCTION dbo.SplitStrings
(
#List NVARCHAR(4000),
#Delimiter CHAR(1)
)
RETURNS #Items TABLE
(
Item NVARCHAR(4000)
)
AS
BEGIN
DECLARE
#Item VARCHAR(12),
#Pos INT;
WHILE LEN(#List)>0
BEGIN
SET #Pos = CHARINDEX(#Delimiter, #List);
IF #Pos = 0
SET #Pos = LEN(#List)+1;
SET #Item = LEFT(#List, #Pos-1);
INSERT #Items SELECT LTRIM(RTRIM(#Item));
SET #List = SUBSTRING(#List, #Pos + LEN(#Delimiter), LEN(#List));
IF LEN(#List) = 0 BREAK;
END
RETURN;
END
GO
Then create a stored procedure:
CREATE PROCEDURE dbo.RunScaryQuery
#columns NVARCHAR(4000),
#table NVARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #collist NVARCHAR(4000), #sql NVARCHAR(4000);
SELECT #collist = COALESCE(#collist + ',', '') + c.name
FROM syscolumns AS c
INNER JOIN dbo.SplitStrings(#columns, ',') AS s
ON s.Item = c.name
WHERE c.id = OBJECT_ID(#table);
SELECT #sql = 'SELECT ' + #collist + ' FROM ' + #table
-- where ...
;
EXEC sp_executesql #sql;
END
GO
Now call that stored procedure from ASP with a properly parameterized command object.
This will ensure that your SQL query is generated only using column names that actually exist in the table. (Any nonsense will be ignored.)
This presumes that you will get at least one valid column name in the list.
I'm at home, no db to test but this should do it
Basically, get all the fields from the db that fit the where, get the requested fields in an array and compare the two lists, putting out only the requested fields.
name = Trim(Request.QueryString("name"))
flds = split(Trim(Request.QueryString("flds")),",")
sql = "set rowcount 0 select * from [TABLE] where Name = '" & name & "'"
set oRst = oConn.execute(sql)
on error resume next
do while not oRst.eof
result = ""
separator = ""
for each field in flds
for each requested_field in flds
if uCase(field.name) = uCase(trim(requested_field)) then
result = result & separator & field.value
separator = ","
end if
next
next
response.write result & "<br>"
oRst.movenext
loop
hm... so I'm going with another solution.
I first have an SQL query that return all the valid fields
select
tcol.name
from
sysObjects tobj
join syscolumns tcol on tobj.id = tcol.id
where
tobj.xtype = 'U'
and tobj.name = '[TABLE]'
and then I validate every element as suggested by #peter. All the validated parameters are then used to build the query string, and name is passed as a parameter in the second query.
This seems to minimize the overhead and the strain on the database.
Have a look at http://www.userfriendlythinking.com/Blog/BlogDetail.asp?p1=7013&p2=119&p7=3001
which shows usage of parameterized queries.