Using the dangerous IN clause in SQL - sql

Why does SQL server behave this way. I am running this on SQL 2005.
The IN clause does not validate the column names in the sub query but validates it
against the table name in the outer query. Here is an example of getting
Create table #table1(col1 int, col2 char(10), col3 char(15));
Create table #table2(col10 int, col11 char(10), col2 char(15));
insert into #table1(col1, col2, col3)
select 1, 'one', 'three'
insert into #table1(col1, col2, col3)
select 2, 'two', 'three'
insert into #table1(col1, col2, col3)
select 3, 'three', 'four'
insert into #table2(col10, col11, col2)
select 1, 'one', 'three'
insert into #table2(col10, col11, col2)
select 2, 'two', 'three'
insert into #table2(col10, col11, col2)
select 3, 'three', 'four'
select * from #table1
where col1 IN
(select col1 from #table2)
Where as if I just select the "select col1 from #table2" and run it spits an error
Msg 207, Level 16, State 1, Line 1
Invalid column name 'col1'.

Why? Because it's frequently useful to be able to reference columns from the outer query in subqueries. There's no setting you can use to turn off this behaviour, but if you get into the habit of using aliases, you should avoid most problems with it:
select * from #table1 t1
where t1.col1 IN
(select t2.col1 from #table2 t2)
Will produce an error.

It's not the IN clauses that's the problem.
This:
SELECT *
FROM #table1
WHERE col1 IN (SELECT col1
FROM #table2)
...works, because the optimizer is assumes col1 to be from #table1. If you use table aliases so there's no ambiguity:
SELECT t1.*
FROM #table1 t1
WHERE t1.col1 IN (SELECT t2.col1
FROM #table2 t2)
...you'll get the Msg 207: Invalid column error.
This is the same principle as when working with DELETE and UPDATE statements, because the typical syntax doesn't allow you to alias the table being deleted or updated.

Related

Adding result of column expression as new column on table variable creation

I'm sure this is really easy but I can't think of a solution and can't seem to find any documentation that answers my exact question.
While inserting values into a table variable how do I set the value of a field to be the result of an expression from another field in the same table?
For example:
declare #tableVar table(
[col1] int,
[col2] dec(18,2),
[col3] dec(18,2)
)
insert into #tableVar
values (100,.03,[col1] * [col2])
select *
from #tableVar
Would ideally return:
col1 col2 col3
100 0.03 3.00
But I get this error instead:
Msg 207, Level 16, State 1, Line 19
Invalid column name 'col1'.
Msg 207, Level 16, State 1, Line 19
Invalid column name 'col2'.
I understand why I get the error I just can't seem to come up with a solution.
Any hints?
You would use a subquery:
insert into #tableVar (col1, col2, col3)
select col1, col2, col1 * col2
from (values (100, 0.03)) v(col1, col2);
Or, better yet, use a computed column:
declare #tableVar table (
col1 int,
col2 dec(18, 2),
col3 as ( convert(dec(18, 2), col1 * col2) )
);
insert into #tableVar (col1, col2)
values (100, 0.03);
Note that both these examples explicitly list the columns being inserted. That is considered a best-practice.
You need values construct :
insert into #tableVar (col1, col2, col3)
select col1, col2, col1 * col2
from (values (100, .03)
) t(col1, col2);

Insert into a table using values from another

I am trying to perform an insert using the following command:
insert into table2(COL1, COL2, COL3, COL4) values((select COL1 FROM table1 WHERE COL1 = 121212),120,10,"Y");
But I get the following error:
ERROR at line 1: ORA-00984: column not allowed here
Any help?
INSERT INTO table2( col1, col2, col3, col4 )
SELECT col1, 120, 10, 'Y'
FROM table1
WHERE col1 = 121212
should work.

SQL get multiple values of columns in one row

I am using MS Sql server 2008 R2.
I have a query that gives me output like this
Col1....Col2
CV1.....AV1
CV1.....AV2
CV2.....AV3
CV2.....AV4
The query is
select Tab1.Col1, Tab2.Col2
from Table1 Tab1
JOIN Table2 Tab2 on Tab1.PKID = Tab2.FKID
What I want is one row for each distinct values in Col1 and in Col2 all the values related to col1 with comma or pipeline delimiter
Col1....Col2
CV1.....AV1,AV2
CV2.....AV3,AV4
Can anyone help me on this?
Basically I need something like group_concat that is available in My sql
CREATE TABLE a(
Col1 varchar(50),
Col2 varchar(20));
INSERT INTO a (Col1,Col2) values ('CV1','AV1');
INSERT INTO a (Col1,Col2) values ('CV1','AV2');
INSERT INTO a (Col1,Col2) values ('CV2','AV3');
INSERT INTO a (Col1,Col2) values ('CV2','AV4');
with t as (SELECT Col1,(CAST(Col2 AS nvarchar (12))) as col2 from a )
Select distinct T2.Col1,
substring((Select ',' + T1.col2 AS [text()]
From t T1
Where T1.Col1 = T2.Col1
ORDER BY T1.Col1
For XML PATH ('')),2, 100) [col2]
From t T2
Try this query. I am doing it in sql server. check at sqlfidddle
http://sqlfiddle.com/#!3/7ab28/1

Sql insert select from – multiple rows with unique column id

I am trying to copy multiple records using one query using insert select from.
Insert into tab_A(colId, col1, col2, col3)
Select colId, col1, col2, col3 form tab_A
Where colId in ( 2,4,6)
Would it be possible to assign different colId for new entries? For example colid 2 should be replaced with 23, 4 with 24 and 6 with 25. How could I achieve it in a single query?
this would work
Insert into tab_A(colId, col1, col2, col3)
Select 23 , col1, col2, col3 form tab_A Where colId = 2 UNION ALL
Select 24 , col1, col2, col3 form tab_A Where colId = 4 UNION ALL
Select 25 , col1, col2, col3 form tab_A Where colId = 6
If you give some more info I could provide somthing more reusable. Should/is colId (be) an identity column?
EDIT
This would work in this very specialised case
Insert into tab_A(colId, col1, col2, col3)
Select ((colId - 4) * (-1)) + colId + 20 , col1, col2, col3
form tab_A Where colId IN (2, 4, 6)
The function newId = ((oldId - 4) * (-1)) + oldId + 20 is obviously specific to the stated problem.
EDIT2
I suspect somthing like this is more generic approach is appropriate.
DECLARE #MaxColID INT
BEGIN TRANSACTION
SELECT #MaxColID = MAX(ColID) FROM tab_A
INSERT tab_A(colId, col1, col2, col3)
SELECT row + #MaxColID, col1, col2, col3
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY ColID) row, col1, col2, col3
FROM tab_A WHERE colID IN (2, 4, 6)
)
COMMIT
EDIT 3
If you think EDIT 2 is actually what you want then you really want to make ColID an IDENTITY column, then you could do this.
INSERT tab_A (col1, col2, col3)
SELECT col1, col2, col3 FROM tab_A WHERE colId IN (2, 4, 6)
I dont see col4 or col6 in your query, but is this what you want:
Insert into tab_A(colId, col1, col2, col3)
Select colId, col1, 23, col3 form tab_A
Where colId in ( 2,4,6)
have you just tried adding the disired difference to colId -
In your case, since you need to replace 2 by 23, difference is 21.
Insert into tab_A(colId, col1, col2, col3)
Select colId+21, col1, col2, col3
form tab_A Where colId in ( 2,4,6)
Note: I missed the part, that the differnce is not consistent in your case.
The proposed solution will work only if difference is same
There are a few options:
Add the new ID column to the original table and populate it with the new values before you do this insert, selecting the new ID column instead of the old. This would be the tidiest solution I think.
Alternative - Modify the ID value on the insert based on a rule e.g.
INSERT INTO tab_A(colID, col1, col2, col3)
SELECT colId + 20, col1, col2, col3
FROM tab_A
WHERE colID IN(2,4,6)
Last resort - Process the insert sequentially with a cursor, modifying the ID value each time.
You could also write case in the select. when 2 then 23 or whatever value.

How to get count on ths query

I'm doing an
INSERT INTO table1...
SELECT...
FROM table2
However, I need to retrieve the identity from a table3 and do an insert into it just before inserting into table1. These two inserts need to occur together, with table3 insert going first. I've tried something like this:
INSERT INTO table1 (col1, col2, col3)
SELECT (
col1=(insert into table3(col1, col2)values(1,1) select SCOPE_IDENTITY(),
col2, col3)
FROM table2
But that doesn't work. table1.col1 does need the identity value from the new insert into table3. Amount of data to insert probably no more than a few 100 rows. Any suggestions?
It looks like you might be able to use the Output Clause.
BEGIN TRANSACTION
DECLARE #MyResults table(col1 int, col2 int, col3 int);
INSERT INTO table3 (col1, col2)
OUTPUT SCOPE_IDENTITY(), table2.col2, table2.col3 INTO #MyResults
SELECT 1, 1 FROM table2
INSERT INTO table1 (col1, col2, col3)
SELECT col1, col2, col3 FROM #MyResults
COMMIT