Merge causes too slow, how to optimize this query - sql

How can I optimize this query. I am using merge to pull inserted id's
I have looked at execution plan. It was causing more cost on sort.
Is there any other way to do this instead of using merge. I have tried using BY TARGET. Still its slow. I want to get ride of this merge statement.
DECLARE #TEMP_STUDENT_DETAILS_ID AS TABLE (STUDENT_DETAILS_ID INT, SCHOOL_ID INT)
MERGE INTO DBO.STUDENT_DETAILS USING (
SELECT ISNULL(SUM(MARKS),0) AS MARKS, SCHOOL_ID
FROM DBO.OLD_STUDENT_DETAILS
WHERE SCHOOL_ID IS NOT NULL
GROUP BY SCHOOL_ID) SRC ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT (MARKS
,SCHOOL_ID
,CODE_ID
,CODE_VALUE
,CREATED_BY
,CREATED_DATE
,MODIFIED_BY
,MODIFIED_DATE)
VALUES (SRC.MARKS
,SRC.SCHOOL_ID
,101 --CODE_ID
,'ADA' --CODE_VALUE
,'Admin'
,GETDATE()
,'Admin'
,GETDATE())
OUTPUT INSERTED.STUDENT_DETAILS_ID, SRC.SCHOOL_ID INTO #TEMP_STUDENT_DETAILS_ID;

You can use the OUTPUT statement with an insert. https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql This is certainly a bit cleaner than a merge with a forced not matching.
INSERT DBO.STUDENT_DETAILS
( MARKS
,SCHOOL_ID
,CODE_ID
,CODE_VALUE
,CREATED_BY
,CREATED_DATE
,MODIFIED_BY
,MODIFIED_DATE
)
OUTPUT INSERTED.STUDENT_DETAILS_ID, INSERTED.SCHOOL_ID INTO #TEMP_STUDENT_DETAILS_ID
SELECT ISNULL(SUM(MARKS),0) AS MARKS
, SCHOOL_ID
,101 --CODE_ID
,'ADA' --CODE_VALUE
,'Admin'
,GETDATE()
,'Admin'
,GETDATE()
FROM DBO.OLD_STUDENT_DETAILS
WHERE SCHOOL_ID IS NOT NULL
GROUP BY SCHOOL_ID

Related

Insert into select Subquery returned more than 1 value

I have the following code and it give me an error when the table #ListaDeProducto has more than 1 row. Any idea?
insert into Solicitud_Plastico_Interna_Detalle(
IDSolicitud_Plastico_Interna
,IDTipo_Producto
,Cantidad_Solicitada
,Create_User
,Create_Date
,Contingencia
,Total
)
select
#IdSolicitud
,IDTipo_Producto
,Cantidad_Requerida
,#USUARIO
,getdate()
,Contingencia
,Total
from #ListaDeProducto
Table schema
CREATE TYPE [ListaProductoTableType2] AS TABLE
(
IDTipo_Producto int,
Tipo_Producto varchar(1000),
Cantidad_Requerida int,
Contingencia int ,
Total int,
IdSolicitud_batch varchar(100)
)
GO
I still will bet there is some trigger in the table.
So why you dont try create a new table to prove this query is ok with multiple rows
CREATE TABLE Solicitud_Plastico_Temporal AS (
select
#IdSolicitud as IDSolicitud_Plastico_Interna
,IDTipo_Producto
,Cantidad_Requerida
,#USUARIO as Create_User
,getdate() as Create_Date
,Contingencia
,Total
from #ListaDeProducto
)

How to write proper insert query with select subquery returning more than one row?

I have a problem with SQL. I looked all over stackoverflow and didn't find a solution for my problem. I have an insert statement which selects from different table where in "where" statement it looks in different table, etc.
There is a problem, that one of the subqueries return more than one value. What I need to achieve: there will be inserted two (or more, depends how much it will return) rows in the designated table.
Example with error, what I'mtrying to achieve:
create database tst;
/*1*/
create table person_account_table(
per_id varchar(50),
acct_id varchar(50)
);
insert into person_account_table(per_id, acct_id)
values('123','123');
insert into person_account_table(per_id, acct_id)
values('321','321');
insert into person_account_table(per_id, acct_id)
values('321','363');
/*1*/
/*2*/
create table person_id_table(
per_id varchar(50),
per_nbr varchar(50),
type_cd varchar(50)
);
insert into person_id_table(per_id, per_nbr, type_cd)
values ('123', 'zx32', 'good');
insert into person_id_table(per_id, per_nbr, type_cd)
values ('123', '32zx', 'pklx');
insert into person_id_table(per_id, per_nbr, type_cd)
values ('321', '35xcz', 'good');
insert into person_id_table(per_id, per_nbr, type_cd)
values ('321', 'fes235', 'pklx');
/*2*/
/*3*/
create table table_one(
au_id varchar(50),
type_cd varchar(50),
acct_id varchar(50)
);
insert into table_one(au_id, type_cd, acct_id)
values('1', 'e-pg', '321');
insert into table_one(au_id, type_cd, acct_id)
values('2', 'e-pg', '363');
insert into table_one(au_id, type_cd, acct_id)
values('3', 'e-pg', '123');
/*3*/
/*4*/
create table table_two(
per_nbr varchar(50),
ob_nbr varchar(50),
flag varchar(1)
);
insert into table_two(per_nbr, ob_nbr, flag)
values('zx32', 'dfas', 'N');
insert into table_two(per_nbr, ob_nbr, flag)
values('zx32', 'dfsvgd', 'P');
insert into table_two(per_nbr, ob_nbr, flag)
values('zx32', 'dsfds', 'N');
insert into table_two(per_nbr, ob_nbr, flag)
values('zx32', 'sdfdsf', 'P');
insert into table_two(per_nbr, ob_nbr, flag)
values('35xcz', 'dhf', 'N');
insert into table_two(per_nbr, ob_nbr, flag)
values('35xcz', 'tes', 'N');
insert into table_two(per_nbr, ob_nbr, flag)
values('35xcz', 'dfgdf', 'P');
insert into table_two(per_nbr, ob_nbr, flag)
values('35xcz', 'ehdhs', 'P');
/*4*/
-- table in which I want to inser data
create table result_table(
obj_nbr varchar(50),
au_id varchar(50)
);
insert statement:
insert into result_table (obj_nbr, au_id)
select tt.ob_nbr,
(
select to1.au_id
from table_one to1
where to1.acct_id in
(
select pat.acct_id
from person_account_table pat
where pat.per_id in
(
select pit.per_id
from person_id_table pit
where pit.per_nbr = tt.per_nbr and
pit.type_cd in ('good')
)
)
)
from table_two tt
where tt.flag = 'N';
And I get in result: Subquery returns more than 1 row
In this particular example, I get this error, because per_id = 321 has two different account. And each account gets it's own au_id from table_one.
How can I rewrite this query, that it will not crash, but insert two rows in the result table?
Here is the solution using inner joins. It returns the same result as #Dimitry but does not use SQL89 syntax. (very old)
insert into result_table (obj_nbr, au_id)
select
tt.ob_nbr,
to1.au_id
from
table_one to1
inner join person_account_table pat on to1.acct_id = pat.acct_id
inner join person_id_table pit on pat.per_id = pit.per_id
inner join table_two tt on tt.per_nbr = person_id_table.per_nbr
where
pit.type_cd in ('good')
and tt.flag = 'N';
Your query can be turned to this:
insert into result_table (obj_nbr, au_id)
select tt.ob_nbr,
to1.au_id
from person_account_table pat,
person_id_table pit,
table_one to1,
table_two tt
where pat.per_id = pit.per_id
and pit.per_nbr = tt.per_nbr
and to1.acct_id = pat.acct_id
and pit.type_cd in ('good')
and tt.flag = 'N';
But it returns 6 rows (check on sqlfiddle.com).
EDIT
As Dave Costa noted in comments, my query can produce more rows in a case of duplicates. I didn't study your test data, because it is quite complicated, but you have a lot of rows with duplicates in columns, which are used to connect tables. It can be reason of your error and redundant rows in mine. So it can be one of two possibilities: your query should really return 6 rows or you need more conditions to specify what you need. In second case it will depend on meaning of your data and desired result.

Prevent insertion of duplicates

I have a table with columns CompanyID, EmployeeCode. EmployeeCode is unique and CompanyID can be repeated. So we can keep a list of employees in Google, Apple etc.
I want a way to ensure that the same employee cannot be added if he/she is already in the database. What would be a good way to do this ? I'd prefer not to create additional columns or tables for this.
Create a UNIQUE constraint on the column where you want to prevent duplicates:
CREATE UNIQUE INDEX [IX_YourTableName_EmployeeCode] ON [YourTableName] (EmployeeCode)
Try this..
Create table UniqueTable
(
CompanyID int,
EmployeeCode int
)
--Insert dummy data
INSERT INTO UniqueTable(EmployeeCode ,CompanyID ) Values(1,200)
--When are Going to insert duplicate Ecmployeecode '1' then it will not insert data into table
INSERT INTO UniqueTable(EmployeeCode ,CompanyID )
Select 1,200 From UniqueTable t1
Left join UniqueTable t2 on 1=t2.EmployeeCode
WHERE t2.EmployeeCode IS NULL
--We are Going to insert different Ecmployeecode '2' it will insert into table
INSERT INTO UniqueTable(EmployeeCode ,CompanyID )
Select 2,300 From UniqueTable t1
Left join UniqueTable t2 on 2=t2.EmployeeCode
WHERE t2.EmployeeCode IS NULL
Try this
Create PROCEDURE [dbo].[sp_Emp_Insert]
#EmployeeCode nvarchar(50)
As
Begin
if Not Exists (select EmployeeCode from YOUR_TABlE where EmployeeCode =#EmployeeCode )
Begin
Insert QUERY
End
else
Return 0
End
End

looping in sql where certain value is equal to something

im trying to insert values into a table in sql in one run.
INSERT INTO sampleTable
(
,ID
,aa
,bb
,cc
,dd
,ee
)
SELECT
,(select id from otherTable where value="something")
,aa
,bb
,cc
,dd
,ee
how do i loop it in sql that it inserts values for each id on the otherTable?
INSERT INTO sampleTable
(
,ID
,aa
,bb
,cc
,dd
,ee
)
SELECT
,id
,aa
,bb
,cc
,dd
,ee
from otherTable where value="something"
Explanation: If you want to SELECT..INSERT multiple lines (a set) you need to have multiple lines in your SELECT statement. This will only work with a FROM part of the query.
The best way to test INSERT..SELECT is to remove the insert part and see if it works by itself. Once you are happy with the result you can add the INSERT part in front of it.

How do I do an Upsert Into Table?

I have a view that has a list of jobs in it, with data like who they're assigned to and the stage they are in. I need to write a stored procedure that returns how many jobs each person has at each stage.
So far I have this (simplified):
DECLARE #ResultTable table
(
StaffName nvarchar(100),
Stage1Count int,
Stage2Count int
)
INSERT INTO #ResultTable (StaffName, Stage1Count)
SELECT StaffName, COUNT(*) FROM ViewJob
WHERE InStage1 = 1
GROUP BY StaffName
INSERT INTO #ResultTable (StaffName, Stage2Count)
SELECT StaffName, COUNT(*) FROM ViewJob
WHERE InStage2 = 1
GROUP BY StaffName
The problem with that is that the rows don't combine. So if a staff member has jobs in stage1 and stage2 there's two rows in #ResultTable. What I would really like to do is to update the row if one exists for the staff member and insert a new row if one doesn't exist.
Does anyone know how to do this, or can suggest a different approach?
I would really like to avoid using cursors to iterate on the list of users (but that's my fall back option).
I'm using SQL Server 2005.
Edit: #Lee: Unfortunately the InStage1 = 1 was a simplification. It's really more like WHERE DateStarted IS NOT NULL and DateFinished IS NULL.
Edit: #BCS: I like the idea of doing an insert of all the staff first so I just have to do an update every time. But I'm struggling to get those UPDATE statements correct.
Actually, I think you're making it much harder than it is. Won't this code work for what you're trying to do?
SELECT StaffName, SUM(InStage1) AS 'JobsAtStage1', SUM(InStage2) AS 'JobsAtStage2'
FROM ViewJob
GROUP BY StaffName
You could just check for existence and use the appropriate command. I believe this really does use a cursor behind the scenes, but it's the best you'll likely get:
IF (EXISTS (SELECT * FROM MyTable WHERE StaffName = #StaffName))
begin
UPDATE MyTable SET ... WHERE StaffName = #StaffName
end
else
begin
INSERT MyTable ...
end
SQL2008 has a new MERGE capability which is cool, but it's not in 2005.
IIRC there is some sort of "On Duplicate" (name might be wrong) syntax that lets you update if a row exists (MySQL)
Alternately some form of:
INSERT INTO #ResultTable (StaffName, Stage1Count, Stage2Count)
SELECT StaffName,0,0 FROM ViewJob
GROUP BY StaffName
UPDATE #ResultTable Stage1Count= (
SELECT COUNT(*) AS count FROM ViewJob
WHERE InStage1 = 1
#ResultTable.StaffName = StaffName)
UPDATE #ResultTable Stage2Count= (
SELECT COUNT(*) AS count FROM ViewJob
WHERE InStage2 = 1
#ResultTable.StaffName = StaffName)
To get a real "upsert" type of query you need to use an if exists... type of thing, and this unfortunately means using a cursor.
However, you could run two queries, one to do your updates where there is an existing row, then afterwards insert the new one. I'd think this set-based approach would be preferable unless you're dealing exclusively with small numbers of rows.
The following query on your result table should combine the rows again. This is assuming that InStage1 and InStage2 are never both '1'.
select distinct(rt1.StaffName), rt2.Stage1Count, rt3.Stage2Count
from #ResultTable rt1
left join #ResultTable rt2 on rt1.StaffName=rt2.StaffName and rt2.Stage1Count is not null
left join #ResultTable rt3 on rt1.StaffName=rt2.StaffName and rt3.Stage2Count is not null
I managed to get it working with a variation of BCS's answer. It wouldn't let me use a table variable though, so I had to make a temp table.
CREATE TABLE #ResultTable
(
StaffName nvarchar(100),
Stage1Count int,
Stage2Count int
)
INSERT INTO #ResultTable (StaffName)
SELECT StaffName FROM ViewJob
GROUP BY StaffName
UPDATE #ResultTable SET
Stage1Count= (
SELECT COUNT(*) FROM ViewJob V
WHERE InStage1 = 1 AND
V.StaffName = #ResultTable.StaffName COLLATE Latin1_General_CI_AS
GROUP BY V.StaffName),
Stage2Count= (
SELECT COUNT(*) FROM ViewJob V
WHERE InStage2 = 1 AND
V.StaffName = #ResultTable.StaffName COLLATE Latin1_General_CI_AS
GROUP BY V.StaffName)
SELECT StaffName, Stage1Count, Stage2Count FROM #ResultTable
DROP TABLE #ResultTable