Aggregate column based on comparison with array in Hive - sql

Assume I have a hive table that looks like this:
|ID |CODE |AMT |NEW AMT|
|---|---------------|-----|-------|
|1 |['a','b',,,] |10 | 50 |
|2 |[,,,'a','b'] |20 | 70 |
|3 |[,'c','d','e',]|30 | 20 |
|4 |['p','q',,,] |40 | 20 |
The code column is of an array datatype. It can have 5 values and these values are being populated by an ETLjob. These values are comma separated.
I need to find the aggregated value of AMT column keeping the following conditions in place:
if the code has values 'a', 'b' then the value in amount for that id should be zero.
if the code has values 'c','d','e' then the value in amount should be replaced with the value
that is in new amt.
if it doesn't match either of the above conditions, the value should be same as that in amt.
After this, the sum of amt can be taken. So with the table given above, the sum(amt) should be 60.
I have been struggling with this as I am new to hql/sql.
I have tried summing up using a case statement but failed.
Thank you for any input you may have!

"The code column is of an array datatype."
Use array_contains() function with case expressions:
select t.id, t.code,
case when array_contains(t.code, 'a') and array_contains(t.code, 'b') then 0
when array_contains(t.code, 'c') and array_contains(t.code, 'd') and array_contains(t.code, 'e') then t.new_amt
else t.amt
end AMT
from table_name t

Just use if else or case when
Lets create a table with the sample data you provided
CREATE TABLE `table1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` longtext NOT NULL,
`amt` int(11) NOT NULL,
`new_amt` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `table1` (`id`, `code`, `amt`, `new_amt`) VALUES
(1, '[\'a\',\'b\',,,]', 10, 50),
(2, '[,,,\'a\',\'b\']', 20, 70),
(3, '[,\'c\',\'d\',\'e\',]', 30, 20),
(4, '[\'p\',\'q\',,,]', 40, 20);
See how the table looks like SELECT * FROM table1
id
code
amt
new_amt
1
['a','b',,,]
10
50
2
[,,,'a','b']
20
70
3
[,'c','d','e',]
30
20
4
['p','q',,,]
40
20
Now use if else to decide the value
SELECT
`code`,
IF(
`code` LIKE "%a','b%",
0,
IF(
`code` LIKE "%c','d','e%",
`new_amt`,
`amt` + `new_amt`
)
) AS price
FROM
`table1`
Result :
id
code
price
1
['a','b',,,]
0
2
[,,,'a','b']
0
3
[,'c','d','e',]
20
4
['p','q',,,]
60

Related

HQL, insert two rows if a condition is met

I have the following table called table_persons in Hive:
+--------+------+------------+
| people | type | date |
+--------+------+------------+
| lisa | bot | 19-04-2022 |
| wayne | per | 19-04-2022 |
+--------+------+------------+
If type is "bot", I have to add two rows in the table d1_info else if type is "per" i only have to add one row so the result is the following:
+---------+------+------------+
| db_type | info | date |
+---------+------+------------+
| x_bot | x | 19-04-2022 |
| x_bnt | x | 19-04-2022 |
| x_per | b | 19-04-2022 |
+---------+------+------------+
How can I add two rows if this condition is met?
with a Case When maybe?
You may try using a union to merge or duplicate the rows with bot. The following eg unions the first query which selects all records and the second query selects only those with bot.
Edit
In response to the edited question, I have added an additional parity column (storing 1 or 0) named original to differentiate the duplicate entry named
SELECT
p1.*,
1 as original
FROM
table_persons p1
UNION ALL
SELECT
p1.*,
0 as original
FROM
table_persons p1
WHERE p1.type='bot'
You may then insert this into your other table d1_info using the above query as a subquery or CTE with the desired transformations CASE expressions eg
INSERT INTO d1_info
(`db_type`, `info`, `date`)
WITH merged_data AS (
SELECT
p1.*,
1 as original
FROM
table_persons p1
UNION ALL
SELECT
p1.*,
0 as original
FROM
table_persons p1
WHERE p1.type='bot'
)
SELECT
CONCAT('x_',CASE
WHEN m1.type='per' THEN m1.type
WHEN m1.original=1 AND m1.type='bot' THEN m1.type
ELSE 'bnt'
END) as db_type,
CASE
WHEN m1.type='per' THEN 'b'
ELSE 'x'
END as info,
m1.date
FROM
merged_data m1
ORDER BY m1.people,m1.date;
See working demo db fiddle here
I think what you want is to create a new table that captures your logic. This would simplify your query and make it so you could easily add new types without having to edit logic of a case statement. It may also make it cleaner to view your logic later.
CREATE TABLE table_persons (
`people` VARCHAR(5),
`type` VARCHAR(3),
`date` VARCHAR(10)
);
INSERT INTO table_persons
VALUES
('lisa', 'bot', '19-04-2022'),
('wayne', 'per', '19-04-2022');
CREATE TABLE info (
`type` VARCHAR(5),
`db_type` VARCHAR(5),
`info` VARCHAR(1)
);
insert into info
values
('bot', 'x_bot', 'x'),
('bot', 'x_bnt', 'x'),
('per','x_per','b');
and then you can easily do a join:
select
info.db_type,
info.info,
persons.date date
from
table_persons persons inner join info
on
info.type = persons.type

Sum all values of column A, find the first value of column B ordered by C, compare the sum with this first value to return a boolean

REPRODUCIBLE EXAMPLE HERE
I need to sum all values VALUEX returned by a select with three where clauses, compare with another column value and return true or false, like this:
SELECT
// sum all VALUEX found (will be a list) and sum this result with a value passed by user (I am using JDBCTemplate to create the query and set parameters)
// compare this sum with the first VALUEY found ordered by COLUMNCLAUSEORDER,
// in the case that the sum is greater return true, else false
FROM
mytable
WHERE
VALUECLAUSEONE = '587c97e1-1fac'
AND VALUECLAUSETWO > '2021-02-03'
AND VALUECLAUSETHREE IN ('40', '20')
ORDER BY
COLUMNCLAUSEORDER
The query below returns the result as expected (although it doesn't return a bool), but I'm doing almost two times the same select. Is there a "prettier"/more efficient way to do that?
SELECT
CASE WHEN t.m < (SELECT (SUM(COALESCE(CAST(VALUEX as DECIMAL(9,2)),0)) +
COALESCE(CAST(10 as DECIMAL(9,2)),0))
FROM #mytable
WHERE VALUECLAUSEONE = '587c97e1-1fac' AND VALUECLAUSETWO > '2021-02-03' AND
VALUECLAUSETHREE IN ('40','20'))
then '1' else '0' end
FROM
(SELECT MAX(fv.f) as m from
(SELECT
FIRST_VALUE(VALUEY) OVER (ORDER BY COLUMNCLAUSEORDER) as f
FROM #mytable
WHERE VALUECLAUSEONE = '587c97e1-1fac'
AND VALUECLAUSETWO > '2021-02-03'
AND VALUECLAUSETHREE IN ('40','20'))fv)t
PS: It's for prod purposes, so I can't write it as a procedure
PS2: the number 10 in here COALESCE(CAST(10 as DECIMAL(9,2)),0)) represents the value passed by user.
Columns data types:
VALUEX decimal(9, 2)
VALUEY decimal(9, 2)
VALUECLAUSEONE string
VALUECLAUSETWO datetime
VALUECLAUSETHREE string
COLUMNCLAUSEORDER integer
Sample of data, and an example of expected result:
MY_TABLE
|ID | VALUEX | VALUEY | VALUECLAUSEONE | VALUECLAUSETWO | VALUECLAUSETHREE | COLUMNCLAUSEORDER |
|1 | 10 | 33 |'587c97e1-1fac' | '2021-02-03' | '20' | 1 |
|2 | 15 | 2 |'587c97e1-1fac' | '2021-02-03' | '40' | 2 |
|3 | 5 | 40 |'587c97e1-1fac' | '2021-02-04' | '40' | 4 |
|4 | 1 | 1 |'587c97e1-1fac' | '2021-02-07' | '20' | 5 |
|5 | 30 | 1 |'587c97e1-1fac' | '2021-02-09' | '40' | 6 |
|66 | 30 | 1 |'587c97e1-1fac' | '2021-02-09' | '88' | 6 | <- will not count, doesn't match with where clauses
User passed the number 10 by parameter to the query.
Here, I'll sum all the VALUEX results = 61
After, sum with the number 10 passed by the user = 71
The first VALUEY found in ORDER BY COLUMNCLAUSEORDER is 33. So, I compare:
IF 71 (sum of all VALUEX + param) > 33 (FIRST VALUEY order by COLUMNCLAUSEORDER) returns true
ELSE returns false
In this example, the query returns true.
How the table structure looks like, and the inserts:
CREATE TABLE #mytable (
ID INTEGER NOT NULL PRIMARY KEY
,VALUEX INTEGER NOT NULL
,VALUEY INTEGER
,VALUECLAUSEONE VARCHAR(14) NOT NULL
,VALUECLAUSETWO DATETIME NOT NULL
,VALUECLAUSETHREE VARCHAR(4) NOT NULL
,COLUMNCLAUSEORDER INTEGER NOT NULL
);
INSERT INTO #mytable(ID,VALUEX,VALUEY,VALUECLAUSEONE,VALUECLAUSETWO,VALUECLAUSETHREE,COLUMNCLAUSEORDER) VALUES (1,10,33,'587c97e1-1fac','2021-02-03','20',1);
INSERT INTO #mytable(ID,VALUEX,VALUEY,VALUECLAUSEONE,VALUECLAUSETWO,VALUECLAUSETHREE,COLUMNCLAUSEORDER) VALUES (2,15,2,'587c97e1-1fac','2021-02-03','40',2);
INSERT INTO #mytable(ID,VALUEX,VALUEY,VALUECLAUSEONE,VALUECLAUSETWO,VALUECLAUSETHREE,COLUMNCLAUSEORDER) VALUES (3,5,40,'587c97e1-1fac','2021-02-04','40',4);
INSERT INTO #mytable(ID,VALUEX,VALUEY,VALUECLAUSEONE,VALUECLAUSETWO,VALUECLAUSETHREE,COLUMNCLAUSEORDER) VALUES (4,1,1,'587c97e1-1fac','2021-02-07','20',5);
INSERT INTO #mytable(ID,VALUEX,VALUEY,VALUECLAUSEONE,VALUECLAUSETWO,VALUECLAUSETHREE,COLUMNCLAUSEORDER) VALUES (5,30,1,'587c97e1-1fac','2021-02-09','40',6);
INSERT INTO #mytable(ID,VALUEX,VALUEY,VALUECLAUSEONE,VALUECLAUSETWO,VALUECLAUSETHREE,COLUMNCLAUSEORDER) VALUES (6,30,1,'587c97e1-1fac','2021-02-09','88',6);
Assuming that all you are trying to do is avoid repeating the same query then just add your SUM as a window function to the main query e.g.
DECLARE #UserValue int = -20; -- use a variable then modify to test
SELECT
MAX(CASE WHEN s.vd > s.vd1 THEN '1' ELSE '0' END) TrueFalseValue
FROM (
SELECT FIRST_VALUE(VALUEY) OVER (ORDER BY COLUMNCLAUSEORDER) AS vd
-- Sum it all here
, SUM(VALUEX) OVER () + COALESCE(#UserValue,0) vd1
FROM #mytable
WHERE VALUECLAUSEONE = '587c97e1-1fac'
AND VALUECLAUSETWO > '2021-02-03'
AND VALUECLAUSETHREE IN ('40','20')
) s;
Also you don't need all your CASTs - the original datatypes are fine. And using MAX at the very end seems cleaner.

Selecting records where two rows are met but if not select them all

I have a table like this where i want to select all records except where two columns match certain values
id Signal_Type Input
1 Alarm 2
1 Alarm 4
1 Video 1
2 Alarm 4
2 Video 5
2 Alarm 7
I would like to select all records where ID = 1 and where Signal_Type != Alarm and Input != 2, but if both the Signal_Type and input arent matched then still show the record.
SELECT *
FROM ReceiverAlarms
WHERE id = '1' AND (Signal_Type != 'Alarm' AND Input != '2')
I expected this query to return
id Signal_Type Input
1 Alarm 4
1 Video 1
but instead it returns nothing.
Im sure there will be other questions on here asking for that but i wasnt sure how to word it to find what i wanted.
"if both the type and input arent matched then still show the record."
That's an OR
(Type != 'Alarm' OR Input != '2')
Below demo is for MySQL, but this will work for any DBMS
Schema (MySQL v5.7)
CREATE TABLE ReceiverAlarms (
`id` INTEGER,
`Type` VARCHAR(5),
`Input` INTEGER
);
INSERT INTO ReceiverAlarms
(`id`, `Type`, `Input`)
VALUES
(1, 'Alarm', 2),
(1, 'Alarm', 4),
(1, 'Video', 1),
(2, 'Alarm', 4),
(2, 'Video', 5),
(2, 'Alarm', 7);
Query #1
SELECT *
FROM ReceiverAlarms
WHERE id = '1' AND (Type != 'Alarm' OR Input != '2');
Output
| id | Type | Input |
| --- | ----- | ----- |
| 1 | Alarm | 4 |
| 1 | Video | 1 |
View on DB Fiddle
You can try below way -
select id, type,max(input) as input
from tablename
where id=1
group by id,type

BINARY_CHECKSUM - different result depending on number of rows

I wonder why the BINARY_CHECKSUM function returns different result for the same:
SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, NULL, 100),
(2, NULL, NULL),
(3, 1, 2)) s(id,a,b);
SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, NULL, 100),
(2, NULL, NULL)) s(id,a,b);
Ouput:
+-----+----+------+-------------+
| id | a | b | bc |
+-----+----+------+-------------+
| 1 | | 100 | -109 |
| 2 | | | -2147483640 |
| 3 | 1 | 2 | 18 |
+-----+----+------+-------------+
-- -109 vs 100
+-----+----+------+------------+
| id | a | b | bc |
+-----+----+------+------------+
| 1 | | 100 | 100 |
| 2 | | | 2147483647 |
+-----+----+------+------------+
And for second sample I get what I would anticipate:
SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, 1, 100),
(2, 3, 4),
(3,1,1)) s(id,a,b);
SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, 1, 100),
(2, 3, 4)) s(id,a,b);
Ouptut for both first two rows:
+-----+----+------+-----+
| id | a | b | bc |
+-----+----+------+-----+
| 1 | 1 | 100 | 116 |
| 2 | 3 | 4 | 52 |
+-----+----+------+-----+
db<>fiddle demo
It has strange consequences when I want to compare two tables/queries:
WITH t AS (
SELECT 1 AS id, NULL AS a, 100 b
UNION ALL SELECT 2, NULL, NULL
UNION ALL SELECT 3, 1, 2 -- comment this out
), s AS (
SELECT 1 AS id ,100 AS a, NULL as b
UNION ALL SELECT 2, NULL, NULL
UNION ALL SELECT 3, 2, 1 -- comment this out
)
SELECT t.*,s.*
,BINARY_CHECKSUM(t.a, t.b) AS bc_t, BINARY_CHECKSUM(s.a, s.b) AS bc_s
FROM t
JOIN s
ON s.id = t.id
WHERE BINARY_CHECKSUM(t.a, t.b) = BINARY_CHECKSUM(s.a, s.b);
db<>fiddle demo2
For 3 rows I get single result:
+-----+----+----+-----+----+----+--------------+-------------+
| id | a | b | id | a | b | bc_t | bc_s |
+-----+----+----+-----+----+----+--------------+-------------+
| 2 | | | 2 | | | -2147483640 | -2147483640 |
+-----+----+----+-----+----+----+--------------+-------------+
but for 2 rows I get also id = 1:
+-----+----+------+-----+------+----+-------------+------------+
| id | a | b | id | a | b | bc_t | bc_s |
+-----+----+------+-----+------+----+-------------+------------+
| 1 | | 100 | 1 | 100 | | 100 | 100 |
| 2 | | | 2 | | | 2147483647 | 2147483647 |
+-----+----+------+-----+------+----+-------------+------------+
Remarks:
I am not searching for alternatives like(HASH_BYTES/MD5/CHECKSUM)
I am aware that BINARY_CHECKSUM could lead to collisions(two different calls produce the same output) here scenario is a bit different
For this definition, we say that null values, of a specified type,
compare as equal values. If at least one of the values in the
expression list changes, the expression checksum can also change.
However, this is not guaranteed. Therefore, to detect whether values
have changed, we recommend use of BINARY_CHECKSUM only if your
application can tolerate an occasional missed change.
It is strange for me that hash function returns different result for the same input arguments.
Is this behaviour by design or it is some kind of glitch?
EDIT:
As #scsimon
points out it works for materialized tables but not for cte.
db<>fiddle actual table
Metadata for cte:
SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set('
SELECT *
FROM (VALUES(1, NULL, 100),
(2, NULL, NULL),
(3, 1, 2)) s(id,a,b)', NULL,0);
SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set('
SELECT *
FROM (VALUES(1, NULL, 100),
(2, NULL, NULL)) s(id,a,b)', NULL,0)
-- working workaround
SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set('
SELECT *
FROM (VALUES(1, cast(NULL as int), 100),
(2, NULL, NULL)) s(id,a,b)', NULL,0)
For all cases all columns are INT but with explicit CAST it behaves as it should.
db<>fidde metadata
This has nothing to do with the number of rows. It is because the values in one of the columns of the 2-row version are always NULL. The default type of NULL is int and the default type of a numeric constant (of this length) is int, so these should be comparable. But from a values() derived table, these are (apparently) not exactly the same type.
In particular, a column with only typeless NULLs from a derived table is not comparable, so it is excluded from the binary checksum calculation. This does not occur in a real table, because all columns have types.
The rest of the answer illustrates what is happening.
The code behaves as expected with type conversions:
SELECT *, BINARY_CHECKSUM(a, b) AS bc
FROM (VALUES(1, cast(NULL as int), 100),
(2, NULL, NULL)
) s(id,a,b);
Here is a db<>fiddle.
Actually creating tables with the values suggests that columns with only NULL values have exactly the same type as columns with explicit numbers. That suggests that the original code should work. But an explicit cast also fixes the problem. Very strange.
This is really, really strange. Consider the following:
select v.*, checksum(a, b), checksum(c,b)
FROM (VALUES(1, NULL, 100, NULL),
(2, 1, 2, 1.0)
) v(id, a, b, c);
The change in type for "d" affects the binary_checksum() for the second row, but not for the first.
This is my conclusion. When all the values in a column are binary, then binary_checksum() is aware of this and the column is in the category of "noncomparable data type". The checksum is then based on the remaining columns.
You can validate this by seeing the error when you run:
select v.*, binary_checksum(a)
FROM (VALUES(1, NULL, 100, NULL),
(2, NULL, 2, 1.0)
) v( id,a, b, c);
It complains:
Argument data type NULL is invalid for argument 1 of checksum function.
Ironically, this is not a problem if you save the results into a table and use binary_checksum(). The issue appears to be some interaction with values() and data types -- but something that is not immediately obvious in the information_schema.columns table.
The happyish news is that the code should work on tables, even if it does not work on values() generated derived tables -- as this SQL Fiddle demonstrates.
I also learned that a column filled with NULLs really is typeless. The assignment of the int data type in a select into seems to happen when the table is being defined. The "typeless" type is converted to an int.
For the literal NULL without the CAST (and without any typed values in the column) it entirely ignores it and just gives you the same result as BINARY_CHECKSUM(b).
This seems to happen very early on. The initial tree representation output from
SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, NULL, 100),
(2, NULL, NULL)) s(id,a,b)
OPTION (RECOMPILE, QUERYTRACEON 8605, QUERYTRACEON 3604);
Already shows that it has decided to just use one column as input to the function
ScaOp_Intrinsic binary_checksum
ScaOp_Identifier COL: Union1008
This compares with the following output for your first query
ScaOp_Intrinsic binary_checksum
ScaOp_Identifier COL: Union1011
ScaOp_Identifier COL: Union1010
If you try and get the BINARY_CHECKSUM with
SELECT *, BINARY_CHECKSUM(a) AS bc
FROM (VALUES(1, NULL, 100)) s(id,a,b)
It gives the error
Msg 8184, Level 16, State 1, Line 8 Error in binarychecksum. There are
no comparable columns in the binarychecksum input.
This is not the only place where an untyped NULL constant is treated differently from an explicitly typed one.
Another case is
SELECT COALESCE(CAST(NULL AS INT),CAST(NULL AS INT))
vs
SELECT COALESCE(NULL,NULL)
I'd err on the side of "glitch" in this case rather than "by design" though as the columns from the derived table are supposed to be int before they get to the checksum function.
SELECT COALESCE(a,b)
FROM (VALUES(NULL, NULL)) s(a,b)
Does work as expected without this glitch.

SQL. How to insert one select into another select with the ignoring of duplicates and the with changing of Foreign Key in a copy

Original task: Copy lines from one document to another with ignoring duplicates.
Copy from the document with the minimum number to the document with the maximum number.
Assume that there is at least one entry in the header.
When adding, skip (do not add) rows for products that already exist.
Here's the whole code:
CREATE TABLE TOV
(
KTOV INT PRIMARY KEY NOT NULL,
NTOV VARCHAR(MAX) NOT NULL,
SORT VARCHAR(MAX) NOT NULL
);
GO
CREATE TABLE DMZ
(
DDM DATE NOT NULL,
NDM INT PRIMARY KEY NOT NULL,
PR INT NOT NULL
);
GO
CREATE TABLE DMS
(
KTOV INT NOT NULL
FOREIGN KEY REFERENCES TOV(KTOV),
NDM INT NOT NULL
FOREIGN KEY REFERENCES DMZ(NDM),
KOL INT NOT NULL,
CENA DECIMAL(13,2) NOT NULL,
SORT VARCHAR(MAX) NOT NULL
);
GO
INSERT TOV
VALUES
(101, 'Beer', 'Light'),
(102, 'Beer', 'Dark'),
(103, 'Chips', 'With paprika');
go
INSERT DMZ
VALUES
('01.05.2014', 2, 1),
('01.05.2104', 3, 2),
('02.05.2014', 5, 2);
GO
INSERT DMS
VALUES
(101, 2, 100, 8.00, 'Light'),
(102, 3, 80, 9.50, 'Dark'),
(103, 5, 50, 6.50, 'With paprika'),
(101, 2, 1, 10.00, 'Light'),
(103, 3, 1, 8.50, 'With paprika'),
(101, 5, 2, 10, 'Light'),
(102, 3, 1, 11.50, 'Dark'),
(101, 2, 2, 10.50, 'Light'),
(103, 5, 1, 8.60, 'With paprika');
GO
I'm stuck and I can not find a solution.
There is such a pseudo-query:
INSERT INTO (select * from DMS WHERE NDM = (SELECT MIN(NDM) FROM DMS))
FROM (select * from DMS WHERE NDM = (SELECT MAX(NDM) FROM DMS))
ON DUPLICATE KEY UPDATE
There is also such a variant of the query:
CREATE VIEW MINDMS1
AS SELECT * FROM DMS1 WHERE NDM = (SELECT MIN(NDM) FROM DMS1);
CREATE VIEW MAXDMS1
AS SELECT * FROM DMS1 WHERE NDM = (SELECT MAX(NDM) FROM DMS1);
MERGE MAXDMS1 AS MAXD
USING MINDMS1 AS MIND
ON (MAXD.KTOV = MIND.KTOV AND MAXD.NDM > MIND.NDM)
WHEN NOT MATCHED THEN
INSERT (KTOV, NDM, KOL, CENA, SORT)
VALUES (MIND.KTOV, MIND.NDM, MIND.KOL, MIND.CENA, MIND.SORT);
But it works wrong. The data coped to the MINDMS1. But it is need to copy into MAXDMS1. And I don't know how to change ndm in copy(it is foreign key) to MAXDMS1.NDM
No need for merge if this operation is insert only:
Depending on which columns would determine a duplicate, compare them in the where of a not exists() clause. For the example, I am comparing ktov and kol (ktov alone results in no rows inserted)
declare #min_ndm int, #max_ndm int;
select #min_ndm = min(ndm), #max_ndm = max(ndm) from DMS;
insert into dms (ktov, ndm, kol, cena, sort)
output inserted.*
select o.ktov, ndm=#max_ndm, o.kol, o.cena, o.sort
from dms o
where o.ndm = #min_ndm
and not exists (
select 1
from dms i
where i.ndm = #max_ndm
and i.ktov = o.ktov
and i.kol = o.kol
)
dbfiddle.uk demo
inserts the following rows:
+------+-----+-----+-------+-------+
| KTOV | NDM | KOL | CENA | SORT |
+------+-----+-----+-------+-------+
| 101 | 5 | 100 | 8.00 | Light |
| 101 | 5 | 1 | 10.00 | Light |
+------+-----+-----+-------+-------+
If you really want to use merge, then you can use common table expressions for your target and source:
declare #min_ndm int, #max_ndm int;
select #min_ndm = min(ndm), #max_ndm = max(ndm) from DMS;
;with mindms as (select * from dms where ndm = #min_ndm)
, maxdms as (select * from dms where ndm = #max_ndm)
merge into maxdms as t
using mindms as s
on (t.ktov = s.ktov and t.kol = s.kol)
when not matched then
insert values (s.ktov, #max_ndm, s.kol, s.cena, s.sort)
output $action, inserted.*;
dbfiddle.uk demo
returns:
+---------+------+-----+-----+-------+-------+
| $action | KTOV | NDM | KOL | CENA | SORT |
+---------+------+-----+-----+-------+-------+
| INSERT | 101 | 5 | 100 | 8.00 | Light |
| INSERT | 101 | 5 | 1 | 10.00 | Light |
+---------+------+-----+-----+-------+-------+
Some merge issues to be aware of:
Use Caution with SQL Server''s MERGE Statement - Aaron Bertrand
An Interesting MERGE Bug - Paul White