Good morning,
I found the solution to my problem, but I thought I'd share it anyway as it might be useful for future projects/problems. I have a simple SQL table below which will be the foreign key of my much bigger table of stock prices market data.
CREATE TABLE [StockMarket]
(
[ID] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[ReutersRIC] VARCHAR(50),
[BloombergTicker] VARCHAR(50),
[YahooSymbol] VARCHAR(50)
/* other irrelevant columns here*/
)
With that in mind, I am trying to add robustness to the structure as I will be adding from different data sources. For each underlying time series on the financial markets, there are multiple names depending on which data provider you use. I wanted to avoid having multiple lines with different data sources representing the same time series. I needed a trigger which:
1) If the inserted values are not yet in the table, it is simply inserted.
2) if I insert a line for which at least one [ReutersRIC], [BloombergTicker], [YahooSymbol], [ISIN] already exists, I update that specific line instead.
2.1) The update should only happen on Non-Null entrees
My question was how this can be achieved in the best possible way? It took me some time, but I wanted to share the answer below for future reference.
Here is my solution:
CREATE TRIGGER UniqueStockMarketInserts ON [dbo].[StockMarket]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #StockId int
SELECT #StockId = S.ID
FROM [StockMarket] S INNER JOIN [INSERTED] I
ON
S.YahooSymbol = I.YahooSymbol OR
S.ReutersRIC = I.ReutersRIC OR
S.BloombergTicker = I.BloombergTicker OR
S.ISIN = I.ISIN
IF #StockId IS NULL
BEGIN
INSERT INTO [StockMarket] (YahooSymbol,ReutersRIC,BloombergTicker,ISIN)
SELECT I.YahooSymbol, I.ReutersRIC, I.BloombergTicker, I.ISIN
FROM [INSERTED] I
END
ELSE
BEGIN
UPDATE S SET
S.ISIN = ISNULL(S.ISIN,I.ISIN),
S.YahooSymbol = ISNULL(S.YahooSymbol,I.YahooSymbol),
S.ReutersRIC = ISNULL(S.ReutersRIC,I.ReutersRIC),
S.BloombergTicker = ISNULL(S.BloombergTicker,I.BloombergTicker)
FROM INSERTED I, StockMarket S
WHERE S.ID = #StockId
END
END
GO
Here is the test I ran on it with real-life examples (except the ISIN which I made up):
/*-----------TESTING THE TRIGGER--------*/
INSERT INTO StockMarket (ReutersRIC, BloombergTicker) VALUES ('G.TO', 'GG US Equity');
INSERT INTO StockMarket (YahooSymbol, BloombergTicker, ISIN) VALUES ('GOOG', 'GOOG US Equity','US123454321');
INSERT INTO StockMarket (YahooSymbol, ISIN) VALUES ('RDSA','NL111112222')
INSERT INTO StockMarket (YahooSymbol, ReutersRIC) VALUES ('GG', 'G.TO'); /*this should update as per trigger*/
INSERT INTO StockMarket (ReutersRIC, ISIN) Values ('GOOG.OQ','US123454321'); /*this should update as per trigger*/
INSERT INTO StockMarket (YahooSymbol, ReutersRIC) Values ('RDSA', 'RDSa.L'); /*this should update as per trigger*/
And the results:
Hope this can help someone in the future. Happy coding
Related
There is a table with existing records with different entity ids, now need to insert records in the Table using Cursor and without hard coding the records.
Please find my script below.
DECLARE #txId varchar(50)
Declare #UserRole_id int
#txId = NEWID()
set #UserRole_id = (SELECT #UserRole_id = MAX(User_Role_Id) + 1 FROM USER_TB
INSERT INTO USER_ROLE_TB
(User_Role_id,
entity_Id,
User_role_code,
User_Role_description,
Print_Sequence,
Update_date,
Tx_id,
Add_date,
Add_Username,
Update_Username,
Active_Flag)
Values...
(#UserRole_id,entity_Id,'ACH ','AC-HEATING',7,GETUTCDATE(),#txID,GETDATE(),'30383212','30383212',1)
(#UserRole_id,entity_Id,'BRAKE TECH','BRAKE',8,GETUTCDATE(),#txID,GETDATE(),'30383212','30383212',1)
(#UserRole_id,Pos_entity_Id,'CASH OUT','CASH OUT TECHNICIAN',4,GETDATE(),#txID,GETDATE(),'30383212','30383212',1)
Here i have hardcoded the records what if i have to insert new records. Im not sure how do i do it.
i got to insert in these column without harding coding them and using cursor.
Not so familiar with Cursor. Please help.
I have a table with 3 fields: Id(PK,AI), Name(varchar(36)), LName(varchar(36)).
I have to insert name and last name, Id inserts automatically because of it's constraints,
Is There a way to Jump id auto increment value when it reaches 6?
for instance do this 7 times:
Insert Into table(Name, LName) Values ('name1', 'lname1') "And jump id to 7 if it is going to be 6"
It may sound stupid to do this but I have the doubt.
Also Jump and do not record id 6.
record only, 1-5, 7,8,9 and so on
What I want to achieve starts from a Union:
Select * From TableNames
Union All
Select * From TableNames_general
In the TableNames_general I assign it's first value so that when the user sees the table for the first time it will be displayed the record I inserted.
The problem comes when the user inserts a new record, if the Id of the inserted record is the same as the one I have inserted it will be duplicated, that is why I want to achieve when the users inserts one record and if the last insert id already exists just jump that record. this is because I must have different ids due to its relationship among child tables.
Identity column generate values for you, And its best left this way, You have the ability to insert specific values in Identity column but its best left alone and let it generate values for you.
Imagine you have inserted a value explicitly in an identity column and then later on Identity column generates the same value for you, you will end up with duplicates.
If you want to have your input in that column then why bother with identity column anyway ??
Well this is not the best practice but you can jump to a specific number by doing as follows:
MS SQL SERVER 2005 and Later
-- Create test table
CREATE TABLE ID_TEST(ID INT IDENTITY(1,1), VALUE INT)
GO
-- Insert values
INSERT INTO ID_TEST (VALUE) VALUES
(1),(2),(3)
GO
-- Set idnentity insert on to insert values explicitly in identity column
SET IDENTITY_INSERT ID_TEST ON;
INSERT INTO ID_TEST (ID, VALUE) VALUES
(6, 6),(8,8),(9,9)
GO
-- Set identity insert off
SET IDENTITY_INSERT ID_TEST OFF;
GO
-- 1st reseed the value of identity column to any smallest value in your table
-- below I reseeded it to 0
DBCC CHECKIDENT ('ID_TEST', RESEED, 0);
-- execute the same commad without any seed value it will reset it to the
-- next highest idnetity value
DBCC CHECKIDENT ('ID_TEST', RESEED);
GO
-- final insert
INSERT INTO ID_TEST (VALUE) VALUES
(10)
GO
-- now select data from table and see the gap
SELECT * FROM ID_TEST
If you query the database to get the last inserted ID, then you can check if you need to increment it, by using a parameter in the query to set the correct ID.
If you use MSSQL, you can do the following:
Before you insert check for the current ID, if it's 5, then do the following:
Set IDENTITY_INSERT to ON
Insert your data with ID = 7
Set IDENTITY_INSERT to OFF
Also you might get away with the following scenario:
check for current ID
if it's 5, run DBCC CHECKIDENT (Table, reseed, 6), it will reseed the table and in this case your next identity will be 7
If you're checking for current identity just after INSERT, you can use SELECT ##IDENTITY or SELECT SCOPE_IDENTITY() for better results (as rcdmk pointed out in comments)
Otherwise you can just use select: SELECT MAX(Id) FROM Table
There's no direct way to influence the AUTO_INCREMENT to "skip" a particular value, or values on a particular condition.
I think you'd have to handle this in an AFTER INSERT trigger. An AFTER INSERT trigger can't update the values of the row that was just inserted, and I don't think it can make any modifications to the table affected by the statement that fired the trigger.
A BEFORE INSERT trigger won't work either, because the value assigned to an AUTO_INCREMENT column is not available in a BEFORE INSERT trigger.
I don't believe there's a way to get SQL Server IDENTITY to "skip" a particular value either.
UPDATE
If you need "unique" id values between two tables, there's a rather ugly workaround with MySQL: roll your own auto_increment behavior using triggers and a separate table. Rather than defining your tables with AUTO_INCREMENT attribute, use a BEFORE INSERT trigger to obtain a value.
If an id value is supplied, and it's larger than the current maximum value from the auto_increment column in the dummy auto_increment_seq table, we'd need to either update that row, or insert a new one.
As a rough outline:
CREATE TABLE auto_increment_seq
(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=MyISAM;
DELIMITER $$
CREATE TRIGGER TableNames_bi
BEFORE INSERT ON TableNames
FOR EACH ROW
BEGIN
DECLARE li_new_id INT UNSIGNED;
IF ( NEW.id = 0 OR NEW.id IS NULL ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NULL);
SELECT LAST_INSERT_ID() INTO li_new_id;
SET NEW.id = li_new_id;
ELSE
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END IF;
END$$
CREATE TRIGGER TableNames_ai
AFTER INSERT ON TableNames
FOR EACH ROW BEGIN
DECLARE li_max_seq INT UNSIGNED;
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END;
DELIMITER ;
The id column in the table could be defined something like this:
TableNames
( id INT UNSIGNED NOT NULL DEFAULT 0 PRIMARY KEY
COMMENT 'populated from auto_increment_seq.id'
, ...
You could create an identical trigger for the other table as well, so the two tables are effectively sharing the same auto_increment sequence. (With less efficiency and concurrency than an Oracle SEQUENCE object would provide.)
IMPORTANT NOTES
This doesn't really insure that the id values between the tables are actually kept unique. That would really require a query of the other table to see if the id value exists or not; and if running with InnoDB engine, in the context of some transaction isolation levels, we might be querying a stale (as in, consistent from the point in time at the start of the transaction) version of the other table.
And absent some additional (concurrency killing) locking, the approach outline above is subject to a small window of opportunity for a "race" condition with concurrent inserts... the SELECT MAX() from the dummy seq table, followed by the INSERT, allows a small window for another transaction to also run a SELECT MAX(), and return the same value. The best we can hope for (I think) is for an error to be thrown due to a duplicate key exception.
This approach requires the dummy "seq" table to use the MyISAM engine, so we can get an Oracle-like AUTONOMOUS TRANSACTION behavior; if inserts to the real tables are performed in the context of a REPEATABLE READ or SERIALIZABLE transaction isolation level, reads of the MAX(id) from the seq table would be consistent from the snapshot at the beginning of the transaction, we wouldn't get the newly inserted (or updated) values.
We'd also really need to consider the edge case of an UPDATE of row changing the id value; to handle that case, we'd need BEFORE/AFTER UPDATE triggers as well.
Im trying to generate a bunch of discounts codes. These have foreign key contraints.
At the moment I have
INSERT INTO code (title, code, desc) VALUES ('code1','XPISY9','test code');
INSERT INTO code_details (code_id, used, attempts) VALUES (
SELECT code_id from code where code = 'XPISY9',0,0);
The code_id in code_details is a foreign key for code_id in the code table.
What would be the best way to create a loop where I can generate a set of these values (around 100). I would need the code to be a not repeating random value.
Any help would be appreciated.
THanks
Once you have your 100 or so records in the code table, you can add the code details in one statement:
INSERT INTO code_details (code_id, used, attempts)
SELECT code, 0, 0
FROM code;
Generating the code records in the first place is another matter and is probably best done by using some other tool to generate 100 insert statements in a text file which you can then execute.
I've seen that done with every scripting language possible - chose your favourite. I've even seen Excel used with a column for the id and string formula to generate the insert statements.
Thanks for the help guys. I decided to put together a procedure for this and it worked great:
DELIMITER $$
CREATE PROCEDURE vouchergen(IN length INT(10) ,IN duration VARCHAR(20),IN sponsor VARCHAR(20),IN amount INT(10))
BEGIN
DECLARE i INT DEFAULT 1;
WHILE (i< amount) DO
SET #vcode= CONCAT(BINARY brand , UPPER(SUBSTRING(MD5(RAND()) FROM 1 FOR 6)));
INSERT INTO code (title, code, desc) VALUES (CONCAT(brand,i),#vcode,CONCAT(length,' ',duration));
INSERT INTO code_details (code_id, used, attempts) VALUES (
SELECT code_id from code where code = #vcode,0,0);
SET i=i+1;
END WHILE;
END$$;
I can then call this to gen however many i want:
CALL vouchergen(1,'week',APPL,400);
CALL vouchergen(1,'month',APPL,100);
CALL vouchergen(1,'day',APPL,200);
I'm trying to create a simple table where I insert field and I do some checks in MySql. I've used Microsoft SQL relatively easy. Instead, MySql give evrrytime query errors without even specifying what's going on. Poor MySql software design apart, here's what I'm trying to do:
1 table with 4 fields with an autoincremental autogenerated number to det an ID as primary key
CREATE TABLE `my_db`.`Patients_table` (
`ID_Patient` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`Patient_name` VARCHAR( 200 ) NOT NULL ,
`Recovery_Date` DATETIME NOT NULL ,
`Recovery_count` INT NOT NULL
) ENGINE = MYISAM
a simple stored procedure to insert such fields and check if something exist before inserting:
CREATE PROCEDURE nameInsert(IN nome, IN data)
INSERT INTO Patients_table (Patient_name,Recovery_Date) values (nome,data)
IF (EXISTS (SELECT Recovery_count FROM Tabella_nomi) = 0) THEN
INSERT INTO (Patients_table (Recovery_count)
ELSE
SET Recovery_count = select Recovery_count+1 from Patients_table
END
this seems wrong on many levels and MySQL useless syntax checker does not help.
How can I do this? Thanks.
There seems to be a lot wrong with this block of code. (No offense intended!)
First, Procedures need to be wrapped with BEGIN and END:
CREATE PROCEDURE nameInsert(IN nome, IN data)
BEGIN
...[actually do stuff here]
END
Second, since your table is declared with all fields as NOT NULL, you must insert all fields with an INSERT statement (this includes the Recovery_Date column, and excludes the AUTO_INCREMENT column). You can add DEFAULT CURRENT_TIMESTAMP to the date column if you want it to be set automatically.
INSERT INTO Patients_table (Patient_name,Recovery_Date) values (nome,data)
Third, what exactly is your IF predicate doing?
EXISTS (SELECT Recovery_count FROM Tabella_nomi) = 0
If you want to check if a row exists, don't put the = 0 at the end. Also, Tabella_nomi isn't declared anywhere in that procedure. Also, your SELECT statement should have a WHERE clause, since I'm assuming you want to select a specific row (this is going to select a result set of all recovery_counts).
Fourth, the second INSERT statement seems a little messy. It should look more like the first INSERT, and keep the point I made above in mind.
INSERT INTO (Patients_table (Recovery_count)
Fifth, the ELSE statement
SET Recovery_count = select Recovery_count+1 from Patients_table
Has some problems too. SET is meant for setting variables, not values in rows. I'm not 100% sure what your intent is from this statement, but it looks like you meant to increment the Recovery_count column of a certain row if it already exists. In which case, you meant to do something like this:
UPDATE Patients_table SET Recovery_count = Recovery_count+1 WHERE <conditional predicate>
Where the conditional predicate is something like this:
Patients_name = nome
Try these things, and look at the errors it gives you when you try to execute the CREATE STATEMENT. I bet they're more useful then you think!
I had posted other questions relating to this problem, but haven't had any responses to directly address the issue of multiple row data imports from XLS. I'm an infrequent user of SQL or DBs in general, so my background/experience is limited in regard to writing these queries. If there is an easier or more direct approach to reach my goal, I'm certainly open to them. I don't mean to overpost or anything, but this site seems to be the most helpful (thank you everyone who has replied to my other post).
From some posts I have looked at, I understand that I have a working set-based query/trigger (since multiple rows do get imported). Ultimately I only need to import data into the parent table, and the child table can be populated with static values and or values from the parent table, but the PK/FK relationship needs to be maintained. And this is what I seem to have the most trouble with when more than 1 row of data is imported from XLS.
I have set up a trigger to insert values into a child table when a insert is executed on the parent table. The query executes correctly however I am unable to have the FK match the PK when multiple rows of data are inserted. The FK always has the ID of the last row inserted in the parent table. I have tried several approaches from other forum posts (here and on other sites) but always get errors.
Here is my updatePgVer Trigger code:
ALTER TRIGGER [updatePgVer]
ON [prototype].[dbo].[PageVersion]
FOR INSERT AS
BEGIN
SET NOCOUNT ON;
-- Insert into PageHistory
INSERT
INTO [prototype].[dbo].[PageHistory] ([VersionID], [Date], [Action], [Who], [StateId], [Owner])
SELECT
##IDENTITY
, GETDATE()
, 'created'
, 'xls_user'
, [StateID]
, 'xls_user'
FROM inserted
END
And the query used to insert into the parent table:
INSERT INTO [prototype].[dbo].[PageVersion] ([Number], [PageId], [Properties], [StateId], [Language], [SearchText], [PageMetaDescription], [PageMetaKeyWords], [PageTypeId], [Name], [Title], [Owner], [Admin], [ShowInMenu])
SELECT [Number], [PageId], [Properties], [StateId], [Language], [SearchText], [PageMetaDescription], [PageMetaKeyWords], [PageTypeId], [Name], [Title], [Owner], [Admin], [ShowInMenu]
FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0', 'Excel 8.0;Database=C:\test_import.xls', 'SELECT * FROM [Query$]');
The only other idea I have would to be to create some sort of loop that goes through each row and imports 1 at a time, so that the ##IDENTITY will always match. However, examples I have looked at seem hard to apply to my import.
The value for column VersionID, which appears to be the column with IDENTITY, is in the inserted table. You can reference it in your trigger like this
INSERT
INTO [prototype].[dbo].[PageHistory] ([VersionID], [Date], [Action], [Who], [StateId], [Owner])
SELECT
[VersionID],
, GETDATE()
, 'created'
, 'xls_user'
, [StateID]
, 'xls_user'
FROM inserted
If you want to see what data is available from inserted during the INSERT, temporarily put this in your trigger:
SELECT *
FROM inserted