Related
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]
There is a table in Oracle with the columns:
id | start_number | end_number
---+--------------+------------
1 | 100 | 200
2 | 151 | 200
3 | 25 | 49
4 | 98 | 99
5 | 49 | 100
There is a list of numbers (50, 99, 150).
I want an sql statement that returns all the ids where any of the numbers in the list of numbers is found equal to or between the start_number and the end_number.
Using the above example; 1, 4 and 5 should be returned.
1 - 150 is between or equal to 100 and 200
2 - none of the numbers are between or equal to 151 and 200
3 - none of the numbers are between or equal to 25 and 49
4 - 99 is between or equal to 98 and 99
5 - 50 and 99 are between or equal to 49 and 100
drop table TEMP_TABLE;
create table TEMP_TABLE(
THE_ID number,
THE_START number,
THE_END number
);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (1, 100, 200);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (2, 151, 200);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (3, 25, 49);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (4, 98, 99);
insert into TEMP_TABLE(the_id, the_start, the_end) values (5, 49, 100);
The following is the solution I came up with based on the comments and answers below plus some additional research:
SELECT
*
from
TEMP_TABLE
where
EXISTS (select * from(
select column_value as id
from table(SYS.DBMS_DEBUG_VC2COLL(50,99,150))
)
where id
BETWEEN TEMP_TABLE.the_start AND TEMP_TABLE.the_end
)
This works too:
SELECT
*
from
TEMP_TABLE
where
EXISTS (select * from(
select column_value as id
from table(sys.ku$_vcnt(50,99,150))
)
where id
BETWEEN TEMP_TABLE.the_start AND TEMP_TABLE.the_end
)
Here is a full example:
create table #list (
number int
)
create table #table (
id int,
start_number int,
end_number int
)
insert into #list values(50)
insert into #list values(99)
insert into #list values(150)
insert into #table values(1,100,200)
insert into #table values(2,151,200)
insert into #table values(3,25,49)
insert into #table values(4,98,99)
insert into #table values(5,49,100)
select distinct a.* from #table a
inner join #list l --your list of numbers
on l.number between a.start_number and a.end_number
drop table #list
drop table #table
You'll simply need to remove the code about #table (create, insert and drop) and put your table in the select.
It partly depends on how your are storing your list of numbers. I'll assume that they're in another table for now, as even then you have many options.
SELECT
*
FROM
yourTable
WHERE
EXISTS (SELECT * FROM yourList WHERE number BETWEEN yourTable.start_number AND yourTable.end_number)
Or...
SELECT
*
FROM
yourTable
INNER JOIN
yourList
ON yourList.number BETWEEN yourTable.start_number AND yourTable.end_number
Both of those are the simplest expressions, and work well for small data sets. If your list of numbers is relatively small, and your original data is relatively large, however, this may not scale well. This is because both of the above scan the whole of yourTable and then check each record against yourList.
What may be preferable is to scan the list, and then attempt to use indexes to check against the original data. This would require you to be able to reverse the BETWEEN statement to yourTable.start_number BETWEEN x and y
This can only be done if you know the maximum gap between start_number and end_number.
SELECT
*
FROM
yourList
INNER JOIN
yourTable
ON yourTable.end_number >= yourList.number
AND yourTable.start_number <= yourList.number
AND yourTable.start_number >= yourList.number - max_gap
To achieve this I would store the value of max_gap in another table, and update it as the values in yourTable change.
You will want to create a temporary table to hold your numbers, if the numbers aren't already in one. Then it becomes relatively simple:
SELECT DISTINCT mt.ID FROM MyTable mt
INNER JOIN TempTable tt --your list of numbers
ON tt.number Between mt.start_number and mt.end_number
To create the table based on an array of passed values, you can use table definitions in your procedure. I'm light on Oracle syntax and don't have TOAD handy, but you should be able to get something like this to work:
CREATE OR REPLACE PROCEDURE FindIdsFromList
AS
DECLARE
TYPE NumberRecord IS RECORD (Number int NOT NULL)
TYPE NumberList IS TABLE OF NumberRecord;
NumberList myNumberList;
BEGIN
myNumberList := (50,99,150);
SELECT DISTINCT mt.ID FROM MyTable mt
INNER JOIN myNumberList nt --your list of numbers
ON nt.Number Between mt.start_number and mt.end_number
END
Table1
ID (NVarchar Column)
01
02
03
...
Query
Select max(id) from table1
Ouput is
3
I want to get maximum id with leading zero, if it is 002 means, the query should return 002
Expected Output is
03
How to do this
Need Query Help
I executed the following piece of code:
DECLARE #TABLE TABLE (
ID nvarchar(10)
)
INSERT #TABLE VALUES ( '003' )
INSERT #TABLE VALUES ( '004' )
SELECT MAX (ID)
FROM #TABLE
The output was
004
When using the data type as int however:
DECLARE #TABLE TABLE (
ID int
)
INSERT #TABLE values ( '003' )
INSERT #TABLE VALUES ( '004')
SELECT MAX (ID )
FROM #TABLE
The output was:
4
I would check you have your data types defined correctly.
To get the idea: Select right('000' + max(id),2) from table1
You can write the query as following:
declare #input int, #len int
set #len = 2
select right(REPLICATE('0',#len)+convert(varchar(5),2),#len ) AS LPAD
I am assuming that you know the length all the time. You can make it a UDF as well.
Using SQL Server 2000
table1
ID Value
101 100
102 200
103 500
I have only 3 row in the table. I want to make in row wise view
Expected Output
101 102 103
100 200 500
How to make a select query for the above condition.
Need Query Help
If you defenitely know which values you have in ID column you can use query like this:
SELECT
MAX(id_101) AS id_101,
MAX(id_102) AS id_102,
MAX(id_103) AS id_103
FROM (
SELECT
1 AS aux,
CASE WHEN ID = 101 THEN val ELSE null END AS id_101,
CASE WHEN ID = 102 THEN val ELSE null END AS id_102,
CASE WHEN ID = 103 THEN val ELSE null END AS id_103
FROM table1
) AS t(aux, id_101, id_102, id_103)
GROUP BY t.aux;
Assuming that 101, 102, 103 is a column names you can rotate table usign PIVOT:
SELECT *
FROM
(
SELECT id, value
FROM #data1
) p
PIVOT (SUM(value) FOR [id] IN ([101], [102], [103])) AS pvt
Prepare data:
DECLARE #data1 TABLE(id int, value INT)
INSERT INTO #data1 VALUES(101, 100)
INSERT INTO #data1 VALUES(102, 200)
INSERT INTO #data1 VALUES(103, 500)
Using SQL Server 2000, consider a source table with more than 400,000 records.
The task is to select each regno entry with an incrementing on-the-fly rowid or sequence number for those with duplicates or multiple entries. For those which do NOT have duplicate entries in the source table, the rowid should simply be null.
Here's an example of the desired output:
regno rowid
100 1
100 2
100 3
200 null
300 4
300 5
400 null
500 null
600 6
600 7
Question:
What query would do the desired sequence incrementing using TSQL in SQL Server 2000?
If my comment is correct (600 should be 6,7) then have a look at this
DECLARE #Table TABLE(
regno INT,
rowid INT
)
INSERT INTO #Table (regno,rowid) SELECT 100, NULL
INSERT INTO #Table (regno,rowid) SELECT 100, NULL
INSERT INTO #Table (regno,rowid) SELECT 100, NULL
INSERT INTO #Table (regno,rowid) SELECT 200, NULL
INSERT INTO #Table (regno,rowid) SELECT 300, NULL
INSERT INTO #Table (regno,rowid) SELECT 300, NULL
INSERT INTO #Table (regno,rowid) SELECT 400, NULL
INSERT INTO #Table (regno,rowid) SELECT 500, NULL
INSERT INTO #Table (regno,rowid) SELECT 600, NULL
INSERT INTO #Table (regno,rowid) SELECT 600, NULL
DECLARE #TempTable TABLE(
ID INT IDENTITY(1,1),
regno INT
)
INSERT INTO #TempTable (regno)
SELECT regno
FROM #Table
SELECT regno,
CASE
WHEN (SELECT COUNT(1) FROM #TempTable WHERE regno = t.regno) = 1
THEN NULL
ELSE (SELECT COUNT(1) FROM #TempTable WHERE regno = t.regno) - (SELECT COUNT(1) FROM #TempTable WHERE regno = t.regno AND ID > t.ID) +
(SELECT COUNT(1) FROM #TempTable WHERE regno < t.regno AND regno IN (SELECT regno FROM #TempTable GROUP BY regno having COUNT(1) > 1))
END Val
FROM #TempTable t
The query to extract the non-unique records would be
select regno,count(*) from table group by regno having count(*) > 1
I don't know enough about MSSQL to tell you how to generate an incrementing sequence number to update the records that match the query.
Without a temp table:
DECLARE #Table TABLE(
regno INT
)
INSERT INTO #Table (regno) SELECT 100
INSERT INTO #Table (regno) SELECT 100
INSERT INTO #Table (regno) SELECT 100
INSERT INTO #Table (regno) SELECT 200
INSERT INTO #Table (regno) SELECT 300
INSERT INTO #Table (regno) SELECT 300
INSERT INTO #Table (regno) SELECT 400
INSERT INTO #Table (regno) SELECT 500
INSERT INTO #Table (regno) SELECT 600
INSERT INTO #Table (regno) SELECT 600
select regno, null as rowid from #Table group by regno having count(*) = 1
union
select regno, row_number() OVER (ORDER BY a.regno) as rowid
from #table a
where regno in (select regno from #table group by regno having count(*) > 1)
regno rowid
----------- --------------------
100 1
100 2
100 3
200 NULL
300 4
300 5
400 NULL
500 NULL
600 6
600 7
Oops - did not see that you want to do this in SQL 2000 until after posting this ... ignore my query please. In SQL 2000 you need a temp table to generate the sequence.