The database is similar to my personal wallet and is designed for 10 altcoins, but can be easily changed for different coins and needs. It serves as a tool to keep track of cryptocurrency transactions and holdings. The code includes table structures for storing transaction information, including the transaction hash, date, amount, and type.
Of course, there are better ways to track currencies, such as Coin Gecko or Coinmarketcap, but it’s nice to have your own design. I enjoyed creating this project.
If you’re happy with my work, feel free to take the code in full or in parts to use for your own needs.
My most transaction is made on Binance but I’m also using Crypto.com, Coinbase, and Huobi. For now, let’s assume we are going to use only these four most popular cryptocurrency exchanges. If you’re using other exchanges, you can add your own symbols for your own needs. Just remember to change the constraints for the column. Be aware that the column unique identifier is a 16-byte binary SQL Server data type, so it can take up space if you’re planning a lot of transactions. However, it can be very helpful in identifying transactions.
Why did I use a UNIQUE NONCLUSTERED INDEX?
A UNIQUE constraint only allows one null in the column, so it is not suitable for my needs. I want the column Transaction_Hash to be a nullable column because not everybody needs the transaction hash. However, if you decide to insert the hash, the Transaction_Hash key has to be unique without duplicates, as in crypto transactions, there is no way for the transaction keys to be duplicated. Here is the website to track crypto transactions >>> www.blockchair.com
Transaction_Hash is used to track transactions on the blockchain, like cryptocurrency exchanges to wallets, wallet to wallet, or miner to a wallet.
Why am I using SEQUENCE?
A SEQUENCE will give me an ID independently, no matter which table I insert the transaction. That means I can easily track which coin I bought first its easy how much money I spend on crypto weekly, monthly, quarterly and yearly. All tables use the same SEQUENCE (newSeq).*
Symbols :
B – Binance
C – Coinbase
CR – Crypto.com
H – Huobi
Content of Database :
CREATE DATABASE
CREATE SEQUENCE
CREATE TABLE x 10
CREATE PROCEDURE
CREATE VIEW x 2
CREATE FUNCTION
TESTING
a few ways to introduce data:
OVER ( PARTITION BY [Name] ORDER BY Sum_tran
GROUP BY ROLLUP
WITH CTE_PIVOT AS
PIVOT
There is no need to create indexes on such a small database!
Download the full query here >>>>>>> Project_crypto
or
see at GitHub >>> https://github.com/jacenty79/Crypto_project_jacenty.net/blob/main/Crypto_Project_jacek79.net.sql
CODE :
CREATE DATABASE Project_Crypto_tracker USE Project_Crypto_tracker CREATE SEQUENCE newSeq AS BIGINT START WITH 1 INCREMENT BY 1 MINVALUE 1 GO --ALTER SEQUENCE newSeq RESTART WITH 1 -- to restart sequence if needed ! --================================== Patern for additional coin ==================== replace all {/} /* CREATE TABLE {/} ( [Id_Transaction] int not null PRIMARY KEY , [Name] nvarchar (30) not null , [Symbol] char(10) null , [Date_time] datetime not null , [Quantity] decimal(20,4) null , [Current_USD] money null , [Trans_type] char(3) not null , [Sum_tran] as cast((Quantity*Current_USD) as decimal(8,2) ) , [Unq_nr] uniqueidentifier not null , [Transaction_Hash] nvarchar(300) null ) ALTER TABLE {/} ADD CONSTRAINT [Chk_Symbol_UP_{/}] CHECK (Symbol = upper('{/}')) ALTER TABLE {/} ADD CONSTRAINT [Df_Date_time_{/}] DEFAULT (getdate()) FOR Date_time ALTER TABLE {/} ADD CONSTRAINT [Df_Trans_type_{/}] DEFAULT ('B') FOR Trans_type ALTER TABLE {/} ADD CONSTRAINT [Chk_Trans_type_{/}] CHECK ( Trans_type = upper ('B') or Trans_type = upper ('C') or Trans_type = upper ('CR') or Trans_type = upper ('H') ) ALTER TABLE {/} ADD CONSTRAINT [Df_Unq_nr_{/}] DEFAULT NEWSEQUENTIALID() FOR Unq_nr ALTER TABLE {/} ADD CONSTRAINT [Df_defaultName_{/}] DEFAULT ('{/}') FOR Name ALTER TABLE {/} ADD CONSTRAINT [Df_sequence_{/}] DEFAULT NEXT VALUE FOR newSeq FOR Id_Transaction CREATE UNIQUE NONCLUSTERED INDEX IND_TEST ON {/} (Transaction_Hash )WHERE Transaction_Hash IS NOT NULL */
There is no need to add all 10 tables on the page I will add only a pattern and one example table,
rest of the tables you will find in the download attachment
IF EXISTS (SELECT * FROM PolcaDot ) DROP TABLE PolcaDot CREATE TABLE PolcaDot ( [Id_Transaction] int not null PRIMARY KEY , [Name] nvarchar (30) not null , [Symbol] char(10) null , [Date_time] datetime not null , [Quantity] decimal(20,4) null , [Current_USD] money null , [Trans_type] char(3) null , [Sum_tran] as cast((Quantity*Current_USD) as decimal(8,2) ) , [Unq_nr] uniqueidentifier not null , [Transaction_Hash] nvarchar(300) null ) ALTER TABLE PolcaDot ADD CONSTRAINT [Chk_Symbol_UP] CHECK (Symbol = upper('DOT')) -- to prevent inserting incorect coins into DOT table ALTER TABLE PolcaDot ADD CONSTRAINT [Df_Date_time] DEFAULT (getdate()) FOR Date_time -- time of inserting into DB ALTER TABLE PolcaDot ADD CONSTRAINT [Df_Trans_type_POL] DEFAULT ('B') FOR Trans_type ALTER TABLE PolcaDot ADD CONSTRAINT [Chk_Trans_type] CHECK ( Trans_type = upper ('B') -- Binance or Trans_type = upper ('C') -- Coinbase or Trans_type = upper ('CR') -- Crypto.com or Trans_type = upper ('H') ) -- Huobi ALTER TABLE PolcaDot ADD CONSTRAINT [Df_Unq_nr] DEFAULT NEWSEQUENTIALID() FOR Unq_nr ALTER TABLE PolcaDot ADD CONSTRAINT [Df_defaultName] DEFAULT ('PolcaDot') FOR Name ALTER TABLE PolcaDot ADD CONSTRAINT [Df_sequence] DEFAULT NEXT VALUE FOR newSeq FOR Id_Transaction -- Using my sequence for Id_transaction -- UNIQUE CONSTRAINT allow only one null in column so UNIQUE NONCLUSTERED INDEX will allow more than one NULL and unique --Transaction_Hash key without duplicates as in crypto transaction there is no way for the transaction keys to be duplicated CREATE UNIQUE NONCLUSTERED INDEX IND_crypto_PolcaDot ON PolcaDot (Transaction_Hash )WHERE Transaction_Hash IS NOT NULL
Creating a procedure for transaction (Insert)
This SP is for quick insert and does not allow changing the date
for inserting with a different date than getdate() please use INSERT statement or UPDATE instead
IF EXISTS (SELECT * FROM sys.procedures WHERE name = 'Crypto_INSERT') DROP PROCEDURE Crypto_INSERT GO CREATE PROCEDURE Crypto_INSERT ( @Statement nvarchar(40) = '' , @Quantity decimal(20,4) , @Current_USD money , @Trans_type char(3) = default , @Transaction_Hash nvarchar(300) = default ) AS BEGIN if @Statement = 'LINK' begin Insert into Chainlink (Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash ) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash ) end if @Statement = 'DOT' begin Insert into PolcaDot ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'MATIC' begin Insert into Polygon ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'FTM' begin Insert into Fantom ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'CRO' begin Insert into Cronos ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'JASMY' begin Insert into JasmyCoin ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'GRT' begin Insert into TheGraph ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'BCH' begin Insert into BitcoinCash ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'REEF' begin Insert into Reef ( Symbol , Quantity ,Current_USD , Trans_type,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end if @Statement = 'XVG' begin Insert into Verge ( Symbol , Quantity ,Current_USD , Trans_type ,Transaction_Hash) OUTPUT inserted.* values ( @Statement , @Quantity ,@Current_USD ,@Trans_type , @Transaction_Hash) end print 'Transaction added into DB' if @Statement not in ('XVG','REEF','BCH','GRT','JASMY','LINK','CRO','DOT','FTM') begin select 'Transaction unsuccessful please check symbol of your coin ' as [Information] , CAST(getdate () as nvarchar(20)) as [DateTime] , SYSTEM_USER as [User] end END
Testing buying/selling options, procedure, constraints
######################## TESTING ############################################################################################# -- inserting Insert into PolcaDot ( Symbol , Quantity ,Trans_type ,Current_USD) values ( 'DOT' , 14.5 , 'B' , 186.89 ) ,( 'DOT' , 11.3 , 'CR' , 111.12 ) ,( 'DOT' , 100 , 'H' , 186.89 ) ----- testing selling >>> negative value Insert into PolcaDot ( Symbol , Quantity ,Trans_type ,Current_USD) values ('DOT' , -12 , 'B' , 100 ) Insert into PolcaDot ( Symbol , Quantity ,Trans_type ,Current_USD ,Transaction_Hash) -- testing with Hash values ('DOT' , 10 , 'b' , 30, '0xfeb138c320f01d255e3036c0ec42ce3023aa14bea11fe9218341d3667e4ee18A' ) -- success when inserting once , second failing as expected because Unique Nonclustered Index on column hash need to be unique Insert into PolcaDot ( Symbol , Quantity ,Trans_type ,Current_USD ) -- testing with Hash values ('GRT' , 10 , 'b' , 30 ) -- failed as expected because >>> CHECK constraint "Chk_Symbol_UP". The conflict occurred in database "Project_Crypto_tracker", table "dbo.PolcaDot", column 'Symbol' ############################### TESTING Stored Procedure ############################################################## Execute Crypto_INSERT 'Dot' ,19 ,6.93 -- transaction type NULL success ! Execute Crypto_INSERT 'Dot' ,20 ,5.52,'C' -- testing procedure with mix of letters in symbol column Execute Crypto_INSERT 'DOT' ,-9 ,6.78,'H' -- selling (negative value) Execute Crypto_INSERT 'LINK' ,1990 ,6.60,'cr','0xfeb138c320f01d255e3036c0ec42ce3023aa14bea11fe9218341d3667e4ee18L' Execute Crypto_INSERT 'CRO' ,50000 ,0.071334 Execute Crypto_INSERT 'XRP' , 10 ,55 -- not existing coin >>>> launches "if not" statement >> correct ! EXEC Crypto_INSERT 'CRO',303,0.071105,'CR' ########################################## The test passed successfully ########################################################
Creating View
GO CREATE VIEW Crypto_View_coins AS SELECT * FROM PolcaDot UNION SELECT * FROM Chainlink UNION SELECT * FROM [dbo].[Verge] UNION SELECT * FROM [dbo].[BitcoinCash] UNION SELECT * FROM [dbo].[Cronos] UNION SELECT * FROM [dbo].[Fantom] UNION SELECT * FROM [dbo].[JasmyCoin] UNION SELECT * FROM [dbo].[Polygon] UNION SELECT * FROM [dbo].[Reef] UNION SELECT * FROM [dbo].[TheGraph] ORDER BY Id_Transaction OFFSET 0 ROWS FETCH NEXT 5000 ROWS ONLY
In this example, it is easy to explain why I used the SEQUENCE “newSeq”. Regardless of which table the coin was inserted, in this view all coins are sorted by date in ascending order thanks to the single sequence used on all tables instead of repeating Id_Transaction
Creating UDF on View
GO CREATE FUNCTION QUickView ( @Symbol char(10) ) RETURNS TABLE AS RETURN ( SELECT * FROM Crypto_View_coins WHERE @Symbol = Symbol) SELECT * FROM QUickView ('XVG') -- testing !!
Creating target price – table with desired price, selling price
SELECT Name ,Symbol ,SUM(Sum_tran) as [Total_USD] ,SUM(Quantity) as [Total_coins] ,[AverageBuyingPrice] = SUM(Sum_tran) / SUM(Quantity) ,[My_target] = '--->>>>' INTO #Target_Price_sell FROM [dbo].[Crypto_View_coins] GROUP BY Name ,Symbol ALTER TABLE #Target_Price_sell ADD [Trg_price_coin] DECIMAL (10,2) -- insert your desire target coin price update #Target_Price_sell SET Trg_price_coin = 400 WHERE Symbol = 'BCH' update #Target_Price_sell SET Trg_price_coin = 1.50 WHERE Symbol = 'CRO' update #Target_Price_sell SET Trg_price_coin = 45 WHERE Symbol = 'DOT' update #Target_Price_sell SET Trg_price_coin = 2 WHERE Symbol = 'FTM' update #Target_Price_sell SET Trg_price_coin = 1.50 WHERE Symbol = 'GRT' update #Target_Price_sell SET Trg_price_coin = 0.15 WHERE Symbol = 'JASMY' update #Target_Price_sell SET Trg_price_coin = 45 WHERE Symbol = 'Link' update #Target_Price_sell SET Trg_price_coin = 3.50 WHERE Symbol = 'MATIC' update #Target_Price_sell SET Trg_price_coin = 0.03 WHERE Symbol = 'REEF' update #Target_Price_sell SET Trg_price_coin = 0.05 WHERE Symbol = 'XVG' SELECT * ,FORMAT (ceiling (Trg_price_coin / AverageBuyingPrice * 100.0) ,'P0','en-GB' )as [PercentFromBuingPrice] ,FORMAT (Trg_price_coin * Total_coins ,'C','en-US' ) as [Total_Desired_Profit] INTO Personal_target FROM #Target_Price_sell SELECT * FROM Personal_target
Creating View summary – how much money was invested per coin
GO CREATE VIEW Crypto_summary as SELECT Name ,CAST( SUM(Quantity) AS INT ) as [Total_coins] ,FORMAT (SUM(Sum_tran),'C','en-US' )AS [USD_invested] FROM [dbo].[Crypto_View_coins] GROUP BY Name ORDER BY USD_invested desc OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY SELECT * FROM Crypto_summary -- testing
Now a few queries to introduce data investment
-- OVER SELECT -- Running_investment Name ,Symbol ,Date_time ,Quantity ,Trans_type ,Sum_tran ,SUM(Sum_tran) OVER ( PARTITION BY [Name] ORDER BY Sum_tran) as [Running_investment] FROM [dbo].[Crypto_View_coins] -- ROLLUP SELECT -- Summary every Month / Year / Total Name ,SUM(Sum_tran) AS [USD_invested] ,YEAR(Date_time) as [YYdate] ,MONTH(Date_time) as[MMdate] , CASE WHEN YEAR(Date_time) IS NOT NULL AND MONTH(Date_time) IS NOT NULL THEN 'Month_Summary' WHEN YEAR(Date_time) IS NOT NULL AND MONTH(Date_time) IS NULL THEN 'Year_Summary' WHEN YEAR(Date_time) IS NULL AND MONTH(Date_time) IS NULL THEN 'Coin_Summary' END AS [Info] FROM [dbo].[Crypto_View_coins] GROUP BY ROLLUP ( Name ,YEAR(Date_time) ,MONTH(Date_time) ) -- PIVOT GO WITH CTE_PIVOT AS ( SELECT Sum_tran ,YEAR(Date_time) as [YYdate] ,MONTH(Date_time) as[MMdate] FROM [dbo].[Crypto_View_coins] ) SELECT YYdate ,ISNULL ([1] ,0.00) AS [January] ,ISNULL ([2] ,0.00) AS [February] ,ISNULL ([3] ,0.00) AS [March] ,ISNULL ([4] ,0.00) AS [April] ,ISNULL ([5] ,0.00) AS [May] ,ISNULL ([6] ,0.00) AS [June] ,ISNULL ([7] ,0.00) AS [July] ,ISNULL ([8] ,0.00) AS [August] ,ISNULL ([9] ,0.00) AS [September] ,ISNULL ([10],0.00) AS [October] ,ISNULL ([11],0.00) AS [November] ,ISNULL ([12],0.00) AS [December] FROM CTE_PIVOT PIVOT ( SUM (Sum_tran) FOR MMdate IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12]) ) AS PVT ORDER BY [YYdate]