Vertica, Merge and CTE - sql

I need to do Merge in Vertica.
Can I use both merge and CTE in Vertica at the same time? Example below:
WITH cte AS
(
SELECT id,
name
FROM [TableA]
)
MERGE INTO [TableA] AS A
USING cte
ON cte.ID = A.id
WHEN MATCHED
THEN UPDATE
SET A.name = cte.name
WHEN NOT MATCHED
THEN INSERT
VALUES(cte.name);

Does not work as you suggested. And for an insert you need all columns for the target.
Using on a table of mine:
WITH
cte AS (
SELECT
id
, UPPER(fname) AS fname
, UPPER(lname) AS lname
, hire_dt + 7 AS hire_dt
FROM public.foo
)
MERGE
INTO public.foo t
USING cte s
ON s.id = t.id
WHEN MATCHED THEN UPDATE SET
fname = s.fname
, lname = s.lname
, hire_dt = s.hire_dt
WHEN NOT MATCHED THEN INSERT
VALUES (
s.id
, s.fname
, s.lname
, s.hire_dt
);
-- out ERROR 4856: Syntax error at or near "MERGE" at character 130
-- out LINE 10: MERGE
-- out ^
But what would you need it for, if you can put the full-select into the USING() clause?
MERGE
INTO public.foo t
USING (
SELECT
id
, UPPER(fname) AS fname
, UPPER(lname) AS lname
, hire_dt + 7 AS hire_dt
FROM public.foo
) s
ON s.id = t.id
WHEN MATCHED THEN UPDATE SET
fname = s.fname
, lname = s.lname
, hire_dt = s.hire_dt
WHEN NOT MATCHED THEN INSERT
VALUES (
s.id
, s.fname
, s.lname
, s.hire_dt
);
-- out OUTPUT
-- out --------
-- out 42
-- out (1 row)

Related

Insert multiple row different column value

This is my existing table
In this table, each user has their own respective data according to their Status. Each of the user will surely have Status 1.
Now, there are 3 Status to be stored for every user.
Was trying to make every user to have 3 Status, by inserting new row of user copying their Status 1 data, such that:
User Ali currently only have Status 1 and its data, so need insert a new
row Ali with Status 2 and copy along the data from Status 1, again,
insert a new row Ali with Status 3 and copy along the data from
Status 1.
User John currently only have Status 1 and 2, so need insert a new
row John with Status 3 and copy along the data from Status 1.
continue same pattern with other user
Expected result:
I would use CROSS JOIN and NOT EXISTS
with data as
(
select name,
column1,
column2
from your_table
where status = 1
), cross_join_data as
(
select d1.name, t.status, d1.column1, d1.column2
from data d1
cross join
(
select 1 status
union
select 2 status
union
select 3 status
) t
where not exists (
select 1
from your_table d2
where d2.name = d1.name and
d2.status = t.status
)
)
select *
from your_table
union all
select *
from cross_join_data
dbfiddle demo
This should work
with cte as (
select
[Name], coalesce(max(iif([Status]=1, [Column1], null)), max(iif([Status]=2, [Column1], null)), max(iif([Status]=3, [Column1], null))) col1
, coalesce(max(iif([Status]=1, [Column2], null)), max(iif([Status]=2, [Column2], null)), max(iif([Status]=3, [Column2], null))) col2
from
MyTable
group by [Name]
)
--insert into MyTable
select
cte.[Name], nums.n, cte.col1, cte.col2
from
cte
cross join (values (1),(2),(3)) nums(n)
left join MyTable on cte.[Name]=MyTable.[Name] and n=MyTable.[Status]
where
MyTable.[Status] is null
This works if data is not nullable
declare #table table (name varchar(10), status int, data int);
insert into #table values
('a', 1, 2)
, ('a', 2, 5)
, ('a', 3, 7)
, ('b', 1, 5)
, ('b', 2, 6)
, ('c', 1, 3)
select stats.status as statusStats
, tn.name as nameTN
, t.status as statusData, t.name, t.data
, ISNULL(t.data, t1.data) as 'fillInData'
from (values (1),(2),(3)) as stats(status)
cross join (select distinct name from #table) tn
left join #table t
on t.status = stats.status
and t.name = tn.name
join #table t1
on t1.name = tn.name
and t1.status = 1
order by tn.name, stats.status
Here is what I would do:
CREATE TABLE #existingtable (Name VARCHAR(50), Status INT, Column1 VARCHAR (10), Column2 VARCHAR(10));
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Ali',1,'100','90');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('John',1,'20','200');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('John',2,'80','90');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Ming',1,'54','345');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Mei',1,'421','123');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Mei',3,'24','344');
SELECT * FROM #existingtable;
WITH CTE (Name,Column1,Column2)
AS
(
SELECT DISTINCT NAME,COLUMN1,COLUMN2
FROM #existingtable
)
, CTE2 (NAME,Status,Column1,Column2)
AS
(
SELECT NAME,1 AS STATUS,COLUMN1,COLUMN2
FROM CTE
UNION
SELECT NAME,2 AS STATUS,COLUMN1,COLUMN2
FROM CTE
UNION
SELECT NAME,3 AS STATUS,COLUMN1,COLUMN2
FROM CTE
)
INSERT INTO #existingtable (Name,Status,Column1,Column2)
SELECT C.Name,C.Status,C.Column1,C.Column2
FROM CTE2 AS C
LEFT JOIN #existingtable AS E
ON C.NAME = E.Name
AND C.Status = E.Status
WHERE E.Status IS NULL
SELECT * FROM #existingtable
ORDER BY Name, status
This has 2 edits. Initial edit added a where clause to the CTE
Second edit added the values added by the OP

Query to select and combine uppermost and lowermost value from tables

We have the following tables on our SQL Server 2012.
Table A (data):
ID, Description
---------------
1 , Bla 1
2 , Bla 2
3 , Bla 3
Table P (data):
ID, ParentID, Name
------------------
1 , NULL , AAA
2 , 3 , CCC
3 , 1 , XXX
Table X (foreign keys A_ID to A.ID and P_ID to P.ID):
ID, A_ID, P_ID
--------------
1 , 1 , 1
2 , 1 , 2
3 , 2 , 1
4 , 2 , 2
5 , 2 , 3
6 , 3 , 1
Question:
We need a query something like:
SELECT ...
WHERE A_ID = 1
which should return this result:
ID, Name, Subname
-----------------
2 , AAA , CCC
Name needs to contain the upper most Name from Table P, i.e. the one that has no ParentID.
Subname needs to contain the bottom most Name from the Table P for which the ID still exists in Table X.
ID needs to contain the ID from Table X where P_ID is the ID of the bottom most child.
Another example:
SELECT ...
WHERE A_ID = 2
should return this result:
ID, Name, Subname
-----------------
4 , AAA , CCC
And
SELECT ...
WHERE A_ID = 3
should return this result:
ID, Name, Subname
-----------------
6 , AAA , NULL
We've tried various queries, but some work only for 'where A_ID = 1' and not for 'where A_ID = 2'. In order to select the lowest level child from P, we've looked at the 'How to select lowest level in hierarchy form table post' which probably comes in handy for the query we're looking for.
A single query would be nice, but we will accept a stored procedure as well.
Thanks in advance!
Information
The ID columns in all tables are primary keys
The ID columns in any given table can be changed to any other value in the sample data, while taking into account the primary and foreign key constraints. (E.g. changing P.ID '2' to '4' also results in the change of X.P_ID's '2' to '4'.) This is to show that ID's are not necessarily in order.
Values in the column P.Name can be any non-null value.
Table P can have multiple rows with ParentId set to null.
Sample Data
Taken from #NEER
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
Try with the below query. I think Table A is not required for getting the desired result.
SELECT TOP 1 First_VALUE(x.ID) OVER(ORDER BY x.ID desc) ID
,First_VALUE(Name) OVER(ORDER BY p.ID) Name
,CASE WHEN First_VALUE(Name) OVER(ORDER BY p.ID) = First_VALUE(Name) OVER(ORDER BY p.ID desc) THEN NULL
ELSE First_VALUE(Name) OVER(ORDER BY p.ID desc) END SubName
FROM [table P] p
JOIN [table X] x
ON p.ID=x.[P_ID]
WHERE x.[A_ID]=3
Try following query
DECLARE #TableA AS TABLE (ID INT,Des NVARCHAR(MAX));
Insert Into #TableA VALUES(1,'Bal 1'); Insert Into #TableA VALUES(2,'Bal 2'); Insert Into #TableA VALUES(3,'Bal 3');
DECLARE #TableP AS TABLE (ID INT,ParentID INT,Name NVARCHAR(MAX));
Insert Into #TableP VALUES(1,Null,'AAA'); Insert Into #TableP VALUES(2,1,'BBB'); Insert Into #TableP VALUES(3,2,'CCC');
DECLARE #TableX AS TABLE (ID INT,A_ID INT,P_ID INT);
Insert Into #TableX Values(1,1,1); Insert Into #TableX Values(2,1,2); Insert Into #TableX Values(3,2,1); Insert Into #TableX Values(4,2,3); Insert Into #TableX Values(5,3,1);
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Inner Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=1
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=2
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=3
Order by X.ID Desc
You can try the following
with report as (
select max(x.ID) as ID, min(x.P_ID) as MinP, max(x.P_ID) as MaxP
from X x
where x.A_ID = 1 -- <-- here you can change the value
)
select r.ID,
mn.Name as Name,
case when r.MinP = r.MaxP then null else mx.Name end as Subname
from report r
inner join P mn on mn.ID = r.MinP
inner join P mx on mx.ID = r.MaxP
Hope this will help you
Try it with a GROUP BY:
SELECT x.a_id, max(x.id) AS id, min(p.name) AS name,
CASE WHEN max(p.name) = min(p.name) THEN NULL
ELSE max(p.name) END AS subname
FROM p INNER JOIN x
ON p.id = x.p_id
GROUP BY x.a_id
HAVING x.a_id = 1
Still works with your updated sample data. Tested here: http://sqlfiddle.com/#!9/99597f/1
You can use Recursive CTE as the below:
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
DECLARE #A_ID INT = 2
;WITH Parents
AS
(
SELECT
P.ID, P.ParentID, P.Name
FROM #P P WHERE P.ParentID IS NULL
UNION ALL
SELECT
P.ID, Parent.ID, Parent.Name
FROM
#P P INNER JOIN
Parents Parent ON P.ParentID = Parent.ID
), Temp
AS
(
SELECT
X.ID,
Parent.Name Name,
IIF(P.ParentID IS NULL, NULL, P.Name) SubName
FROM
#A A INNER JOIN
#X X ON X.A_ID = A.ID INNER JOIN
#P P ON X.P_ID = P.ID LEFT JOIN
Parents Parent ON P.ParentID = Parent.ID OR (P.ParentID IS NULL AND P.ID = Parent.ID)
WHERE
A.ID = #A_ID
), MainTable
AS
(
SELECT
Temp.ID ,
Temp.Name ,
Temp.SubName,
COUNT(Temp.ID) OVER (PARTITION BY Temp.Name ORDER BY (SELECT NULL)) CountOfRowByParent
FROM
Temp
)
SELECT
MainTable.ID ,
MainTable.Name ,
MainTable.SubName
FROM
MainTable
WHERE
(
MainTable.CountOfRowByParent > 1 AND
MainTable.SubName IS NOT NULL
) OR
MainTable.CountOfRowByParent = 1
Result for 2:
ID Name SubName
4 AAA CCC
5 AAA XXX

SQL Server - from two rows, one column to one row, two columns?

if object_id( 'tempdb.dbo.#ctp', 'u' ) is not null
drop table #ctp ;
create table #ctp( id int, mastername varchar( 16 ) ) ;
insert into #ctp values( 1, 'Big Boy' ) ;
if object_id( 'tempdb.dbo.#client', 'u' ) is not null
drop table #client ;
create table #client( id int, name varchar(16 ), type int ) ;
insert into #client values( 1, 'ABC', 5 ) ;
insert into #client values( 2, 'XYZ', 6 ) ;
if object_id( 'tempdb.dbo.#ctpclient', 'u' ) is not null
drop table #ctpclient ;
create table #ctpclient( id int, ctpfk int, clientfk int ) ;
insert into #ctpclient values( 1, 1, 1 ) ;
insert into #ctpclient values( 2, 1, 2 ) ;
select tp.mastername
, c.name
, c.type
, cc.ctpfk
, cc.clientfk
from #ctp tp
join #ctpclient cc
on tp.id = cc.ctpfk
join #client c
on c.id = cc.clientfk
;
current output
mastername|name|type
Big Boy|ABC|5
Big Boy|XYZ|6
Instead of two rows of output, I would like the output to be as follows:
mastername|nameone|nametwo
Big Boy | ABC | XYZ
What is the optimal way to do this given that I have a many to many table such as #ctpclient?
Assuming you always have 2 rows you can use a crosstab (aka conditional aggregation). It would look something like this.
with SortedValues as
(
select tp.mastername
, c.name
, ROW_NUMBER() over (partition by mastername order by clientfk) as RowNum
from #ctp tp
join #ctpclient cc on tp.id = cc.ctpfk
join #client c on c.id = cc.clientfk
)
select mastername
, MAX(case when RowNum = 1 then name end) as NameOne
, MAX(case when RowNum = 2 then name end) as NameTwo
from SortedValues
group by mastername
If you have a varying numbers you can still accomplish but it is bit more complex.

Is it possible to simplify the SQL query used to produce the checksum based difference between a table and the given TVP?

I need to write many entities into the database. I want to optimize it by:
Issuing a preflight request to compute the difference between the current data in the table and the new data in the process memory.
Only update/delete/insert the relevant records.
All the data is checksumed, so I am only going to compare the checksums.
Here is my preflight request:
;WITH src AS (
SELECT cs.AdmClientSiteId, src.Id ClientId, Checksum, AuxId
FROM #src src
JOIN AdmClientSite cs ON cs.AdmClientMasterId = src.Id
WHERE cs.AdmSiteId = #AdmSiteId
), dst AS (
SELECT dst.AdmClientSiteId, cs.AdmClientMasterId ClientId, Checksum, LegalAuxId AuxId
FROM AdmCustomerInfoLegal dst
JOIN AdmClientSite cs ON cs.AdmClientSiteId = dst.AdmClientSiteId
WHERE cs.AdmSiteId = #AdmSiteId
)
SELECT ISNULL(src.AdmClientSiteId, dst.AdmClientSiteId) AdmClientSiteId, ISNULL(src.ClientId, dst.ClientId) ClientId, ISNULL(src.AuxId, dst.AuxId) AuxId,
CASE
WHEN src.Checksum IS NULL THEN 0 -- DBAction.DELETE
WHEN dst.Checksum IS NOT NULL THEN 1 -- DBAction.UPDATE
ELSE 2 -- DBAction.INSERT
END Action
FROM src
FULL JOIN dst ON src.AdmClientSiteId = dst.AdmClientSiteId AND src.AuxId = dst.AuxId
WHERE src.Checksum IS NULL OR dst.Checksum IS NULL OR src.Checksum <> dst.Checksum
ORDER BY Action, ClientId
In this code:
#src is a TVP
AdmCustomerInfoLegal is the table to be updated
The schema of #src is slightly different from that of AdmCustomerInfoLegal.
My question - can it be simplified/improved?
Yes , you can use merge statement which used in above condition.
I does not have time , please check this example and modify at your end.
create table temptable (id int, firstname varchar(50), lastname varchar(50), email varchar(50), homephone varchar(50))
insert into temptable values
(1,'aaa' , 'bbb', 'xxx#yyy.com', 1234444),
(2,'aaa' , 'bbb', null, null),
(3,'ccc' , 'ddd', 'abc#ddey.com', null),
(4,'ccc' , 'ddd', null, 34343322 )
select * from temptable
;with cte as
(
select firstname, lastname
,(select top 1 id from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and ( b.email is not null or b.homephone is not null)) tid
,(select top 1 email from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and b.email is not null ) email
,(select top 1 homephone from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and b.homephone is not null ) homephone
from temptable a
group by firstname , lastname
)
--select * from cte
merge temptable as a
using cte as b
on ( a.id = b.tid )
when matched
then
update set a.email = b.email , a.homephone = b.homephone
when not matched by source then
delete ;
select * from temptable
drop table temptable

SQL UPDATE statement to switch two values in two rows

I'm using SQL Server to swap two values in two rows. Let me show:
[ord] [name]
1 John
4 Jack
7 Pete
9 Steve
11 Mary
Say, I need to swap [ord] numbers for "Pete" and "Steve" to make this table to be like so:
[ord] [name]
1 John
4 Jack
9 Pete
7 Steve
11 Mary
This seems like a trivial task but I can't seem to write an SQL UPDATE statement for it.
If 'Peter' and 'Steve' are unique in your table, this will do:
UPDATE TableX
SET ord = ( SELECT MIN(ord) + MAX(ord)
FROM TableX
WHERE name IN ('Peter', 'Steve')
) - ord
WHERE name IN ('Peter', 'Steve')
or (improved by #Erwin):
UPDATE TableX
SET ord = ( SELECT SUM(ord)
FROM TableX
WHERE name IN ('Peter', 'Steve')
) - ord
WHERE name IN ('Peter', 'Steve')
Use a CASE expression:
UPDATE yourtable
SET [ord] = CASE [ord] WHEN 9 THEN 7
WHEN 7 THEN 9 END
WHERE [ord] IN (7, 9)
This is very similar to your earlier question: SQL to move rows up or down in two-table arrangement
I prepared another demo on data.stackexchange.com for you.
Edit: the setup is simplified now, so I simplified my query accordingly.
WITH x AS (SELECT name, ord FROM t WHERE name = 'Pete') -- must be unique!
, y AS (SELECT name, ord FROM t WHERE name = 'Steve') -- must be unique!
UPDATE t
SET ord = z.ord
FROM (
SELECT x.name, y.ord FROM x,y
UNION ALL
SELECT y.name, x.ord FROM x,y
) z
WHERE t.name = z.name;
This query only updates if both rows can be found and does nothing otherwise.
UPDATE Table_1
SET ord =
CASE name
WHEN 'Pete' THEN (SELECT ord FROM Table_1 WHERE name = 'Steve')
WHEN 'Steve' THEN (SELECT ord FROM Table_1 WHERE name = 'Pete')
END
WHERE name IN ('Pete', 'Steve')
You can easily replace 'Pete' and 'Steve' with other names...
BEGIN TRANSACTION
UPDATE TABLENAME
SET ord = 9
where name = 'Pete'
UPDATE TABLENAME
SET ord = 7
where name = 'Steve'
COMMIT TRANSACTION
Use below script to swap values
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL DROP TABLE #TempTable
CREATE TABLE #TempTable
(
ROW_ID INT IDENTITY(1,1),
SEQUENCE_NO INT,
ID INT
)
DECLARE #Id INT = 24780, --Row Id
DECLARE #NewPosition INT = -1; -- (Move Up or Move Down +1 for Up and -1 For Down)
DECLARE #SEQUENCE_NO INT = 0;
INSERT INTO #TempTable
SELECT SEQUENCE_NO ,ID
FROM TABLE_NAME S
WHERE ID = #Id
SET #SEQUENCE_NO = (SELECT SEQUENCE_NO FROM #TempTable)
INSERT INTO #TempTable
SELECT SEQUENCE_NO AS SNO,ID
FROM TABLE_NAME S
WHERE ID <> #Id
AND SEQUENCE_NO = (#SEQUENCE_NO + #NewPosition) -- (Move Up or Move Down +1 for Up and -1 For Down)
--Add check point here temp table to have 2 exact records
;WITH x AS (SELECT ID, SEQUENCE_NO FROM #TempTable WHERE ROW_ID = 1)
, y AS (SELECT ID, SEQUENCE_NO FROM #TempTable WHERE ROW_ID = 2)
UPDATE #TempTable
SET SEQUENCE_NO = z.SEQUENCE_NO
FROM (
SELECT x.ID, y.SEQUENCE_NO FROM x,y
UNION ALL
SELECT y.ID, x.SEQUENCE_NO FROM x,y
) z
WHERE #TempTable.ID = z.ID;
UPDATE SI
SET SI.SEQUENCE_NO = T.SEQUENCE_NO -- (Swap Values here)
FROM TABLE_NAME SI
JOIN #TempTable T ON SI.ID = T.ID