Combining subselects in SQL query - sql-server

How can I simplify this query by combining the subselects?
SELECT *
FROM table
WHERE id1 IN (SELECT id1 FROM table WHERE [keyid] = 123)
AND id2 IN (SELECT [id2] FROM table WHERE [keyid] = 123)
I naively tried:
SELECT *
FROM table
WHERE id1 = t.id1
AND id2 = t.id2
IN (SELECT id1, id2 FROM table WHERE keyid = 123) AS t

There is no real need to rewrite your query, it is fine as-is. You could rewrite the subqueries using exists logic:
SELECT t1.*
FROM yourTable t1
WHERE
EXISTS (SELECT 1 FROM yourTable t2 WHERE t2.id1 = t1.id1 AND t2.keyid = 123) AND
EXISTS (SELECT 1 FROM yourTable t3 WHERE t3.id2 = t1.id2 AND t3.keyid = 123);
The exists logic would let SQL Server stop scanning your table as soon as it finds a single match. This might mean improved performance over the version you currently have.
If you wanted to rewrite using a series of self joins, here is what you could try:
SELECT DISTINCT t1.*
FROM yourTable t1
INNER JOIN yourTable t2 ON t2.id1 = t1.id1
INNER JOIN yourTable t3 ON t3.id2 = t1.id2
WHERE t2.keyid = 123 AND t3.keyid = 123;

From your tablenames, I assume that both IN Clauses refer to same table and refer to same key field.
SELECT t1.* FROM Table AS t1
INNER JOIN
(
SELECT Id1, Id2 FROM Table WHERE keyid = 123
) as t2
ON t1.id1 = t2.id1 AND t1.id2 = t2.id2

Related

SQL Server insert new rows ONLY based on multiple columns

I searched in SO but couldn't find anything for my purpose. I need to insert unique rows ONLY from one table into another. I have:
table1
id name bookid bookname start_date end_date rel_date rel_id
1 horror 1221 rockys 04/01/2016 04/30/2016 05/01/2016 4545
2 horror 1331 elm 04/01/2016 04/30/2016 05/01/2016 5656
table2
id name bookid bookname start_date end_date rel_date rel_id
1 horror 1221 rockys 04/01/2016 04/30/2016 05/01/2016 4545
2 horror 1441 elm 04/01/2016 04/30/2016 05/01/2016 5656
I need to insert into table1 the row with id = 2 in table2 AND also delete the row with id = 2 from table1, because bookid is different even though the rest of the columns match.
I tried following:
insert into table1
select * from table2
where not exists (select * from table2 where table1.id = table2.id
and table1.name = table2.name and table1.bookid = table2.bookid and
table1.bookname = table2.bookname and table1.start_date = table2.start_date
and table1.end_date = table2.end_date and table1.rel_date = table2.rel_date
and table1.rel_id = table2.rel_id)
Any way I can do all of this in one sql block?
In theory the following merge statement should achieve what you are looking for.
MERGE table1 [Target]
USING table2 [Source]
ON ([Target].[name] = [Source].[name]
AND
[Target].[bookname] = [Source].[bookname]
AND
[Target].[start_date] = [Source].[start_date]
AND
[Target].[end_date] = [Source].[end_date]
AND
[Target].[rel_date] = [Source].[rel_date]
AND
[Target].[rel_id] = [Source].[rel_id]
)
WHEN MATCHED AND ([Target].[bookid] <> [Source].[bookid]) THEN
UPDATE
SET [Target].[name] = [Source].[name]
,[Target].[bookid] = [Source].[bookid]
,[Target].[bookname] = [Source].[bookname]
,[Target].[start_date] = [Source].[start_date]
,[Target].[end_date] = [Source].[end_date]
,[Target].[rel_date] = [Source].[rel_date]
,[Target].[rel_id] = [Source].[rel_id]
WHEN NOT MATCHED THEN
INSERT(
[name]
,[bookid]
,[bookname]
,[start_date]
,[end_date]
,[rel_date]
,[rel_id]
)
VALUES
(
[Source].[name]
,[Source].[bookid]
,[Source].[bookname]
,[Source].[start_date]
,[Source].[end_date]
,[Source].[rel_date]
,[Source].[rel_id]
);
Note that there are some risks and limitations to this approach. If your [id] column has a uniqueness constraint, then it should be set as an identity column otherwise you will run into uniqueness violation errors. Also if [id] column value in table1 is different to [id] column in table2 then merge statement will keep the original [id] value from table1.
Basically this query simply updates your existing record in table1 with the matching record in table2 and insert new records from table2 into table1 if they don’t already exists.
All you should need to achieve your objective is this:
UPDATE T1
SET T1.bookid = T2.bookid
FROM Table1 T1
JOIN Table2 T2
ON T1.ID = T2.ID
However, to answer the question exactly as it was asked:
DELETE T1
FROM Table1 T1
JOIN Table2 T2
ON T1.ID = T2.ID
AND T1.bookid <> T2.bookid
INSERT INTO Table1
SELECT id, name, bookid, bookname, start_date, end_date, rel_date, rel_id
FROM Table2 T2
LEFT OUTER JOIN Table1 T1
ON T1.ID = T2.ID
AND T1.bookid = T2.bookid
WHERE T1.id IS NULL
Note that if your ID fields aren't unique, you'll need to add other conditions to the ON clauses.
If you are just concerned about updating the bookid value from table2, you can change the value of bookid with the below query
UPDATE t1 SET t1.bookid = t2.bookid
FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id
If you think your id column is not unique in two tables, you might need to consider adding other matching columns in the JOIN.

SubSelect Top 1 OR Left Join

I have a Select with sub selects using Top 1 and where clause.
I tried to optimize the select by doing a Left Join of the sub selects but the query time took longer. Is subselect better in this case? I couldnt post my whole select because it is too long and confidential but I will try to recreate the important part below:
Sub Select
SELECT
(select top 1 colId FROM table1 WHERE col1 = b.Id and col2 = 3 Order by 1) Id3,
(select top 1 colId FROM table1 WHERE col1 = b.Id and col2 = 5 Order by 1) Id5,
(select top 1 colId FROM table1 WHERE col1 = b.Id and col2 = 7 Order by 1) Id7
FROM table2 b
Trying it w/ Left Join
SELECT
t1.colid id3,
t2.colid id5,
t3.colid id7
FROM table2 b
LEFT JOIN (
select colId, col1 FROM table1 WHERE col2 = 3
) t1 ON t1.col1 = b.Id
LEFT JOIN (
select colId, col1 FROM table1 WHERE col2 = 5
) t2 ON t1.col1 = b.Id
LEFT JOIN (
select colId, col1 FROM table1 WHERE col2 = 7
) t3 ON t1.col1 = b.Id
Is there a better way to do this? and why is it the Left join takes longer query time?
You can use ROW_NUMBER:
;WITH cte AS
(
SELECT a.colId,
rn = ROWN_NUMBER() OVER (PARTITION BY a.col2 ORDER BY a.col1)
FROM table1 a
LEFT JOIN table2 b on a.col1 = b.id
WHERE a.col2 IN (3,5,7)
)
SELECT *
FROM cte
WHERE rn = 1
This will give you the first row for each col2 value and you can restrict the values you want to 3,5,7.

Compare and merge data from 2 tables with same structure

Having 2 tables with the same structure, like this SQLFiddle, is it possible to build a SQL statement that compares the values of the columns of both tables (where id is the unique key), and return a list of the change columns in the format:
columnname, oldvalue, newvalue
Where oldvalue is the value in Table1 and newvalue is the value in Table2.
You can do something like this:
SELECT T1.Id
,'Name' AS ColumnName
,CAST(T1.name AS VARCHAR(MAX)) AS OldValue
,CAST(T2.name AS VARCHAR(MAX)) AS NewValue
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
UNION
SELECT T1.Id
,'Amount'
,CAST(T1.amount AS VARCHAR(MAX))
,CAST(T2.amount AS VARCHAR(MAX))
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
You need to use a MERGE statement , please note it is only available from SQL 2008 onwards
here is an example
MERGE Production.ProductInventory AS target
USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail AS sod
JOIN Sales.SalesOrderHeader AS soh
ON sod.SalesOrderID = soh.SalesOrderID
AND soh.OrderDate = #OrderDate
GROUP BY ProductID) AS source (ProductID, OrderQty)
ON (target.ProductID = source.ProductID)
WHEN MATCHED AND target.Quantity - source.OrderQty <= 0
THEN DELETE
WHEN MATCHED
THEN UPDATE SET target.Quantity = target.Quantity - source.OrderQty,
target.ModifiedDate = GETDATE()
OUTPUT $action, Inserted.ProductID, Inserted.Quantity, Inserted.ModifiedDate, Deleted.ProductID,
Deleted.Quantity, Deleted.ModifiedDate;
GO
you can learn more about merge's here SQL merge
if you only want rows with differences:
SELECT COALESCE(T1.Id, T2.Id) Id
,'Name' AS ColumnName
,CAST(T1.name AS VARCHAR(MAX)) AS OldValue
,CAST(T2.name AS VARCHAR(MAX)) AS NewValue
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
WHERE COALESCE(T1.name,'**') != COALESCE(T2.name ,'**')
UNION ALL
SELECT COALESCE(T1.Id, T2.Id) Id
,'Amount' AS ColumnName
,CAST(T1.Amount AS VARCHAR(MAX)) AS OldValue
,CAST(T2.Amount AS VARCHAR(MAX)) AS NewValue
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
WHERE COALESCE(T1.Amount,0) != COALESCE(T2.Amount,0)

Using where condition in sql query

I have an sql query like this
Select col1, (select abc from table2 where def=1) as col2
From Table1 inner join table3 on Table1.id = table3.id
Where col2 = 4
The problem is that the where condition doesn't work. I get an error saying
Invalid column name 'col2'
Kindly help me fix this sql query.
Thanks in advance
You can define it in a CROSS APPLY and then reference in the SELECT and WHERE
SELECT col1,
col2
FROM Table1
INNER JOIN table3
ON Table1.id = table3.id
CROSS APPLY (SELECT abc
FROM table2
WHERE def = 1) C(col2)
WHERE col2 = 4
Using a CTE (Common Table Expression):
WITH SubQuery AS (Col2) {
SELECT
ABC
FROM
table2
WHERE
def = 1
}
SELECT
T.Col1,
S.Col2
FROM
SubQuery S,
Table1 T
INNER JOIN table3 t3
ON T.id = t3.id
WHERE
S.Col2 = 4
Although I must say I agree with the first comment - this makes no sense since your subquery is not correlated (joined) to the rest of your query...

Sql server DELETE and WITH clause

I need to build an SQL statement to delete from certain table the records that match another select statement.
In Teradata we use
delete from table1
where (col1, col2) in (
select col1,col2
from table2
)
While in SQL Server it's not allowed to have more than 1 column in the WHERE..IN clause. I thought I can use the WITH clause:
with tempTable(col1,col2) as (
select col1,col2
from table2
)
delete from table1
where table1.col1 = tempTable.col1
and table1.col2 = tempTable.col2
How to use WITH..DELETE clause? Is there another way?
This should do it:
DELETE Table1
from Table1 t1
inner join tempTable t2
on t2.Col1 = t1.Col1
and t2.Col2 = t1.Col2
First build a query that selects the rows you need:
SELECT t1.*
FROM [Table1] t1
INNER JOIN [Table2] t2 ON t1.[col1] = t2.[col1] AND t1.[Col2]=t2.[Col2]
Test it to make sure it returns exactly the rows you want to delete. Then turn it into a delete statement by changing the "SELECT" to "DELETE" and removing the column list:
DELETE t1
FROM [Table1] t1
INNER JOIN [Table2] t2 ON t1.[col1] = t2.[col1] AND t1.[Col
delete from table1 t1 where exists
(
select 1 from table2 t2 where t1.col1 = t2.col1 and t1.col2 > t2.col2
)
with tempTable(col1,col2) as (
select col1,col2
from table2
)
delete table1 from tempTable
where table1.col1 = tempTable.col1
and table1.col2 = tempTable.col2
This works for me
WITH CTE AS
(
SELECT TOP 50000 *
from v020101hist order by data
)
DELETE FROM CTE

Resources