Insert multiple rows of data with out looping the table data - sql-server

I have a table where it holds some duplicate entries, I would like to copy over the distinct entries to another table with out looping the data. I need to check if the distinct data exists in other table and insert what ever is missing. Here is the query I am writing, I feel like it can be implement better
CREATE TABLE ForgeRock
([productName] varchar(13));
INSERT INTO ForgeRock
([productName])
VALUES
('OpenIDM'), ('OpenAM'), ('OpenDJ'), ('OpenDJ'),('OpenDJ1');
CREATE TABLE ForgeRock1
([productName] varchar(13));
DECLARE #prodName NVARCHAR(MAX)
SELECT DISTINCT #prodName = STUFF((SELECT ',' + productName
FROM ForgeRock
FOR XML PATH('')) ,1,1,'')
set #prodName = ''''+replace(#prodName,',',''',''')+''''
INSERT INTO ForgeRock1 (productName)
SELECT DISTINCT productName FROM ForgeRock WHERE
productName NOT IN (SELECT productName FROM ForgeRock1
where productName NOT IN (#prodName))
Here is the sample fiddle I tried out http://sqlfiddle.com/#!18/9dbe8f/1/0, is this query efficient or can it be better

This query should do what you want :)
INSERT INTO ForgeRock1 (productName)
SELECT DISTINCT productName FROM ForgeRock fr
WHERE NOT EXISTS ( SELECT 1 FROM ForgeRock1 fr1 WHERE fr1.productName = fr.productName )

Related

Get multiple rows using FOR JSON clause

Using PostgreSQL I can have multiple rows of json objects.
select (select ROW_TO_JSON(_) from (select c.name, c.age) as _) as jsonresult from employee as c
This gives me this result:
{"age":65,"name":"NAME"}
{"age":21,"name":"SURNAME"}
But in SqlServer when I use the FOR JSON AUTO clause it gives me an array of json objects instead of multiple rows.
select c.name, c.age from customer c FOR JSON AUTO
[{"age":65,"name":"NAME"},{"age":21,"name":"SURNAME"}]
How to get the same result format in SqlServer ?
By constructing separate JSON in each individual row:
SELECT (SELECT [age], [name] FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
FROM customer
There is an alternative form that doesn't require you to know the table structure (but likely has worse performance because it may generate a large intermediate JSON):
SELECT [value] FROM OPENJSON(
(SELECT * FROM customer FOR JSON PATH)
)
no structure better performance
SELECT c.id, jdata.*
FROM customer c
cross apply
(SELECT * FROM customer jc where jc.id = c.id FOR JSON PATH , WITHOUT_ARRAY_WRAPPER) jdata (jdata)
Same as Barak Yellin but more lazy:
1-Create this proc
CREATE PROC PRC_SELECT_JSON(#TBL VARCHAR(100), #COLS VARCHAR(1000)='D.*') AS BEGIN
EXEC('
SELECT X.O FROM ' + #TBL + ' D
CROSS APPLY (
SELECT ' + #COLS + '
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) X (O)
')
END
2-Can use either all columns or specific columns:
CREATE TABLE #TEST ( X INT, Y VARCHAR(10), Z DATE )
INSERT #TEST VALUES (123, 'TEST1', GETDATE())
INSERT #TEST VALUES (124, 'TEST2', GETDATE())
EXEC PRC_SELECT_JSON #TEST
EXEC PRC_SELECT_JSON #TEST, 'X, Y'
If you're using PHP add SET NOCOUNT ON; in the first row (why?).

Unpivot dynamic table columns into key value rows

The problem that I need to resolve is data transfer from one table with many dynamic fields into other structured key value table.
The first table comes from a data export from another system, and has the following structure ( it can have any column name and data):
[UserID],[FirstName],[LastName],[Email],[How was your day],[Would you like to receive weekly newsletter],[Confirm that you are 18+] ...
The second table is where I want to put the data, and it has the following structure:
[UserID uniqueidentifier],[QuestionText nvarchar(500)],[Question Answer nvarchar(max)]
I saw many examples showing how to unpivot table, but my problem is that I dont know what columns the Table 1 will have. Can I somehow dynamically unpivot the first table,so no matter what columns it has, it is converted into a key-value structure and import the data into the second table.
I will really appreciate your help with this.
You can't pivot or unpivot in one query without knowing the columns.
What you can do, assuming you have privileges, is query sys.columns to get the field names of your source table then build an unpivot query dynamically.
--Source table
create table MyTable (
id int,
Field1 nvarchar(10),
Field2 nvarchar(10),
Field3 nvarchar(10)
);
insert into MyTable (id, Field1, Field2, Field3) values ( 1, 'aaa', 'bbb', 'ccc' );
insert into MyTable (id, Field1, Field2, Field3) values ( 2, 'eee', 'fff', 'ggg' );
insert into MyTable (id, Field1, Field2, Field3) values ( 3, 'hhh', 'iii', 'jjj' );
--key/value table
create table MyValuesTable (
id int,
[field] sysname,
[value] nvarchar(10)
);
declare #columnString nvarchar(max)
--This recursive CTE examines the source table's columns excluding
--the 'id' column explicitly and builds a string of column names
--like so: '[Field1], [Field2], [Field3]'.
;with columnNames as (
select column_id, name
from sys.columns
where object_id = object_id('MyTable','U')
and name <> 'id'
),
columnString (id, string) as (
select
2, cast('' as nvarchar(max))
union all
select
b.id + 1, b.string + case when b.string = '' then '' else ', ' end + '[' + a.name + ']'
from
columnNames a
join columnString b on b.id = a.column_id
)
select top 1 #columnString = string from columnString order by id desc
--Now I build a query around the column names which unpivots the source and inserts into the key/value table.
declare #sql nvarchar(max)
set #sql = '
insert MyValuestable
select id, field, value
from
(select * from MyTable) b
unpivot
(value for field in (' + #columnString + ')) as unpvt'
--Query's ready to run.
exec (#sql)
select * from MyValuesTable
In case you're getting your source data from a stored procedure, you can use OPENROWSET to get the data into a table, then examine that table's column names. This link shows how to do that part.
https://stackoverflow.com/a/1228165/300242
Final note: If you use a temporary table, remember that you get the column names from tempdb.sys.columns like so:
select column_id, name
from tempdb.sys.columns
where object_id = object_id('tempdb..#MyTable','U')

Grouping XML Elements in FOR XML Clause

I am trying to create a structure xml document from my temp table .The temp table is in the following format .
CREATE TABLE #Temp1 ( Name Char( 30 ), seqid integer, salary int );
INSERT INTO #Temp1 VALUES('DEAL' ,123,6)
INSERT INTO #Temp1 VALUES('DEAL' ,56,6)
INSERT INTO #Temp1 VALUES('TRACNHE' ,1253,56)
INSERT INTO #Temp1 VALUES('TRACNHE' ,5,65)
INSERT INTO #Temp1 VALUES('ASSET' ,56,23)
I am trying to create an xml format in the following form :
<Response>
<Deal>
<seqid="123" salary="6" />
<seqid="56" salary="6" />
<Deal>
<TRACNHE>
<seqid="1253" salary="56"/>
<seqid="5" salary="65"/>
</TRACNHE>
<ASSET>
<seqid="56" salary="23"/>
</ASSET>
</Response>
SELECT Name, (SELECT SEQID FROM #TEMP1 T WHERE T.Name = T1.Name)
FROM (SELECT DISTINCT NAME FROM #TEMP1 ) T1
FOR XML PATH('rEPONSE')
DROP TABLE #Temp1
DROP TABLE #Temp1
I tried the above query but says that subquery returned more than 1 value
Could you let me know as to what i am missing in this query .
Is there a better way to handle this scenario.
Thanks in advance
based on your requirement, i'm seeing there are 2 types of complexities
You are trying to get the xml with grouped items.
For each group trying to create an xml element with two attributes
without any proper name
<seqid="1253" salary="56"/>
instead of
<ss seqid="1253" salary="56"/>
just look into this below query, it may help
SELECT
(SELECT
seqid 'ss/#seqid'
, salary 'ss/#salary'
FROM Temp1 as t where t.Name = 'Deal'
FOR XML PATH('Deal') , TYPE
) ,
(SELECT
seqid 'ss/#seqid'
, salary 'ss/#salary'
FROM Temp1 as t where t.Name = 'TRACNHE'
FOR XML PATH('TRACNHE') , TYPE
) ,
(SELECT
seqid 'ss/#seqid'
, salary 'ss/#salary'
FROM Temp1 as t where t.Name = 'ASSET'
FOR XML PATH('ASSET') , TYPE
)
FOR XML PATH(''), ROOT('Response');

Insert from single table into multiple tables, invalid column name error

I am trying to do the following but getting an "Invalid Column Name {column}" error. Can someone please help me see the error of my ways? We recently split a transaction table into 2 tables, one containing the often updated report column names and the other containing the unchanging transactions. This leave me trying to change what was a simple insert into 1 table to a complex insert into 2 tables with unique columns. I attempted to do that like so:
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.Date, INSERTED.Amount, INSERTED.Id INTO dbo.Transactions
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
The "INSERTED.Date, INSERTED.Amount" are the source of the errors, with or without the "INSERTED." in front.
-----------------UPDATE------------------
Aaron was correct and it was impossible to manage with an insert but I was able to vastly improve the functionality of the insert and add some other business rules with the Merge functionality. My final solution resembles the following:
DECLARE #TransactionsTemp TABLE
(
[Date] DATE NOT NULL,
Amount MONEY NOT NULL,
ReportColumnsId INT NOT NULL
)
MERGE INTO dbo.ReportColumns AS Trgt
USING ( SELECT
{FK}
,[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}) AS Src
ON Src.{FK} = Trgt.{FK}
WHEN MATCHED THEN
UPDATE SET
Trgt.FullName = Src.FullName,
Trgt.Type= Src.Type,
Trgt.Classification = Src.Classification
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
FullName,
Type,
Classification
)
VALUES
(
Src.FullName,
Src.Type,
Src.Classification
)
OUTPUT Src.[Date], Src.Amount, INSERTED.Id INTO #TransactionsTemp;
MERGE INTO dbo.FinancialReport AS Trgt
USING (SELECT
[Date] ,
Amount ,
ReportColumnsId
FROM #TransactionsTemp) AS Src
ON Src.[Date] = Trgt.[Date] AND Src.ReportColumnsId = Trgt.ReportColumnsId
WHEN NOT MATCHED BY TARGET And Src.Amount <> 0 THEN
INSERT
(
[Date],
Amount,
ReportColumnsId
)
VALUES
(
Src.[Date],
Src.Amount,
Src.ReportColumnsId
)
WHEN MATCHED And Src.Amount <> 0 THEN
UPDATE SET Trgt.Amount = Src.Amount
WHEN MATCHED And Src.Amount = 0 THEN
DELETE;
Hope that helps someone else in the future. :)
Output clause will return values you are inserting into a table, you need multiple inserts, you can try something like following
declare #staging table (datecolumn date, amount decimal(18,2),
fullname varchar(50), type varchar(10),
Classification varchar(255));
INSERT INTO #staging
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
Declare #temp table (id int, fullname varchar(50), type varchar(10));
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.id, INSERTED.fullname, INSERTED.type INTO #temp
SELECT
FullName
,Type
,Classification
FROM #stage
INSERT into dbo.transacrions (id, date, amount)
select t.id, s.datecolumn, s.amount from #temp t
inner join #stage s on t.fullname = s.fullname and t.type = s.type
I am fairly certain you will need to have two inserts (or create a view and use an instead of insert trigger). You can only use the OUTPUT clause to send variables or actual inserted values ti another table. You can't use it to split up a select into two destination tables during an insert.
If you provide more information (like how the table has been split up and how the rows are related) we can probably provide a more specific answer.

Using COALESCE in SQL view

I need to create a view from several tables. One of the columns in the view will have to be composed out of a number of rows from one of the table as a string with comma-separated values.
Here is a simplified example of what I want to do.
Customers:
CustomerId int
CustomerName VARCHAR(100)
Orders:
CustomerId int
OrderName VARCHAR(100)
There is a one-to-many relationship between Customer and Orders. So given this data
Customers
1 'John'
2 'Marry'
Orders
1 'New Hat'
1 'New Book'
1 'New Phone'
I want a view to be like this:
Name Orders
'John' New Hat, New Book, New Phone
'Marry' NULL
So that EVERYBODY shows up in the table, regardless of whether they have orders or not.
I have a stored procedure that i need to translate to this view, but it seems that you cant declare params and call stored procs within a view. Any suggestions on how to get this query into a view?
CREATE PROCEDURE getCustomerOrders(#customerId int)
AS
DECLARE #CustomerName varchar(100)
DECLARE #Orders varchar (5000)
SELECT #Orders=COALESCE(#Orders,'') + COALESCE(OrderName,'') + ','
FROM Orders WHERE CustomerId=#customerId
-- this has to be done separately in case orders returns NULL, so no customers are excluded
SELECT #CustomerName=CustomerName FROM Customers WHERE CustomerId=#customerId
SELECT #CustomerName as CustomerName, #Orders as Orders
EDIT: Modified answer to include creation of view.
/* Set up sample data */
create table Customers (
CustomerId int,
CustomerName VARCHAR(100)
)
create table Orders (
CustomerId int,
OrderName VARCHAR(100)
)
insert into Customers
(CustomerId, CustomerName)
select 1, 'John' union all
select 2, 'Marry'
insert into Orders
(CustomerId, OrderName)
select 1, 'New Hat' union all
select 1, 'New Book' union all
select 1, 'New Phone'
go
/* Create the view */
create view OrderView as
select c.CustomerName, x.OrderNames
from Customers c
cross apply (select stuff((select ',' + OrderName from Orders o where o.CustomerId = c.CustomerId for xml path('')),1,1,'') as OrderNames) x
go
/* Demo the view */
select * from OrderView
go
/* Clean up after demo */
drop view OrderView
drop table Customers
drop table Orders
go
In SQL Server 2008, you can take advantage of some of the features added for XML to do this all in one query without using a stored proc:
SELECT CustomerName,
STUFF( -- "STUFF" deletes the leading ', '
( SELECT ', ' + OrderName
FROM Orders
WHERE CustomerId = Customers.CutomerId
-- This causes the sub-select to be returned as a concatenated string
FOR XML PATH('')
),
1, 2, '' )
AS Orders
FROM Customers

Resources