I have two tables as shown here:
I need to insert some data by a stored procedure as below code:
ALTER PROCEDURE [dbo].[DeviceInvoiceInsert]
#dt AS DeviceInvoiceArray READONLY
AS
DECLARE #customerDeviceId BIGINT
DECLARE #customerId BIGINT
DECLARE #filterChangeDate DATE
BEGIN
SET #customerId = (SELECT TOP 1 CustomerId FROM #dt
WHERE CustomerId IS NOT NULL)
SET #filterChangeDate = (SELECT TOP 1 filterChangeDate FROM #dt)
INSERT INTO CustomerDevice (customerId, deviceId, deviceBuyDate, devicePrice)
SELECT customerId, deviceId, deviceBuyDate, devicePrice
FROM #dt
WHERE CustomerId IS NOT NULL
SET #customerDeviceId = SCOPE_IDENTITY()
INSERT INTO FilterChange (customerId, filterId, customerDeviceId, filterChangeDate)
SELECT #customerId, dt.filterId, #customerDeviceId, #filterChangeDate
FROM #dt AS dt
END
The problem is that when the procedure wants to insert data into the FilterChange table, the #customerDeviceId always has the last IDENTITY Id.
How can I figure out this problem?
Update
Thanks for #T N answer but his solution is just to insert one filter per device, so in my case, there can be many filters per device
As mentioned above, using the OUTPUT clause is the best way to capture inserted IDENTITY or other implicitly assigned values. However, you also need to correlate this data with other values from your source table. As far as I know, this cannot be done using a regular INSERT statement, which only allows you to capture data from the target table via the INSERTED pseudo-table.
I am assuming that none of the explicitly inserted values in the first target table can be used to reliably uniquely identify the source record.
A workaround is to use the MERGE statement to perform the insert. The OUTPUT clause may then be used to capture a combination of source and inserted target data.
ALTER PROCEDURE [dbo].[DeviceInvoiceInsert]
#dt AS DeviceInvoiceArray READONLY
AS
BEGIN
-- Temp table to receive captured data from output clause
DECLARE #FilterChangeData TABLE (
customerId INT,
filterId INT,
customerDeviceId INT,
filterChangeDate DATETIME2
)
-- Merge is used instead of a plain INSERT so that we can capture
-- a combination of source and inserted data
MERGE CustomerDevice AS TGT
USING (SELECT * FROM #dt WHERE CustomerId IS NOT NULL) AS SRC
ON 1 = 0 -- Never match
WHEN NOT MATCHED THEN
INSERT (customerId, deviceId, deviceBuyDate, devicePrice)
VALUES (SRC.customerId, SRC.deviceId, SRC.deviceBuyDate, SRC.devicePrice)
OUTPUT SRC.customerId, SRC.filterId, INSERTED.customerDeviceId, SRC.filterChangeDate
INTO #FilterChangeData
;
INSERT INTO FilterChange (customerId, filterId, customerDeviceId, filterChangeDate)
SELECT customerId, filterId, customerDeviceId, filterChangeDate
FROM #FilterChangeData
END
Given the following #dt source data:
customerId
deviceId
deviceBuyDate
devicePrice
filterId
filterChangeDate
11
111
2023-01-01
111.1100
1111
2023-02-01
22
222
2023-01-02
222.2200
2222
2023-02-02
33
333
2023-01-03
333.3300
3333
2023-02-03
11
222
2023-01-04
333.3300
1111
2023-02-04
The following is inserted into CustomerDevice:
customerDeviceId
customerId
deviceId
deviceBuyDate
devicePrice
1
11
111
2023-01-01
111.1100
2
22
222
2023-01-02
222.2200
3
33
333
2023-01-03
333.3300
4
11
222
2023-01-04
333.3300
The following is inserted into FilterChange:
customerId
filterId
customerDeviceId
filterChangeDate
11
1111
1
2023-02-01
22
2222
2
2023-02-02
33
3333
3
2023-02-03
11
1111
4
2023-02-04
See this db<>fiddle.
Thanks to #TN, his solution is just to insert one filter per device, so in my case, there can be many filters per device, So I just manipulate the last part to solve the problem. Also the corrected #dt value is like this:
As you can see, there are many filters per device, and All customers are the same because per invoice belongs to one customer so I had to mark the rest repetitive devices with null to group filters per device.
Here is the corrected code, Thanks by #tn:
-- Example showing MERGE (instead of INSERT) to capture a combination of
-- source and inserted data in an OUTPUT clause.
CREATE TABLE CustomerDevice (
customerDeviceId INT IDENTITY(1,1),
customerId INT,
deviceId INT,
deviceBuyDate DATE,
devicePrice NUMERIC(19,4)
)
CREATE TABLE FilterChange (
customerId INT,
filterId INT,
customerDeviceId INT,
filterChangeDate DATE
)
DECLARE #dt TABLE (
customerId INT,
deviceId INT,
deviceBuyDate DATE,
devicePrice NUMERIC(19,4),
filterId INT,
filterChangeDate DATE
)
INSERT #dt
VALUES
(3, 1, '2023-01-01', 111.11, 1, '2023-02-01'),
(NULL, 1, '2023-01-02', 222.22, 2, '2023-02-02'),
(NULL, 1, '2023-01-03', 333.33, 3, '2023-02-03'),
(NULL, 1, '2023-01-03', 333.33, 4, '2023-02-03'),
(3, 2, '2023-01-04', 333.33, 1, '2023-02-04'),
(NULL, 2, '2023-01-04', 333.33, 2, '2023-02-04'),
(NULL, 2, '2023-01-04', 333.33, 3, '2023-02-04'),
(NULL, 2, '2023-01-04', 333.33, 4, '2023-02-04')
-- Procedure body
DECLARE #customerId BIGINT
SET #customerId = (SELECT TOP 1 CustomerId FROM #dt WHERE CustomerId IS NOT NULL)
DECLARE #FilterChangeData TABLE (
customerId INT,
deviceId INT,
filterId INT,
customerDeviceId INT,
filterChangeDate DATETIME
)
MERGE CustomerDevice AS TGT
USING (SELECT * FROM #dt WHERE CustomerId IS NOT NULL) AS SRC
ON 1 = 0 -- Never match
WHEN NOT MATCHED THEN
INSERT (customerId, deviceId, deviceBuyDate, devicePrice)
VALUES (SRC.customerId, SRC.deviceId, SRC.deviceBuyDate, SRC.devicePrice)
OUTPUT SRC.customerId,SRC.deviceId, SRC.filterId, INSERTED.customerDeviceId, SRC.filterChangeDate
INTO #FilterChangeData;
INSERT INTO FilterChange (customerId, filterId, customerDeviceId, filterChangeDate)
SELECT #customerId, dt.filterId, fcd.customerDeviceId, dt.filterChangeDate
FROM #dt AS dt INNER JOIN #FilterChangeData AS fcd
ON fcd.deviceId = dt.deviceId
-- End procedure body
SELECT * FROM #dt
SELECT * FROM CustomerDevice
SELECT * FROM FilterChange
Result show in, [https://dbfiddle.uk/yf7z_wqr][2]
Related
I have 2 tables: Order and product_order. Every order has some product in it and that's because I store products another table.
Table Order:
Id name
Table PRODUCT_ORDER:
id product_id order_id
Before I start to insert, I don't know what the Order Id is. I want to insert the data into both tables at once and I need the order id to do that.
Both id's are auto incremented. I'm using SQL Server. I can insert first order and then find the id of the order and than execute the second insert, but I want to do these both to execute at once.
The output clause is your friend here.
DECLARE #Orders TABLE (OrderID INT IDENTITY, OrderDateUTC DATETIME, CustomerID INT)
DECLARE #OrderItems TABLE (OrderItemID INT IDENTITY, OrderID INT, ProductID INT, Quantity INT, Priority TINYINT)
We'll use these table variables as demo tables with IDs to insert into. You're liking going to be passing the set of items for an order in together, but for the purpose of a demo we'll ad hoc them as a VALUES list.
DECLARE #Output TABLE (OrderID INT)
INSERT INTO #Orders (OrderDateUTC, CustomerID)
OUTPUT INSERTED.OrderID INTO #Output
VALUES (GETUTCDATE(), 1)
We inserted the Order into the Orders table, and used the OUTPUT clause to cause the inserted (and generated by the engine) into the table variable #Output. We can now use this table however we'd like:
INSERT INTO #OrderItems (OrderID, ProductID, Quantity, Priority)
SELECT OrderID, ProductID, Quantity, Priority
FROM (VALUES (5,1,1),(2,1,2),(3,1,3)) AS x(ProductID, Quantity, Priority)
CROSS APPLY #Output
We cross applied it to our items list, and inseted it as if it was any other row.
DELETE FROM #Output
INSERT INTO #Orders (OrderDateUTC, CustomerID)
OUTPUT INSERTED.OrderID INTO #Output
VALUES (GETUTCDATE(), 1)
INSERT INTO #OrderItems (OrderID, ProductID, Quantity, Priority)
SELECT OrderID, ProductID, Quantity, Priority
FROM (VALUES (1,1,1)) AS x(ProductID, Quantity, Priority)
CROSS APPLY #Output
Just to demo a little farther here's another insert. (You likely wouldn't need the DELETE normally, but we're still using the same variable here)
Now when we select that data we can see the two separate orders, with their IDs and the products that belong to them:
SELECT *
FROM #Orders o
INNER JOIN #OrderItems oi
ON o.OrderID = oi.OrderID
OrderID OrderDateUTC CustomerID OrderItemID OrderID ProductID Quantity Priority
------------------------------------------------------------------------------------------------
1 2022-12-08 23:23:21.923 1 1 1 5 1 1
1 2022-12-08 23:23:21.923 1 2 1 2 1 2
1 2022-12-08 23:23:21.923 1 3 1 3 1 3
2 2022-12-08 23:23:21.927 1 4 2 1 1 1
Dale is correct. You cannot insert into multiple tables at once, but if you use a stored procedure to handle your inserts, you can capture the ID and use it in the next insert.
-- table definitions
create table [order]([id] int identity, [name] nvarchar(100))
go
create table [product_order]([id] int identity, [product_id] nvarchar(100), [order_id] int)
go
-- stored procedure to handle inserts
create procedure InsertProductWithOrder(
#OrderName nvarchar(100),
#ProductID nvarchar(100))
as
begin
declare #orderID int
insert into [order] ([name]) values(#OrderName)
select #orderID = ##identity
insert into [product_order]([product_id], [order_id]) values(#ProductID, #orderID)
end
go
-- insert records using the stored procedure
exec InsertProductWithOrder 'Order ONE', 'AAAAA'
exec InsertProductWithOrder 'Order TWO', 'BBBBB'
-- verify the results
select * from [order]
select * from [product_order]
I have to write query to update roomid comparing rooms based on time slots
I have this table data
customerid appointmentfrom appointmentto roomid
----------------------------------------------------------------------------
1 2020-07-18 10:00:00.000 2020-07-18 11:30:00.000 1
2 2020-07-18 10:30:00.000 2020-07-18 11:15:00.000 2
3 2020-07-18 11:15:00.000 2020-07-18 11:59:00.000 2
I shouldn't allow customerid 1 to update his roomid as 2 as roomid 2 has been booked for that time slots
customerid 1 is trying to update roomid as 2 , but i need to check whether the appointmentfrom and appointmentto he is booking is available or not
Your question does not state how you get your input or how you want to handle forbidden updates (throw an error?). This solution has parameters as input and does nothing when the update is not allowed. I also included support for when a customer would have multiple appointments.
The where clause uses a (not) exists to only select updatable records.
-- create example
declare #data table
(
customerid int,
appointmentfrom datetime,
appointmentto datetime,
roomid int
);
insert into #data (customerid, appointmentfrom, appointmentto, roomid) values
(1, '2020-07-18 10:00:00.000', '2020-07-18 11:30:00.000', 1),
(2, '2020-07-18 10:30:00.000', '2020-07-18 11:15:00.000', 2),
(3, '2020-07-18 11:15:00.000', '2020-07-18 11:59:00.000', 2);
-- solution (with parameters)
declare #customerid int = 1; -- specify customer
declare #appointmentfrom datetime = '2020-07-18 10:00:00.000'; -- specify customer appointment
declare #newroomid int = 2; -- specify target room
update d
set d.roomid = #newroomid
from #data d
where d.customerid = #customerid -- select customer...
and d.appointmentfrom = #appointmentfrom -- ... and his appointment
-- look for any unwanted overlapping meetings on the target room
and not exists ( select top 1 'x'
from #data d2
where d2.roomid = #newroomid
and d2.appointmentto > d.appointmentfrom
and d2.appointmentfrom < d.appointmentto );
-- (0 rows affected)
I have the following table Items:
Id MemberId MemberGuid ExpiryYear Hash
---------------------------------------------------------------------------
1 1 Guid1 2017 Hash1
2 1 Guid2 2018 Hash2
3 2 Guid3 2020 Hash3
4 2 Guid4 2017 Hash1
I need to copy the items from a member to another (not just to update MemberId, to insert a new record). The rule is: if I want to migrate all the items from a member to another, I will have to check that that item does not exists in the new member.
For example, if I want to move the items from member 1 to member 2, I will move only item with id 2, because I already have an item at member 2 with the same hash and with the same expiry year (this are the columns that I need to check before inserting the new items).
How to write a query that migrates only the non-existing items from a member to another and get the old id and the new id of the records? Somehow with an upsert?
You can as the below:
-- MOCK DATA
DECLARE #Tbl TABLE
(
Id INT IDENTITY NOT NULL PRIMARY KEY,
MemberId INT,
MemberGuid CHAR(5),
ExpiryYear CHAR(4),
Hash CHAR(5)
)
INSERT INTO #Tbl
VALUES
(1, 'Guid1', '2017', 'Hash1'),
(1, 'Guid2', '2018', 'Hash1'),
(2, 'Guid3', '2020', 'Hash3'),
(2, 'Guid4', '2017', 'Hash1')
-- MOCK DATA
-- Parameters
DECLARE #FromParam INT = 1
DECLARE #ToParam INT = 2
DECLARE #TmpTable TABLE (NewDataId INT, OldDataId INT)
MERGE #Tbl AS T
USING
(
SELECT * FROM #Tbl
WHERE MemberId = #FromParam
) AS F
ON T.Hash = F.Hash AND
T.ExpiryYear = F.ExpiryYear AND
T.MemberId = #ToParam
WHEN NOT MATCHED THEN
INSERT ( MemberId, MemberGuid, ExpiryYear, Hash)
VALUES ( #ToParam, F.MemberGuid, F.ExpiryYear, F.Hash)
OUTPUT inserted.Id, F.Id INTO #TmpTable;
SELECT * FROM #TmpTable
Step 1:
Get in cursor all the data of member 1
Step 2:
While moving through cursor.
Begin
select hash, expirydate from items where memberid=2 and hash=member1.hash and expirydate=member1.expirydate
Step 3
If above brings any result, do not insert.
else insert.
Hope this helps
Note: this is not actual code. I am providing you just steps based on which you can write sql.
Actually you just need an insert. When ExpiryYear and Hash matched you don't wanna do anything. You just wanna insert from source to target where those columns doesn't match. You can do that with Merge or Insert.
CREATE TABLE YourTable
(
Oldid INT,
OldMemberId INT,
Id INT,
MemberId INT,
MemberGuid CHAR(5),
ExpiryYear CHAR(4),
Hash CHAR(5)
)
INSERT INTO YourTable VALUES
(null, null, 1, 1, 'Guid1', '2017', 'Hash1'),
(null, null, 2, 1, 'Guid2', '2018', 'Hash2'),
(null, null, 3, 2, 'Guid3', '2020', 'Hash3'),
(null, null, 4, 2, 'Guid4', '2017', 'Hash1')
DECLARE #SourceMemberID AS INT = 1
DECLARE #TargetMemberID AS INT = 2
MERGE [YourTable] AS t
USING
(
SELECT * FROM [YourTable]
WHERE MemberId = #SourceMemberID
) AS s
ON t.Hash = s.Hash AND t.ExpiryYear = s.ExpiryYear AND t.MemberId = #TargetMemberID
WHEN NOT MATCHED THEN
INSERT(Oldid, OldMemberId, Id, MemberId, MemberGuid, ExpiryYear, Hash) VALUES (s.Id, s.MemberId, (SELECT MAX(Id) + 1 FROM [YourTable]), #TargetMemberID, s.MemberGuid, s.ExpiryYear, s.Hash);
SELECT * FROM YourTable
DROP TABLE YourTable
/* Output:
Oldid OldMemberId Id MemberId MemberGuid ExpiryYear Hash
-----------------------------------------------------------------
NULL NULL 1 1 Guid1 2017 Hash1
NULL NULL 2 1 Guid2 2018 Hash2
NULL NULL 3 2 Guid3 2020 Hash3
NULL NULL 4 2 Guid4 2017 Hash1
2 1 5 2 Guid2 2018 Hash2
If you just want to select then do as following
SELECT null AS OldID, null AS OldMemberID, Id, MemberId, MemberGuid, ExpiryYear, Hash FROM YourTable
UNION ALL
SELECT A.Id AS OldID, A.MemberId AS OldMemberID, (SELECT MAX(Id) + 1 FROM YourTable) AS Id, #TargetMemberID AS MemberId, A.MemberGuid, A.ExpiryYear, A.Hash
FROM YourTable A
LEFT JOIN
(
SELECT * FROM YourTable WHERE MemberId = #TargetMemberID
) B ON A.ExpiryYear = B.ExpiryYear AND A.Hash = B.Hash
WHERE A.MemberId = #SourceMemberID AND B.Id IS NULL
Using SQL Server 2005/2008 I would like to insert a new row if the last value (sorted by _timestamp) inserted is not the same value.
For example:
value | timestamp
100 | "yesterday"
101 | "today"
For the next operation:
A value of 101 should not be inserted as this is the latest value
However a value of 102 should be inserted as the latest value is not this
Why you don't use something like that?
DECLARE #table TABLE(val INT, [timestamp] DATETIME)
INSERT INTO #table
SELECT 100, N'20160112'
UNION ALL
SELECT 101, N'20160113'
SELECT * FROM #table
INSERT INTO #table
SELECT 101, N'20160114'
WHERE 101 <> ISNULL((SELECT TOP 1 val FROM #table
ORDER BY timestamp DESC), -1)
SELECT * FROM #table AS T
Using SQL Server 2005.
Data is in 2 separate tables and I have only been given write permissions.
Data looks like:
DateTime1 | DateTime2
-----------------------
2012-06-01 | 2012-06-01
2012-06-02 | 2012-06-02
2012-06-04 | 2012-06-05
2012-06-02 | NULL
NULL | 2012-06-05
2012-06-04 | 2012-06-05
NULL | NULL
What I am trying to do is be able to count values in which DateTime1 and DateTime2 contain values, DateTime1 contains a date and DateTime2 is NULL, DateTime1 is NULL and DateTime2 contains values.
Overall Im trying to avoid DateTime1 being Null and DateTime2 being null.
My where statement looks like this:
Where (DateTime1 is not null or DateTime2 is not null)
The only problem is it is still showing where both are null values. Anyone know why this might be happening or how to solve it?
Thanks
EDIT
Full Query as requested by #Lamak
;With [CTE] As (
Select
TH.ID
,AMT
,Reason
,EffDate
,DateReq
,CS_ID
,ROW_NUMBER()
Over (Partition By ID Order By [pthPrimeKey] Desc) as [RN]
From
DateTime1Table as [MC] (nolock)
Left Join History as [TH] (nolock) on [TH].[ID] = [MC].[ID]
Left Join Trans as [SUB] (nolock) on [SUB].TransactionReasonCode = [TH].Reason
Left Join Renew as [RM] (nolock) on [MC].ID = [RM].ID
Where
([MC].[DateTime1] is not null or [RM].[DateTime2] is not null)
And [PostingDate] = DATEADD(dd, datediff(dd, 1, GetDate()),0)
)
SELECT
[ID]
,[AMT] as [Earned]
,[Reason] as [Reason]
,[EffDate] as [Eff]
,[DateReq] as [Date_Cancel_Req]
,[pthUserId_Number] as [CSR]
FROM [CTE]
Where RN <= 1
The following will allow rows to be included if
only DateTime1 has a value
only DateTime2 has a value
both have values
It will exclude rows where both values are NULL. Is that what you're after? (I tried to follow the conversations but got lost, and wish you'd have a simpler repro with sample data - I think the CTE and all the other joins and logic really take away from the actual problem you're having.)
WHERE COALESCE([MC].[DateTime1], [RM].[DateTime2]) IS NOT NULL
However, since you're performing a LEFT OUTER JOIN, this may belong in the ON clause for [RM] instead of WHERE. Otherwise you won't know if a row is excluded because the value in a matching row was NULL, or because there was no matching row. And maybe that's ok, just thought I would mention it.
EDIT
Of course, that clause provides the exact same results as ...
WHERE ([MC].[DateTime1] is not null or [RM].[DateTime2] is not null)
Want proof?
DECLARE #a TABLE(id INT, DateTime1 DATETIME);
DECLARE #b TABLE(id INT, DateTime2 DATETIME);
INSERT #a SELECT 1, '20120602' ; INSERT #b SELECT 1, NULL;
INSERT #a SELECT 2, NULL ; INSERT #b SELECT 2, '20120605';
INSERT #a SELECT 3, '20120604' ; INSERT #b SELECT 3, '20120605';
INSERT #a SELECT 4, NULL ; INSERT #b SELECT 4, NULL;
INSERT #a SELECT 5, '20120602' ; INSERT #b SELECT 9, NULL;
INSERT #a SELECT 6, NULL ; INSERT #b SELECT 10, '20120605';
INSERT #a SELECT 7, '20120604' ; INSERT #b SELECT 11, '20120605';
INSERT #a SELECT 8, NULL ; INSERT #b SELECT 12, NULL;
SELECT * FROM #a AS a LEFT OUTER JOIN #b AS b
ON a.id = b.id
WHERE COALESCE(a.DateTime1, b.DateTime2) IS NOT NULL;
SELECT * FROM #a AS a LEFT OUTER JOIN #b AS b
ON a.id = b.id
WHERE a.DateTime1 IS NOT NULL OR b.DateTime2 IS NOT NULL;
Both queries yield:
id DateTime1 id DateTime2
-- ---------- ---- ----------
1 2012-06-02 1 NULL -- because left is not null
2 NULL 2 2012-06-05 -- because right is not null
3 2012-06-04 3 2012-06-05 -- because neither is null
5 2012-06-02 NULL NULL -- because of no match
7 2012-06-04 NULL NULL -- because of no match
So as I suggested in the comment, if you're not seeing the rows you expect, you need to look at other parts of the query. If you provide sample data and desired results, we can try to help you narrow that down. As it is, I don't think we know enough about your schema and data to determine where the problem is.