3

I have a stored procedure which drops a table if it exists, then it re-creates the table & fills it with relevant data, a friend of mine has about the same code, the only real difference is in the column headers for the table.

As an illustration, here's how mine looks (not really, just a representation).

+----+-----+-----+--------+
| ID | Foo | Bar | Number |
+----+-----+-----+--------+
|  1 | x   | x   |      0 |
|  2 | x   | x   |      1 |
+----+-----+-----+--------+

And here's what his might look like

+----+--------+--------+-----+--------+
| ID | BarFoo | FooBar | Num | Suffix |
+----+--------+--------+-----+--------+
|  1 | x      | x      |   0 | a      |
|  2 | x      | x      |   1 | b      |
+----+--------+--------+-----+--------+

Again, these are merely representations of the situation.

As this is to be a school assignment, the teacher will be creating & executing both SP's, however when creating the SP after using another, I get this error:

Msg 207, Level 16, State 1, Procedure XYZ, Line 59
Invalid column name 'Foo'.

Msg 213, Level 16, State 1, Procedure XYZ, Line 61
Column name or number of supplied values does not match table definition.

However, at the start of both stored procedures, we have this:

CREATE PROCEDURE XYZ
AS
BEGIN
    IF EXISTS (SELECT name
               FROM   sysobjects
               WHERE  name = 'TABLENAME'
                      AND xtype = 'u')
        DROP TABLE TABLENAME;

From what I understand, this should remove the entire table? Including table/column definitions & data?

The only fix I've found so far, is to either execute the DROP TABLE separately before creating the stored procedure, which won't work for us as it really has to be within the stored procedure.

Help would be much appreciated :)

EDIT: Here's my ACTUAL code, apart from comments, this is exactly how it looks in my script (excluding other code behind it).

IF EXISTS (SELECT name
           FROM   sysobjects
           WHERE  name = 'BerekenStatistiek'
                  AND xtype = 'p')
    DROP PROCEDURE BerekenStatistiek;


GO
CREATE PROCEDURE BerekenStatistiek
@jaar INT=0
AS
BEGIN
    IF EXISTS (SELECT name
               FROM   sysobjects
               WHERE  name = 'Statistiek'
                      AND xtype = 'u')
        DROP TABLE Statistiek;
    DECLARE @year AS NVARCHAR (4);
    SET @year = CONVERT (NVARCHAR (4), @jaar);
    SELECT *,
           CAST (Kost - Korting + Freight AS MONEY) AS Netto,
           '' AS Richting
    INTO   Statistiek
    FROM   (SELECT   O.Kwartaal,
                     CAST (SUM(O.Kost) AS MONEY) AS Kost,
                     CAST (SUM(O.Korting) AS MONEY) AS Korting,
                     CAST (SUM(O.Freight) AS MONEY) AS Freight
            FROM     (SELECT CASE 
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN @year + '0101' AND @year + '0331' THEN 1 
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN @year + '0401' AND @year + '0630' THEN 2 
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN @year + '0701' AND @year + '0930' THEN 3 
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN @year + '1001' AND @year + '1231' THEN 4 
END AS 'Kwartaal',
                             ROUND(UnitPrice * Quantity, 2) AS Kost,
                             Round((UnitPrice * Quantity) * Discount, 2) AS Korting,
                             Freight
                      FROM   Orders AS O
                             INNER JOIN
                             OrderDetails AS Od
                             ON O.OrderID = Od.OrderID
                      WHERE  CONVERT (NVARCHAR (4), OrderDate, 112) = @year) AS O
            GROUP BY O.Kwartaal) AS O1;
    ALTER TABLE Statistiek ALTER COLUMN Kwartaal INT NOT NULL;
    ALTER TABLE Statistiek ALTER COLUMN Richting NVARCHAR (8);
    ALTER TABLE Statistiek
        ADD PRIMARY KEY (Kwartaal);
...

And here's his code (the insertion of values in the variables are excluded just for readability (his code is a bit more bulky):

IF EXISTS (SELECT name
           FROM   sysobjects
           WHERE  name = 'BerekenStatistiek'
                  AND xtype = 'p')
    BEGIN
        DROP PROCEDURE BerekenStatistiek;
    END


GO
CREATE PROCEDURE BerekenStatistiek
@jaartal INT
AS
BEGIN
    DECLARE @huidigkwartaal AS INT = 1;
    DECLARE @beginmaand AS INT;
    DECLARE @eindmaand AS INT;
    DECLARE @vorige_netto_ontvangsten AS MONEY;
    IF EXISTS (SELECT *
               FROM   sysobjects
               WHERE  name = 'Statistiek'
                      AND xtype = 'U')
        BEGIN
            DROP TABLE Statistiek;
        END
    CREATE TABLE Statistiek
    (
        kwartaalnummer         INT          ,
        beginmaand             INT          ,
        eindmaand              INT          ,
        orderbedrag            MONEY        ,
        korting                MONEY        ,
        vervoerskost           MONEY        ,
        netto_ontvangsten      MONEY        ,
        stijgend_dalend_gelijk NVARCHAR (10)
    );

    --Variables get their data here.

    INSERT  INTO Statistiek (kwartaalnummer, beginmaand, eindmaand, orderbedrag, korting, vervoerskost, netto_ontvangsten, stijgend_dalend_gelijk)
    VALUES                 (@huidigkwartaal, @beginmaand, @eindmaand, @orderbedrag, @korting, @vervoerskost, @netto_ontvangsten, @stijgend_dalend_gelijk);
Yorrick
  • 377
  • 2
  • 8
  • 28
  • You've posted the wrong code. That error is when you are putting values in the table. Taking a wild guess I'd say you've used the sql to put data in the old version of the table on the new one, which has more columns, none of which are called foo. – Tony Hopkinson Jan 12 '15 at 17:18
  • Well, yes, but the Stored Procedure has the `DROP TABLE` before any data is being handled at all, so either I'm misunderstanding the exact function of `DROP TABLE`, or the stored procedure isn't taking that into account – Yorrick Jan 12 '15 at 17:21
  • What is the collation of your DB? Run this: `SELECT CONVERT (varchar, SERVERPROPERTY('collation'));` – SouravA Jan 12 '15 at 17:37
  • You aren't getting that error from Drop Table, you are getting it later. It's says the problem is on lines 59 and 61, you only gave us five lines. It's a mismatch between the table that was created and the data you are trying to insert, nothing else it could be. – Tony Hopkinson Jan 12 '15 at 17:37
  • @Sourav_Agasti `Latin1_General_CI_AS` @TonyHopkinson As I said, it's because of the `DROP TABLE` not performing as I expected it to, I know the columns are different in each version, but as both stored procedures have `DROP TABLE` at the start, it shouldn't matter as far as I know? – Yorrick Jan 12 '15 at 17:39
  • `IF EXISTS (<>) DROP ...` Is every version of *<>* returning values? That would be the basic troubleshoot. – SouravA Jan 12 '15 at 17:42
  • @Sourav_Agasti both work fine, as long as I execute the `drop table` separately, the issue only happens when I have the table with 1 SP's format, then try to create the other SP – Yorrick Jan 12 '15 at 17:44
  • If it was me I'd have one sp that executed three other SPs. One to drop if exists, one to create if doesn't exists, and one to populate IF it exists and the structure matches up, (check syscolumns...) – Tony Hopkinson Jan 12 '15 at 17:50
  • @TonyHopkinson That's solve the problem, indeed, however I'm limited by what my teacher makes us do & how he will be expecting it to work, so I'm pretty much forced to have it all in 1 SP, the question I'm asking here isn't because I really need it, but because my teacher probably will. – Yorrick Jan 12 '15 at 17:54
  • @Yorrick Never understood that form of 'teaching' myself. Worth mentioning it though as way of solving this issue and a number of other ones. – Tony Hopkinson Jan 12 '15 at 20:44

4 Answers4

2

"however when creating the SP after using another, I get this error" (Emphasis added.) SQL Server will insist that a stored procedure match the definitions of tables that exist as the time the stored procedure is created. If the table does not exist when the stored procedure is created, SQL Server will assume that a matching table will appear at run time.

create table t (c int)
go
create procedure p as begin
    drop table t 
    select 1 as diff_column_name into t
    select diff_colun_name from t
end

results in:

Msg 207, Level 16, State 1, Procedure p, Line 6
Invalid column name 'diff_colun_name'.

Now, drop table t, and the procedure cane be created:

drop table t 
go
create procedure p as begin
    drop table t 
    select 1 as diff_column_name into t
    select diff_colun_name from t
end

Command(s) completed successfully.
Shannon Severance
  • 18,025
  • 3
  • 46
  • 67
  • Exactly, that's how it's behaving at this point, however, I'm trying to find a way where I don't have to drop the table separately, so both SP's can be executed back to back. – Yorrick Jan 12 '15 at 17:49
  • @Yorrick These are *compile* errors, not execution errors. The tables need to exist *when you save/compile* the stored procedures, not necessarily when you execute them. However, working this out is somewhat difficult and complex. The standard advice is *don't try to have two different definitions for the same table, because SQL Server doesn't like it.* You can get around a lot of this with #temp tables (because the T-SQL compiler handles them differently), but if you need the tables to persist, they need to retain a consistent definition. – RBarryYoung Jan 12 '15 at 18:40
  • @Yorrick, the requirements are weird. Having the table structure change during execution is not common. However, if the table does not exist. Then both procedures that use it, with different definitions could be **compiled** `create procedure ....` And then the two procedure could be ran, with the second dropping and recreating the table with its structure. – Shannon Severance Jan 13 '15 at 02:31
  • Honestly... Have either of you (@RBarryYoung and @ShannonSeverance) read any of the comments on other answers? I can't know/control how the columns in other people's tables be called as this is a school project. I was trying to get this solved so my teacher wouldn't run into problems... – Yorrick Jan 14 '15 at 18:46
0

For I can understand, the wrong queries are the inserts, because the engine can't find correct table structure, check if the inserts have the same structure of your second table example. Dont forget to check the USE at the beginning of the script, maybe you are using a different db, this can happen :).

  • Both scripts have the same USE, I'll post both codes in my original question though, including the actual column names etc. – Yorrick Jan 12 '15 at 17:27
  • In the first script you dont recreate the table, just drop it. I think that is a wrong sequence of you run the script. – Juan Ruiz de Castilla Jan 12 '15 at 17:43
  • In my own (the first) script, it gets auto-created through the `SELECT x INTO Statistiek` however both don't work, so it's not an issue with "just one", it's an issue with both – Yorrick Jan 12 '15 at 17:47
0

In the last bit of code, you are having

AND xtype = 'U'

If your collation is case sensitive, the drop is not taking place and thus the error.

SouravA
  • 5,147
  • 2
  • 24
  • 49
  • 1 has `xtype = 'u'` and the other has `xtype = 'U'`, however neither work, so I don't really think that's the issue here. – Yorrick Jan 12 '15 at 17:40
0

If you can use a different table name, start with that. And, if the table has to exist only for a moment after the proc is executed so that it can be selected from, then create a global temporary table (i.e. table name starts with ## as in ##MyTable).

However, if it is a requirement to use the same table name as your classmate, then the teacher is probably trying to get you to learn about deferred object resolution (i.e. @Shannon's answer) and how to get around it, because outside of learning this, the scenario makes no sense since one would never do such a thing in reality.

Sub-processes (i.e. EXEC and sp_executesql) do not resolve immediately since they aren't executed when creating the stored procedure. So, simplistically, just declare a new NVARCHAR(MAX) variable to hold some Dynamic SQL and put your SELECT statement in there. Use sp_executesql to pass in the @year variable. You are creating a real table so it will survive beyond the subprocess ending and then the ALTER TABLE statement will work.

Additional notes:

  • You don't really need the ALTER statement to set the datatype of the [Richting] field. Just tell SQL Server what the type is in your SELECT statement:

    CONVERT(NVARCHAR(8), '') AS [Richting]
    
  • You don't really want to do CONVERT(NVARCHAR(8), OrderDate, 112) to compare to a value as it invalidates the use of any indexes that might be on [OrderDate]. Instead, construct a date value from the strings and convert that to a DATETIME or DATE (i.e. CONVERT(DATETIME, @year + '0101')).

    To better understand this issue, please read Sargability: Why %string% Is Slow, and at least the first link at the bottom, which is: What makes a SQL statement sargable?

  • You don't really want to convert the OrderDate field to NVARCHAR(4) just to compare the year, for the same reason as just mentioned in the above point. At the very least using the YEAR() function would be more direct. But if you want to make sure indexes can be used, you can't put a function on the field. But you only want the year. So isn't the year the same as BETWEEN @Year + '0101' AND @Year + '1231'? ;-)

    Interestingly enough, the first example in the accepted answer in the "What makes a SQL statement sargable?" S.O. question linked in the previous bullet is exactly what I am recommending here :).

Community
  • 1
  • 1
Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • It's not a requirement, however I don't know what other people will be calling their tables, or how their table structure will be (there's about 15 people in my class, all making this). We haven't seen temporary tables yet & I don't really want to use anything we haven't really seen. However I will definitely look into the `sp_executesql` thing =) – Yorrick Jan 12 '15 at 18:17
  • @Yorrick Do you have your own Schema? You don't need to use a temp table to have a unique name, especially with only 15 other students. You could call it `[djkfhgdjkfhgdjfhgjkdfg]` and be fairly certain that nobody else will be using that name ;-). – Solomon Rutzky Jan 12 '15 at 18:22
  • the `BETWEEN @year + '0101' AND @year + '0331'` is there because the SP needs to fill the table with 4 records, one for each quarter for the given year :-) Also, I could indeed give it some ridiculous name, however in the assignment the table name is specifically stated and my teacher isn't exactly the most forgiving person :-( – Yorrick Jan 12 '15 at 18:28
  • @Yorrick I know why the 4 `BETWEEN` conditions are there in the `CASE` statement, I am just telling you how to write the query to get the same result while allowing SQL Server to be as efficient as it can be. I am saying to replace all 8 instances of `@year + 'xxxx'` with the `CONVERT(DATETIME, @year + 'xxxx')` and just use `[OrderDate] BETWEEN...`, no CONVERT there. And I only suggested the ridiculous name because in your first replace you said that using the same name was _not_ a requirement. Now you say that it is. Which is fine, I was just going on my understanding of what you had said. – Solomon Rutzky Jan 12 '15 at 18:35
  • Oh I see, I misunderstood for a second there then. And yes, I was fairly sure the table names weren't explicitly specified in the assignment, however I checked & turns out it is, however the column names don't matter as much. – Yorrick Jan 12 '15 at 18:44
  • @Yorrick Ok, so forget about the field names (whether they conflict or not) as the main issue is the table name. In which case you need the subprocess. And the only date thing that I was really suggesting to change was in the WHERE condition since `WHERE YEAR(OrderDate) = xxxx` is logically the same as `WHERE OrderDate BETWEEN 'xxxx-01-01' AND 'xxxx-12-31'`, right? But the benefit is that [OrderDate] is then not wrapped in a function. I will add a note in the answer as to why this is important. – Solomon Rutzky Jan 12 '15 at 18:46