How can I have a condition in the delete-clause of a merge-statement ?
I want to have user-group-mappings deleted (for the respective user), if the mapping is not present in the source data.
E.g. in the example below, I can do this with:
WHEN NOT MATCHED BY SOURCE
AND A.MUG_USR = 1 -- <== this is the ID of the user I want mappings deleted, but i don't want ONLY ONE id and put it here manually
-- What I actually want here is: WHEN NOT MATCHED BY SOURCE AND A.MUG_USR = CTE.MUG_USR
AND A.MUG_FromSAML = 1
THEN DELETE
But this only works if I insert ONE user at a time and fetch its ID (MUG_USR) manually.
In the example data below, it is critical that the mapping for user 2 and 3 won't get deleted when the mappings for user 1 are deleted.
Note:
I don't want to duplicate the query or create a temporary table (or table variable).
I also don't want a separate delete-statement after the insert statement (see 1)
Duplicating the query or creating a separate delete-statement via temp-table after the insert is something I can do myselfs - I do not need any help with that.
Is this at all possible ?
I would have expected this to work, since I already have
USING CTE ON A.MUG_USR = CTE.MUG_USR AND A.MUG_Group = CTE.MUG_Group
in the using-clause, therefore I expected the delete to be partitioned by this using clause, just like the update.
Example data:
/*
-- DROP TABLE [dbo].Map_User_Groups
CREATE TABLE [dbo].Map_User_Groups
(
MUG_UID uniqueidentifier NOT NULL
,MUG_USR int NULL
,MUG_Group int NULL
,MUG_FromSAML bit NULL
);
*/
TRUNCATE TABLE dbo.Map_User_Groups ;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,1 AS MUG_USR
,1000 AS MUG_Group
,1 AS MUG_FromSAML
;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,2 AS MUG_USR
,2000 AS MUG_Group
,1 AS MUG_FromSAML
;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,3 AS MUG_USR
,3000 AS MUG_Group
,1 AS MUG_FromSAML
;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,4 AS MUG_USR
,4000 AS MUG_Group
,NULL AS MUG_FromSAML
;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,5 AS MUG_USR
,5000 AS MUG_Group
,NULL AS MUG_FromSAML
;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,6 AS MUG_USR
,6000 AS MUG_Group
,0 AS MUG_FromSAML
;
INSERT INTO Map_User_Groups
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
SELECT
NEWID() AS MUG_UID
,7 AS MUG_USR
,7000 AS MUG_Group
,0 AS MUG_FromSAML
;
SELECT * FROM dbo.Map_User_Groups ORDER BY MUG_USR;
;WITH CTE AS
(
SELECT
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
FROM
(
SELECT
NEWID() AS MUG_UID
,1 AS MUG_USR
,7000 AS MUG_Group
,1 AS MUG_FromSAML
UNION
SELECT
NEWID() AS MUG_UID
,33 AS MUG_USR
,33000 AS MUG_Group
,1 AS MUG_FromSAML
) AS tSource
WHERE (1=1)
-- AND EXISTS ( SELECT * FROM dbo.Users AS tAlias1 WHERE (1=1) AND tAlias1.USR_ID = tSource.MUG_USR )
-- AND EXISTS ( SELECT * FROM dbo.Groups AS tAlias2 WHERE (1=1) AND tAlias2.GRP_ID = tSource.MUG_Group )
) -- End CTE
-- SELECT * FROM CTE
MERGE INTO dbo.Map_User_Groups AS A
USING CTE ON A.MUG_USR = CTE.MUG_USR AND A.MUG_Group = CTE.MUG_Group
WHEN MATCHED THEN
UPDATE
SET A.MUG_FromSAML = CTE.MUG_FromSAML
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
MUG_UID
,MUG_USR
,MUG_Group
,MUG_FromSAML
)
VALUES
(
CTE.MUG_UID
,CTE.MUG_USR
,CTE.MUG_Group
,CTE.MUG_FromSAML
)
WHEN NOT MATCHED BY SOURCE
-- AND A.MUG_USR = CTE.MUG_USR -- <== i want something like this
-- AND A.MUG_USR = 1 -- <== this i don't want
AND A.MUG_FromSAML = 1
THEN DELETE
;
SELECT * FROM dbo.Map_User_Groups ORDER BY MUG_USR;
What you do is instead of using MERGE Map_User_Groups you do a CTE2 subquery to only get users you wanna potentially delete:
...
, cte2 AS (
select *
from dbo.Map_User_Groups AS A
where exists(
select 1 from cte c where c.mug_usr = a.mug_usr
)
)
MERGE INTO cte2 AS A
...
This way, you're only working with correct subset.
I use WHERE EXISTS instead of JOIN to avoid "contaminating" target data by duplicate and other merge-unfriendly issues.
With that being said, in my opinion, and i'm probably wrong, one should avoid MERGE if you know what's best for you, because of all the various bugs and quirks of it.
Related
I have this #variable table with an initial row. I'd like to update the following rows based on that first row.
DECLARE #varTable1 Table
(
[id] [int],
[field1] [decimal](18,4)
)
INSERT INTO #varTable1
VALUES
(1,20),
(1,NULL),
(1,NULL),
(1,NULL)
SELECT * FROM #varTable1
Let's just say I want to multiply field1 by 2. So the expected values following inital row would be
1 20
2 40
3 60
4 80
One way is to get the min value and multiple it by a row number - assuming your id column should actually be incrementing rather than all 1's e.g.
WITH cte AS (
SELECT *
, MIN(field1) OVER () * ROW_NUMBER() OVER (ORDER BY id ASC) newField1
FROM #varTable1
)
UPDATE cte SET field1 = newField1;
I guess id is important so you have to multiple field of same id
DECLARE #varTable1 Table
(
[id] [int],
[field1] [decimal](18,4)
)
INSERT INTO #varTable1
VALUES
(1,20),
(1,NULL),
(1,NULL),
(1,NULL),
(2,NULL)
;with CTE as
(
SELECT *,row_number()over(order by id)rn FROM #varTable1
),
CTE1 as
(
select id, min([field1])Minfield from #varTable1
group by [id]
)
select vt.id,vt.field1,c.Minfield*rn from CTE VT
inner join CTE1 c on vt.id=c.id
Code/data:
DECLARE #T TABLE
(
[Col1] VARCHAR(20)
, [RowNum] INT
) ;
INSERT INTO #T
VALUES
( N'second', 1 )
, ( N'fifth', 4 )
, ( N'fourth', 3 )
--, ( N'zzz', 1 )
, ( N'third', 2 )
---- OR when "zzz" is part of this list
--VALUES
-- ( N'second', 2 )
-- , ( N'fifth', 5 )
-- , ( N'fourth', 4 )
-- , ( N'zzz', 1 )
-- , ( N'third', 3 )
SELECT STUFF ((
SELECT ',' + [SQ].[Col1]
FROM
(
SELECT N'zzz' AS [Col1]
, 1 AS [RowNum]
UNION
SELECT [Col1]
, [RowNum]
FROM #T
) AS [SQ]
FOR XML PATH ( '' ), TYPE
).[value] ( '.', 'varchar(MAX)' ), 1, 1, ''
) ;
Current output:
fifth,fourth,second,third,zzz
Goal:
Prepend "zzz," in the output string if missing in the 2nd part of the union AND the values should be in ASC ordered based on the values specified in [rownum] field defined in the 2nd part of the union. If "zzz" exists in the 2nd part of the input already (it will always be RowNum 1 in that case), it should return it only once as the first value.
Expected output:
zzz,second,third,fourth,fifth
UPDATED the requirement due to an error on my part when creating this post. Updated code/data represents more accurate scenario. Please note the RowNum seq in the 2nd part of the UNION, it also starts with 1, but this time, it might or might not be associated to "zzz" Basically, I want to prepend "zzz" in the comma-delimited & ordered output if it doesn't exist.
Hope the below one will help you.
SELECT ',' + [SQ].[Col1]
FROM
(
SELECT N'first' AS [Col1],1 AS [RowNum]
UNION
SELECT [ABC].[Col1],[ABC].[RowNum]
FROM
(
VALUES
( N'second', 2 )
, ( N'fifth', 5 )
, ( N'fourth', 4 )
--, ( N'first', 1 )
, ( N'third', 3 )
) AS [ABC] ( [Col1], [RowNum] )
) AS [SQ]
ORDER BY [RowNum]
FOR XML PATH ( '' ), TYPE
).[value] ( '.', 'varchar(MAX)' ), 1, 1, ''
) ;
Returns an output
first,second,third,fourth,fifth
Attached the Answer for the updated Scenario-
DECLARE #T TABLE
(
[Col1] VARCHAR(20)
, [RowNum] INT
) ;
INSERT INTO #T
VALUES
( N'second', 1 )
, ( N'fifth', 4 )
, ( N'fourth', 3 )
--, ( N'zzz', 1 )
, ( N'third', 2 )
---- OR when "zzz" is part of this list
--VALUES
-- ( N'second', 2 )
-- , ( N'fifth', 5 )
-- , ( N'fourth', 4 )
-- , ( N'zzz', 1 )
-- , ( N'third', 3 )
SELECT STUFF ((
SELECT ',' + [SQ].[Col1]
FROM
(
SELECT N'zzz' AS [Col1]
, 0 AS [RowNum]
UNION
SELECT [Col1]
, [RowNum]
FROM #T
) AS [SQ]
ORDER BY [RowNum]
FOR XML PATH ( '' ), TYPE
).[value] ( '.', 'varchar(MAX)' ), 1, 1, ''
) ;
Returns
zzz,second,third,fourth,fifth
Common Table Expressions (CTEs) provide a handy way of breaking queries down into simpler steps. Note that you can view the results of each step by switching out the last select statement.
with
Assortment as (
-- Start with the "input" rows.
select Col1, RowNum
from ( values ( N'second', 2 ), ( N'fifth', 5 ), ( N'fourth', 4 ),
-- ( N'first', 1 ),
( N'third', 3 ) ) ABC ( Col1, RowNum ) ),
ExtendedAssortment as (
-- Conditionally add "first".
select Col1, RowNum
from Assortment
union all -- Do not remove duplicate rows.
select N'first', 1
where not exists ( select 42 from Assortment where Col1 = N'first' ) )
-- Output the result.
-- Intermediate results may be seen by uncommenting one of the alternate select statements.
-- select * from Assortment;
-- select * from ExtendedAssortment;
select Stuff(
( select N',' + Col1 from ExtendedAssortment order by RowNum for XML path(N''), type).value( N'.[1]', 'NVarChar(max)' ),
1, 1, N'' ) as List;
The same logic can be performed using tables for input:
-- Rows to be included in the comma delimited string.
declare #Input as Table ( Col1 NVarChar(20), RowNum Int );
insert into #Input ( Col1, RowNum ) values
( N'second', 2 ), ( N'fifth', 5 ),
--( N'ZZZ', 17 ), -- Test row.
( N'fourth', 4 ), ( N'third', 3 );
select * from #Input;
-- Mandatory value that must appear in the result. One row only.
declare #Mandatory as Table ( Col1 NVarChar(20), RowNum Int );
-- By using the maximum negative value for an Int this value will be prepended
-- (unless other rows happen to have the same RowNum value).
insert into #Mandatory ( Col1, RowNum ) values ( N'ZZZ', -2147483648 );
select * from #Mandatory;
-- Process the data.
with
AllRows as (
select Col1, RowNum
from #Input
union all
select Col1, RowNum
from #Mandatory
where not exists ( select 42 from #Mandatory as M inner join #Input as I on M.Col1 = I.Col1 ) )
-- Output the result.
-- Intermediate results may be seen by uncommenting the alternate select statement.
--select * from AllRows;
select Stuff(
( select N',' + Col1 from AllRows order by RowNum for XML path(N''), type).value( N'.[1]', 'NVarChar(max)' ),
1, 1, N'' ) as List;
I wonder if anyone can help: I want to convert rows into columns. This is the original table:
I have tried using pivot but this case it is too complex for me.
declare #Table AS TABLE
(
TYPE VARCHAR(100) ,
SERIE VARCHAR(100) ,
CUR1 INT,
CUR2 INT
)
INSERT #Table
( TYPE, SERIE, CUR1, CUR2)
VALUES
( 'CORP', 'S1' ,2122,322 ),
( 'CORP', 'S2' ,321,546 ),
( 'SER', 'S1',543,788 ),
( 'SER', 'S2' ,655, 988 )
I expect the output to be like the attached table:
Please try this, a variant of this will help:-
;with cte as (
select SERIE, [CORP] as [CORP_CUR1], [SER] as [SER_CUR1] from (
select type , serie, cur1 from #Table)
as d
pivot
( max(cur1) for [type] in ( [CORP], [SER]) ) as p
),
ct as (
select SERIE, [CORP] as [CORP_CUR2], [SER] as [SER_CUR2] from (
select type , serie, cur2 from #Table)
as d
pivot
( max(cur2) for [type] in ( [CORP], [SER]) ) as p
)
select cte.SERIE, cte.[CORP_CUR1], cte.[SER_CUR1], ct.[CORP_CUR2], ct.[SER_CUR2] from cte inner join ct on cte.SERIE=ct.SERIE
How to handle empty column in row_number partition over by duplicate count logic?
In the below query, ProgramID = 300 will have an empty ProgramName twice. Although there are other row of records as duplicate, i will have to ignore them. I will have to pick only the Empty ProgramName and it's corresponding ProgramId which is appearing twice has to shown only one set of record.
Expected output from this 6 row of inserted records would be 5 row of record. Among those 5, 1 row of record has the empty program name.
if object_id('tempdb.dbo.#t') is not null
drop table #t
Create table #t
(
ProgramId int,
ProgramName nvarchar(100),
ProgramStatus nvarchar(100)
)
Insert into #t ( ProgramId,ProgramName, ProgramStatus ) values ( 100, 'Test100', 'TestCompleted' )
go
Insert into #t ( ProgramId,ProgramName, ProgramStatus ) values ( 100, 'Test100', 'TestCompleted' )
go
Insert into #t ( ProgramId,ProgramName, ProgramStatus ) values ( 200, 'Test200', 'TestCompleted' )
go
Insert into #t ( ProgramId,ProgramName, ProgramStatus ) values ( 200, 'Test200', 'TestCompleted' )
go
Insert into #t ( ProgramId,ProgramName, ProgramStatus ) values ( 300, '', 'Progress' )
go
Insert into #t ( ProgramId,ProgramName, ProgramStatus ) values ( 300, '', 'TestCompleted' )
go
select * from #t
This isn't really about the ROW_NUMBER() as such, the question seems to be about writing up the WHERE clause downstream...
with selector as (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY ProgramName ORDER BY ProgramStatus DESC) as rn
-- though it would be good to order by something more useful, like timestamp
)
select *
from selector
where rn=1 OR ProgramName != ''
Will breaking your query into two help?
SELECT
*
FROM
#t
WHERE
ProgramName <> ''
UNION ALL
SELECT
ProgramId,
ProgramName,
MAX(ProgramStatus)
FROM
#t
WHERE
ProgramName = ''
GROUP BY
ProgramId,
ProgramName
This is getting your desired output, but I just picked one of the two ProgramStatuses based on Max(), you might have to change that
I have a simple query that is returning records where "column2" > 0
Here is the data in the database
Column1 Column2
1 123456789
2 123456781
3 13-151-1513
4 alsdjf
5
6 000000000
Her is the query
select column1, replace(a.Payroll_id,'-','')
from table1
where isnumeric(column2) = 1
I'd like to return the following:
Column1 Column2
1 123456789
2 123456781
3 131511513
This mean, I won't select any records when the column is blank (or null), will not return a row if it's not an integer, and will drop out the '-', and would not show row 6 since it's all 0.
How can I do this?
I think you can use something like this :
USE tempdb
GO
CREATE TABLE #Temp
(
ID INT IDENTITY
,VALUE VARCHAR(30)
)
INSERT INTO #Temp (VALUE) VALUES ('1213213'), ('1213213'), ('121-32-13'), ('ASDFASF2123')
GO
WITH CteData
AS
(
SELECT REPLACE(VALUE,'-','') as Valor FROM #Temp
)
SELECT * FROM CteData WHERE (ISNUMERIC(Valor) = 1 AND valor not like '%[0-0]%')
DROP TABLE #Temp
then you can apply validations for empty, NULL,0 etc
If you are using SQL2012 or above you can also use TRY_PARSE that is more selective in its parsing. This function will return NULL if a record can't be converted. You could use it like this:
CREATE TABLE #temp
(
ID INT IDENTITY ,
VALUE VARCHAR(30)
)
INSERT INTO #temp
( VALUE )
VALUES ( '1213213' ),
( '1213213' ),
( '121-32-13' ),
( 'ASDFASF2123' ),
( '0000000' )
SELECT ParsedValue
FROM #temp
CROSS APPLY ( SELECT TRY_PARSE(
Value AS INT ) AS ParsedValue
) details
WHERE ParsedValue IS NOT NULL
AND ParsedValue>0