I'm trying to understand why Query 1 works, but Query 2 doesn't. How does the UNION affect the execution?
I understand that there's implicit conversions taking place. I'm not after a fix, best-practice or performance, just trying to understand the reasoning.
DECLARE #T1 TABLE (ID INT, TXT VARCHAR(10))
DECLARE #T2 TABLE (ID INT, T2Fk INT)
INSERT INTO #T1 VALUES(1, '1'), (2, '2'), (3, '3'), (4, 'AAA')
INSERT INTO #T2 VALUES(1, 1), (2, 3)
-- Query 1
SELECT * FROM #T2 WHERE T2Fk IN (SELECT TXT FROM #T1 WHERE ID IN (1,2,3))
-- Query 2
SELECT * FROM #T2 WHERE T2Fk IN (SELECT TXT FROM #T1 WHERE ID IN (1,2) UNION SELECT '3')
Thanks
Your problem is in the table definition, let say that SQL accept a numer like 3 with no ' ', that's why your firs Query works, but in the second you are traying to pass a varchar as Int in the position. if you make a Change in T2Fk int and put is as Varchar to it will work.
DECLARE #T1 TABLE (ID INT, TXT VARCHAR(10))
DECLARE #T2 TABLE (ID int, T2Fk varchar(10))
And then your Queries both will work
I don't know why SQL will run fine for first one, but not for the second one. SQL is converting your T2FK from an VARCHAR(10) to a INT and that is causing the failure on the second query. Best practice is to have comparing values of the same type. If you convert T2Fk to a VARCHAR(10) your second query also works.
-- Query 2
SELECT * FROM #T2 WHERE CAST(T2Fk AS VARCHAR(10)) IN (SELECT TXT FROM #T1 WHERE ID IN (1,2) UNION SELECT '3')
Related
There was a question here on SO that was since then removed. But while I was researching for ways to solve it, I was writing a script that avoids the use of an identity column and uses a sequence itself:
create table table1(Id int primary key, group_id int, Name varchar(64))
insert into table1(Id, group_id, Name) values (1, 1, 'a'), (2, 1, 'b'), (4, 1, 'c'), (8, 1, 'd')
declare #MaxId as int
select #MaxId = max(Id) + 1 from table1
declare #sql varchar(max)
set #sql = N'CREATE SEQUENCE MySequence AS INTEGER START WITH ' + cast(#maxId as varchar(10))
exec(#sql)
insert into table1(id, group_id, Name)
select next value for MySequence, 2, Name
from table1
where group_id = 1;
This actually works, that is, it successfully inserts four records with dynamically generated ids.
However, the the part of
declare #sql varchar(max)
set #sql = N'CREATE SEQUENCE MySequence AS INTEGER START WITH ' + cast(#maxId as varchar(10))
exec(#sql)
is very much counter-intuitive and hacky in my opinion.
Question: Is there a way to define a sequence that starts from a variable's value without generating a text and execute it?
The CREATE SEQUENCE syntax documentation shows a constant is required so you cannot specify a variable in the DDL statement.
Yes, creating a single-use dynamic sequence is a hack.
Instead use ROW_NUMBER(), something like
use tempdb
drop table if exists table1
go
create table table1(id int, group_id int, name varchar(200))
insert into table1(id,group_id,name) values (1,1,'a')
insert into table1(id,group_id,name) values (2,1,'a')
declare #maxValue int = (select max(id) from table1)
insert into table1(id, group_id, Name)
select #maxValue + row_number() over (order by Id), 2, Name
from table1
where group_id = 1;
select * from table1
I need to perform a row concatenation Operation in SQL Server, for those rows which all have the same Master_ID. Also, the resulted output order is based on the Seq_No Column.
As I am using an older version of SQL Server, I am unable to use STRING_AGG() function.
As of now, I am using Stuff and XML PATH functions to achieve the row concatenation, but I am unable to order the resulted data based on the Seq_No Column.
Table script:
DECLARE #T TABLE (Master_ID INT,
Associated_ID INT,
Class_ID INT,
Code VARCHAR(20),SEQ_No INT)
Insert into #T VALUES(1297232,NULL,3619202, '1101' ,1)
Insert into #T VALUES(1297232,NULL,3619202, '0813' ,2)
Insert into #T VALUES(1297232,NULL,3619202, '170219' ,3)
Insert into #T VALUES(1297232,NULL,3619202, '19053299',1)
Insert into #T VALUES(1297232,1297233,3619202,'1101' ,1)
Insert into #T VALUES(1297232,1297233,3619202,'0813' ,2)
Insert into #T VALUES(1297232,1297233,3619202,'170219' ,3)
Insert into #T VALUES(1297232,1297233,3619202,'19053299' ,1)
Insert into #T VALUES(1297232,1297234,3619202,'1101' ,1)
Insert into #T VALUES(1297232,1297234,3619202,'0813' ,2)
Insert into #T VALUES(1297232,1297234,3619202,'170219' ,3)
Insert into #T VALUES(1297232,1297234,3619202,'19053299' ,1)
Insert into #T VALUES(1297232,1297235,3619202,'1101' ,1)
Insert into #T VALUES(1297232,1297235,3619202,'0813' ,2)
Insert into #T VALUES(1297232,1297235,3619202,'170219' ,3)
Insert into #T VALUES(1297232,1297235,3619202,'19053299' ,1)
SELECT * FROM #T
The query I tried with error:
SELECT STUFF((SELECT DISTINCT' ,'+Code
FROM #T
ORDER by ISNULL(Associated_ID,Master_ID),SEQ_No -- Reason for Error
FOR XML PATH (''),TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
Output for the above code:
0813 ,1101 ,170219 ,19053299
Expected output:
1101,19053299,0813,170219
You can swap DISTINCT for GROUP BY as they to the same and then you can order by aggregation functions like SUM or MAX
Example:
SELECT STUFF((SELECT ' ,'+Code
FROM #T
GROUP BY Code
ORDER by SUM(ISNULL(Associated_ID,Master_ID)),SUM(SEQ_No)
FOR XML PATH (''),TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
-- OUTPUT: 1101 ,19053299 ,0813 ,170219
The error
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
has nothing to do with the stuff, but the fact you are trying to sort on a distinct. More info
I would like to replace the numbers in #CommentsTable column "Comments" with the equivalent text from #ModTable table, without using UDF in a single SELECT. May with a CTE. Tried STUFF with REPLACE, but no luck.
Any suggestions would be a great help!
Sample:
DECLARE #ModTable TABLE
(
ID INT,
ModName VARCHAR(10),
ModPos VARCHAR(10)
)
DECLARE #CommentsTable TABLE
(
ID INT,
Comments VARCHAR(100)
)
INSERT INTO #CommentsTable
VALUES (1, 'MyFirst 5 Comments with 6'),
(2, 'MySecond comments'),
(3, 'MyThird comments 5')
INSERT INTO #ModTABLE
VALUES (1, '[FIVE]', '5'),
(1, '[SIX]', '6'),
(1, '[ONE]', '1'),
(1, '[TWO]', '2')
SELECT T1.ID, <<REPLACED COMMENTS>>
FROM #CommentsTable T1
GROUP BY T1.ID, T1.Comments
**Expected Result:**
ID Comments
1 MyFirst [FIVE] Comments with [SIX]
2 MySecond comments
3 MyThird comments [FIVE]
Create a cursor, span over the #ModTable and do each replacement a time
DECLARE replcursor FOR SELECT ModPos, ModName FROM #ModTable;
OPEN replcursor;
DECLARE modpos varchar(100) DEFAULT "";
DECLARE modname varchar(100) DEFAULT "";
get_loop: LOOP
FETCH replcursor INTO #modpos, #modname
SELECT T1.ID, REPLACE(T1.Comments, #modpos, #modname)
FROM #CommentsTable T1
GROUP BY T1.ID, T1.Comments
END LOOP get_loop;
Of course, you can store the results in a temp table and get the results altogether in the end of loop.
You can use a while loop to iterate over the records and the mods. I slightly modified your #ModTable to have unique values for ID. If this is not your data structure, then you can use a window function like ROW_NUMBER() to get a unique value over which you can iterate.
Revised script example:
DECLARE #ModTable TABLE
(
ID INT,
ModName VARCHAR(10),
ModPos VARCHAR(10)
)
DECLARE #CommentsTable TABLE
(
ID INT,
Comments VARCHAR(100)
)
INSERT INTO #CommentsTable
VALUES (1, 'MyFirst 5 Comments with 6'),
(2, 'MySecond comments'),
(3, 'MyThird comments 5')
INSERT INTO #ModTABLE
VALUES (1, '[FIVE]', '5'),
(2, '[SIX]', '6'),
(3, '[ONE]', '1'),
(4, '[TWO]', '2')
declare #revisedTable table (id int, comments varchar(100))
declare #modcount int = (select count(*) from #ModTable)
declare #commentcount int = (select count(*) from #CommentsTable)
declare #currentcomment varchar(100) = ''
while #commentcount > 0
begin
set #modcount = (select count(*) from #ModTable)
set #currentcomment = (select Comments from #CommentsTable where ID = #commentcount)
while #modcount > 0
begin
set #currentcomment = REPLACE( #currentcomment,
(SELECT TOP 1 ModPos FROM #ModTable WHERE ID = #modcount),
(SELECT TOP 1 ModName FROM #ModTable WHERE ID = #modcount))
set #modcount = #modcount - 1
end
INSERT INTO #revisedTable (id, comments)
SELECT #commentcount, #currentcomment
set #commentcount = #commentcount - 1
end
SELECT *
FROM #revisedTable
order by id
I think the will work even though I generally avoid recursive queries. It assumes that you have consecutive ids though:
with Comments as
(
select ID, Comments, 0 as ConnectID
from #CommentsTable
union all
select ID, replace(c.Comments, m.ModPos, m.ModName), m.ConnectID
from Comments c inner join #ModTable m on m.ConnectID = c.ConnectID + 1
)
select * from Comments
where ConnectID = (select max(ID) from #ModTable)
=> CLR Function()
As I have lot of records in "CommentsTable" and the "ModTable" would have multiple ModName for each comments, finally decided to go with CLR Function. Thanks all of you for the suggestions and pointers.
Let's say I have a table with an ID Identity column, some data, and a datestamp. Like this:
1 data 5/1/2013 12:30
2 data 5/2/2013 15:32
3 data 5/2/2013 16:45
4 data 5/3/2013 9:32
5 data 5/5/2013 8:21
6 data 5/4/2013 9:36
7 data 5/6/2013 11:42
How do I write a query that will show me the one record that is timestamped 5/4? The table has millions of records. I've done some searching, but I don't know what to call what I'm searching for. :/
declare #t table(id int, bla char(4), timestamp datetime)
insert #t values
(1,'data','5/1/2013 12:30'),
(2,'data','5/2/2013 15:32'),
(3,'data','5/2/2013 16:45'),
(4,'data','5/3/2013 9:32'),
(5,'data','5/5/2013 8:21'),
(6,'data','5/4/2013 9:36'),
(7,'data','5/6/2013 11:42')
select timestamp
from
(
select rn1 = row_number() over (order by id),
rn2 = row_number() over (order by timestamp), timestamp
from #t
) a
where rn1 not in (rn2, rn2-1)
in 2008 r2, this would be a way
DECLARE #Table AS TABLE
(id INT , ladate DATETIME)
INSERT INTO #Table VALUES (1, '2013-05-01')
INSERT INTO #Table VALUES (2, '2013-05-02')
INSERT INTO #Table VALUES (3, '2013-05-03')
INSERT INTO #Table VALUES (4, '2013-05-05')
INSERT INTO #Table VALUES (5, '2013-05-04')
INSERT INTO #Table VALUES (6, '2013-05-06')
INSERT INTO #Table VALUES (7, '2013-05-07')
INSERT INTO #Table VALUES (8, '2013-05-08')
--I added the records in the sort order but if not just make sure you are sorted in the query
SELECT t2.ladate FROM #Table T1
INNER JOIN #Table T2 ON T1.Id = T2.Id + 1
INNER JOIN #Table t3 ON t2.id = t3.id + 1
WHERE t3.ladate < t2.ladate AND t2.ladate > t1.ladate
-- I made the assumption that your Id are all there, 1,2,3,4,5.... none missing... if there are rownumbers missing, you can use row_number()
I need some help with conditions on left join, my query is as follows
declare #emp table (id int, name varchar(100))
insert into #emp values (1,'Emp1')
insert into #emp values (2,'Emp2')
insert into #emp values (3,'Emp3')
insert into #emp values (4,'Emp4')
insert into #emp values (5,'Emp5')
--selecT * from #emp
declare #salary table(salaryid int, empid int, salary decimal(10,2))
insert into #Salary values (3,3,10000)
insert into #Salary values (4,4,15000)
insert into #Salary values (3,5,10000)
declare #oldsalary table(oldsalaryid int, empid int, oldsalary decimal(10,2))
insert into #oldsalary values (1,1,20000)
insert into #oldsalary values (2,2,25000)
--select * from #Salary
--select * from #oldsalary
declare #rating table (salaryid int, rating varchar(10))
insert into #rating values (4, 'D')
insert into #rating values (3, 'C')
insert into #rating values (1, 'B')
insert into #rating values (2, 'A')
--select * from #rating
select e.id, e.name, isnull(os.oldsalary, s.salary) salary, r.rating from #emp e
left join #salary s on e.id=s.empid
left join #oldsalary os on e.id=os.empid
left join #Rating r on r.salaryid = isnull(os.oldsalaryid, s.salaryid)
and this is the output
id name salary rating
1 Emp1 20000 B
2 Emp2 25000 A
3 Emp3 10000 C
4 Emp4 15000 D
5 Emp5 10000 C
As you can see from the query, if oldsalaryid is null then salaryid is used to join the rating table. So the left join is completely based on the value of the column. Is this the proper approach, by looking at the data everything seems to be showing correctly. Can I use this query?
What you are essentially telling SQL Server to do is to return all valuese from the #emp table. Then all items from the #salary table that has a match on the #emp table are being pulled in. After that, all values from the #oldsalary table that matches values on the #emp table are being pulled in. And the same with the #Rating table.
I prefer to to go with this approach of only using left joins instead of a combination of left and right joins. It gives the person reading the code a very clear picture of what the dominant tables in a comblex join are. As a result, you won't need to read the FROM section of the statement backwards and forwards.