I am trying to search through a column in one table (Asset_Database_Current) with the details from a column in another table (DNI_Names). I've written a loop to go through each DNI_Name, and then a SELECT CONTAINS statement to (hopefully) compare DNI_Name against the relevant column in Asset_Database_Current.
I've set up a FullText Catalog and have indexed the Asset_Database_Current table to be searchable.
This is the code I have:
DECLARE
#Asset varchar(8)
, #software_Name VARCHAR(64)
, #dniSW VARCHAR(64)
, #DNI VARCHAR(64)
CREATE TABLE #temp_naughtyChildren (naughtyAsset CHAR(8), naughtySW VARCHAR(64))
-- ^ where the Naughty People eventually get put
DECLARE #dni_cur CURSOR FOR
SELECT DNI_Names.DNI_Name
FROM DNI_Names
OPEN #dni_cur
FETCH NEXT FROM #dni_cur
INTO #DNI
WHILE ##FETCH_STATUS = 0
BEGIN -- we go through DNI_Names, selecting each instance of DNI_Name one at a time...
INSERT INTO #temp_naughtyChildren (naughtyAsset, naughtySW)
SELECT Asset_Number, Software_Name
FROM Asset_Database_Current
WHERE CONTAINS (Asset_Database_Current.Software_Name, #DNI)
FETCH NEXT FROM #dni_cur
INTO #DNI
END
DEALLOCATE #dni_cur
-- #temp_NaughtyChildren is now full of all the Asset Numbers and swName's of people who
-- have been very, very naughty. The table can now be used as required (emailed, sent to
-- another d/b, etc.).
SELECT * from #temp_NaughtyChildren ORDER BY naughtyAsset
DROP TABLE #temp_naughtyChildren
END
GO
The above code completes successfully, but when I try and execute the query I get the following error twice:
Msg 7630, Level 15, State 3, Procedure DNIChecker, Line 44
Syntax error near 'Manager' in the full-text search condition 'App Manager'.
My boss has also executed the query (he has full permissions on the database) and he gets the exact same error.
Any suggestions you can give me will be very gratefully received (I haven't done SQL since 1998, I've realised that when they teach you SQL at Uni they don't actually teach you much SQL, and I have spent the last 2 1/2 weeks trawling various sites working on this. Only yesterday did I finally stumble across an article that mentioned there was a wizard to set up full-text search!!).
Thanks, M
CONTAINS require qoutes around the search condition if you use a full phrase, otherwise you should use NEAR, AND, OR between the single words.
SET #DNI = '"' + #DNI + '"';
INSERT INTO #temp_naughtyChildren (naughtyAsset, naughtySW)
SELECT Asset_Number, Software_Name
FROM Asset_Database_Current
WHERE CONTAINS (Asset_Database_Current.Software_Name, #DNI)
Related
I am completely new to sql and teradata(6weeks). and have been given an assignment, which had little instruction.
(Create a stored procedure that allows a user to select a bat’s manufacturer and (optionally) serial number using a stored procedure. The output should display all of the players who use the bat’s manufacturer. If the serial number is also provided, only display the players who use that bat’s manufacturer and serial number. Make sure you use a CREATE PROCEDURE call and insert this procedure into the existing database. ) this is a database through teradata that has been duplicated into my own database to be edited. This is what I got so far and it keeps returning two errors. I'd love help with a solution and best possible recommendation for learning sql quickly and efficiently. I appreciate the help in advance. and i'm sure this is the ugliest code you've seen, I aplogize. :-D
CREATE PROCEDURE batman(manuf varchar(20), bat_type varchar(4)=null)
DYNAMIC RESULT SETS 2
BEGIN
DECLARE c CURSOR FOR
SELECT playernum
FROM affiliation, bats
WHERE manuf = :manuf;
declare serial cursor for
select playernum
from affiliation
where bat_type=NULL or bat_type=:bat_type
if bat_type=NULL then open c
else open serial;
end if;
END;
The default of a parameter is always NULL, no need to declare that.
You can't compare NULLs using =, must be is null instead.
And there are some missing semicolons...
CREATE PROCEDURE batman(manuf VARCHAR(20), bat_type VARCHAR(4))
DYNAMIC RESULT SETS 1
BEGIN
DECLARE c CURSOR FOR
SELECT playernum
FROM affiliation, bats
WHERE manuf = :manuf;
DECLARE serial CURSOR FOR
SELECT playernum
FROM affiliation
WHERE bat_type IS NULL OR bat_type=:bat_type;
IF bat_type IS NULL THEN OPEN c;
ELSE OPEN serial;
end if;
END;
I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more
If I try to execute the following code, I get the errors
Msg 207, Level 16, State 1, Line 3 Invalid column name 'Another'. Msg
207, Level 16, State 1, Line 4 Invalid column name 'Another'.
even though the predicate for both IF statements always evaluates to false.
CREATE TABLE #Foo (Bar INT)
GO
IF (1=0)
BEGIN
SELECT Another FROM #Foo
END
GO
IF (1=0)
BEGIN
ALTER TABLE #Foo ADD Another INT
SELECT Another FROM #Foo
END
GO
DROP TABLE #Foo
This is probably over-simplified for the sake of the example; in reality what I need to do is select the values from a column, but only if the column exists. If it doesn't exist, I don't care about it. In the problem that drove me to ask this question, my predicate was along the lines of EXISTS (SELECT * FROM sys.columns WHERE object_id = #ID AND name = #Name). Is there a way to achieve this without resorting to my arch-enemy Dynamic SQL? I understand that my SQL must always be well-formed (i.e. conform to grammar) - even within a block that's never executed - but I'm flabbergasted that I'm also being forced to make it semantically correct too!
EDIT:
Though I'm not sure the code below adds much to the code above, it's a further example of the problem. In this scenario, I only want to set the value of Definitely (which definitely exists as a column) with the value from Maybe (which maybe exists as a column) if Maybe exists.
IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('dbo.TableName', 'U') AND name = 'Maybe')
BEGIN
UPDATE dbo.TableName SET Definitely = Maybe
END
SQL Server doesn't execute line by line. It isn't procedural like .net or Java code. So there is no "non-executed block"
The batch is compiled in one go. At this point, the column doesn't exist but it knows the table will be. Table does not have a column called "Another". Fail.
Exactly as expected.
Now, what is the real problem you are trying to solve?
Some options:
2 tables or one table with both columns
use Stored procedures to decouple scope
not use temp tables (maybe not needed; it could be your procedural thinking...)
dynamic SQL (from Mitch's deleted answer)
Edit, after comment;
Why not hide schema changes behind a view, rather than changing all code to work with columns that may/may not be there?
You can use EXEC to handle it. It's not really dynamic SQL if the code never actually changes.
For example:
CREATE TABLE dbo.Test (definitely INT NOT NULL)
INSERT INTO dbo.Test (definitely) VALUES (1), (2), (3)
IF EXISTS (SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Test', 'U') AND
name = 'Maybe')
BEGIN
EXEC('UPDATE dbo.Test SET definitely = maybe')
END
SELECT * FROM dbo.Test
ALTER TABLE dbo.Test ADD maybe INT NOT NULL DEFAULT 999
IF EXISTS (SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Test', 'U') AND
name = 'Maybe')
BEGIN
EXEC('UPDATE dbo.Test SET definitely = maybe')
END
SELECT * FROM dbo.Test
DROP TABLE dbo.Test
You can also try Martin Smith's "Workaround" using a non-existing table to get "deferred name resolution" for columns.
I had the same issue.
We are creating a script for all changes for years and this is the first time that we have this issue.
I've tried all your answers and didn't find the issue.
In my case it was because of temporary table within the script that I'm using also within a stored procedure, although every sentence has go.
I've found that if I'm adding if exists with drop to the temporary table after the script is using the temporary table, it is working correctly.
Best regards,
Chen
Derived from the answer by #gbn.
What i did to solve the issue was to use 'GO' between the ALTER query and the query that uses the column added by ALTER. This will make the 2 queries to be run as separate batches thereby ensuring your 'Another' column is there before the SELECT query.
I'm playing with some code from an article written by Peter Brawley found here on page 6 of the pdf. I'm trying to figure out how to automate it so that the result of the procedure is automatically placed in the select query. Right now what I am doing is calling the procedure, exporting the result into a text file, going to the text file manually (point click with mouse), copying the result and pasting it into a select statement. I haven't been able to figure out how to either insert the select statement into the procedure, or put the procedure into a table in my database or variable that I can call from the select statement. Any ideas?
Here is the sample code from Peter Brawley, that I've been trying to automate:
use database;
DROP PROCEDURE IF EXISTS writesumpivot;
DELIMITER |
CREATE PROCEDURE writesumpivot(
db CHAR(64), tbl CHAR(64), pivotcol CHAR(64), sumcol CHAR(64)
)
BEGIN
DECLARE datadelim CHAR(1) DEFAULT '"';
DECLARE comma CHAR(1) DEFAULT ',';
DECLARE singlequote CHAR(1) DEFAULT CHAR(39);
SET #sqlmode = (SELECT ##sql_mode);
SET ##sql_mode='';
SET #pivotstr = CONCAT( 'SELECT DISTINCT CONCAT(', singlequote,
',SUM(IF(', pivotcol, ' = ', datadelim, singlequote,
comma, pivotcol, comma, singlequote, datadelim,
comma, sumcol, ',0)) AS `',
singlequote, comma, pivotcol, comma, singlequote, '`',
singlequote, ') AS sumpivotarg FROM ', db, '.', tbl,
' WHERE ', pivotcol, ' IS NOT NULL' );
-- UNCOMMENT TO SEE THET MIDLEVEL SQL:
-- SELECT #pivotstr;
PREPARE stmt FROM #pivotstr;
EXECUTE stmt;
drop prepare stmt;
SET ##sql_mode=#sqlmode;
END
|
DELIMITER ;
call writesumpivot('database', 'table', 'pivotcol','sumcol');
Then the Select statement is as follows:
SELECT
infoField
[results of the call]
FROM
database.table
GROUP BY infoField;
Assuming I've ran the call, exported the results, copied them and pasted them into the select statement, my personal results of the call in the SELECT query would look something like this:
SELECT
infoField
,SUM(IF(pivotcol = "Yellow",sumcol,0)) AS `Yellow`
,SUM(IF(pivotcol = "Red",sumcol,0)) AS `Red`
,SUM(IF(pivotcol = "Purple",sumcol,0)) AS `Purple`
,SUM(IF(pivotcol = "Orange",sumcol,0)) AS `Orange`
,SUM(IF(pivotcol = "Green",sumcol,0)) AS `Green`
,SUM(IF(pivotcol = "Blue",sumcol,0)) AS `Blue`
,SUM(IF(pivotcol = "White",sumcol,0)) AS `White`
FROM database.table
GROUP BY infoField;
Running the above select statement gives me the pivot table that I need. I'm tryig to figure out how to incorporate this into a website, which is why it needs to be automated.
I tried inserting a create table, and then reference the table, but didn't get desired results.
Edited the last section of the PROCEDURE as follows:
--SELECT #pivotstr;
DROP TABLE IF EXISTS temp2;
CREATE TABLE IF NOT EXISTS temp2(sumpivotarg varchar(8000));
PREPARE stmt FROM #pivotstr;
...
changed call and select as follows:
call writesumpivot('database','table','pivotcol','sumcol');
insert into temp2(sumpivotarg) values(#pivotstr);
SELECT
table.infoField, temp2.sumpivotarg
FROM table, temp2
GROUP BY infoField
Results from this were the generic code rather than summing the contents of the cells in the database. it looks something like this:
infoField | sumpivotarg <-- Col Headings
123 | SELECT DISTINCT CONCAT('Sum(if(pivotcol=",pivotcol",sumcol,0)) AS'pivotcol,'')..
124 | SELECT DISTINCT CONCAT('Sum(if(pivotcol=",pivotcol",sumcol,0)) AS'pivotcol,'')..
125 | select DISTINCT CONCAT('Sum(if(pivotcol=",pivotcol",sumcol,0)) AS'pivotcol,'')..
I do not mean any disrespect towards mySQL, but this whole writing to a temp table solution for passing tablular data between stored procedures is suboptimal and dangerous (in real world transaction processing). I truly hope that the mySQL team will build in some enterprise level stored procedure functionality. Also, mySQL Functions not being able to return tables is a distinct disadvantage.
I have been slowly moving process over to Linux and mySQL from MSSQL. The short comings of mySQL in the procedure and function department is forcing some major kludgey type rewrites (ala temp tables and globals, etc).
I have been writing SPs for about 20 years (Sybase before SQL Server) and feel strongly that using dynamic SQL does not take advantage of the server side database. Many folks try to implement a Data layer at the client level, but the sever is better suited to this task. It is a natural division of functionality and data. Also, simultaneously running multiple precompiled calls at the server is quite a bit more optimal than repeated calls to the server, for the same processes.
Come on mySQL team, I am keeping my fingers crossed....
You could create a temp table in your DB. Use SQL insert to insert data into temp table as the result of the stored procedure execution. Afterwards you could use that temp table inside your select statement.
Here's an answer that shows how to do that:
Use result set of mysql stored procedure in another stored procedure
Just to mention a similar question:
MySQL How to INSERT INTO temp table FROM Stored Procedure
Brief history:
I'm writing a stored procedure to support a legacy reporting system (using SQL Server Reporting Services 2000) on a legacy web application.
In keeping with the original implementation style, each report has a dedicated stored procedure in the database that performs all the querying necessary to return a "final" dataset that can be rendered simply by the report server.
Due to the business requirements of this report, the returned dataset has an unknown number of columns (it depends on the user who executes the report, but may have 4-30 columns).
Throughout the stored procedure, I keep a column UserID to track the user's ID to perform additional querying. At the end, however, I do something like this:
UPDATE #result
SET Name = ppl.LastName + ', ' + ppl.FirstName
FROM #result r
LEFT JOIN Users u ON u.id = r.userID
LEFT JOIN People ppl ON ppl.id = u.PersonID
ALTER TABLE #result
DROP COLUMN [UserID]
SELECT * FROM #result r ORDER BY Name
Effectively I set the Name varchar column (that was previously left NULL while I was performing some pivot logic) to the desired name format in plain text.
When finished, I want to drop the UserID column as the report user shouldn't see this.
Finally, the data set returned has one column for the username, and an arbitrary number of INT columns with performance totals. For this reason, I can't simply exclude the UserID column since SQL doesn't support "SELECT * EXCEPT [UserID]" or the like.
With this known (any style pointers are appreciated but not central to this problem), here's the problem:
When I execute this stored procedure, I get an execution error:
Invalid column name 'userID'.
However, if I comment out my DROP COLUMN statement and retain the UserID, the stored procedure performs correctly.
What's going on? It certainly looks like the statements are executing out of order and it's dropping the column before I can use it to set the name strings!
[Edit 1]
I defined UserID previously (the whole stored procedure is about 200 lies of mostly irrelevant logic, so I'll paste snippets:
CREATE TABLE #result ([Name] NVARCHAR(256), [UserID] INT);
Case sensitivity isn't the problem but did point me to the right line - there was one place in which I had userID instead of UserID. Now that I fixed the case, the error message complains about UserID.
My "broken" stored procedure also works properly in SQL Server 2008 - this is either a 2000 bug or I'm severely misunderstanding how SQL Server used to work.
Thanks everyone for chiming in!
For anyone searching this in the future, I've added an extremely crude workaround to be 2000-compatible until we update our production version:
DECLARE #workaroundTableName NVARCHAR(256), #workaroundQuery NVARCHAR(2000)
SET #workaroundQuery = 'SELECT [Name]';
DECLARE cur_workaround CURSOR FOR
SELECT COLUMN_NAME FROM [tempdb].INFORMATION_SCHEMA.Columns WHERE TABLE_NAME LIKE '#result%' AND COLUMN_NAME <> 'UserID'
OPEN cur_workaround;
FETCH NEXT FROM cur_workaround INTO #workaroundTableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #workaroundQuery = #workaroundQuery + ',[' + #workaroundTableName + ']'
FETCH NEXT FROM cur_workaround INTO #workaroundTableName
END
CLOSE cur_workaround;
DEALLOCATE cur_workaround;
SET #workaroundQuery = #workaroundQuery + ' FROM #result ORDER BY Name ASC'
EXEC(#workaroundQuery);
Thanks everyone!
A much easier solution would be to not drop the column, but don't return it in the final select.
There are all sorts of reasons why you shouldn't be returning select * from your procedure anyway.
EDIT: I see now that you have to do it this way because of an unknown number of columns.
Based on the error message, is the database case sensitive, and so there's a difference between userID and UserID?
This works for me:
CREATE TABLE #temp_t
(
myInt int,
myUser varchar(100)
)
INSERT INTO #temp_t(myInt, myUser) VALUES(1, 'Jon1')
INSERT INTO #temp_t(myInt, myUser) VALUES(2, 'Jon2')
INSERT INTO #temp_t(myInt, myUser) VALUES(3, 'Jon3')
INSERT INTO #temp_t(myInt, myUser) VALUES(4, 'Jon4')
ALTER TABLE #temp_t
DROP Column myUser
SELECT * FROM #temp_t
DROP TABLE #temp_t
It says invalid column for you. Did you check the spelling and ensure there even exists that column in your temp table.
You might try wrapping everything preceding the DROP COLUMN in a BEGIN...COMMIT transaction.
At compile time, SQL Server is probably expanding the * into the full list of columns. Thus, at run time, SQL Server executes "SELECT UserID, Name, LastName, FirstName, ..." instead of "SELECT *". Dynamically assembling the final SELECT into a string and then EXECing it at the end of the stored procedure may be the way to go.