Generate unique id in SQL Server - sql

How can I create a table with custom ID like 22/00/00001 with a sequence? Sorry I'm a newbie with databases. All help will be appreciated thank you!
I am an intern at a hospital. I have to rewrite a database with SQL Server, I have a problem with specific ID numbers. It must be in year/month/id00001 format, for example 11/22/02586. And each department must have its own patient counter. Any help will be appreciated thanks!.
Example:
CREATE SEQUENCE MySequence
AS INT
START WITH 1
INCREMENT BY 1;
GO
-- Create a new table called 'PATIENT' in schema 'dbo'
-- Drop the table if it already exists
IF OBJECT_ID('dbo.PATIENT', 'U') IS NOT NULL
DROP TABLE dbo.PATIENT
GO
-- Create the table in the specified schema
CREATE TABLE dbo.PATIENT
(
NUM_PAT [VARCHAR] (20) NOT NULL
PRIMARY KEY
CONSTRAINT [DF_PATIENT_ID_PATIENT]
DEFAULT FORMAT ((NEXT VALUE FOR MySequence), '22/00/00000#'), -- primary key column
NOM_PAT [VARCHAR](50) NOT NULL,
PREN_PAT [VARCHAR](50) NOT NULL,
DN_PAT [DATE] NOT NULL,
-- specify more columns here
);
GO
-- Insert rows into table 'PATIENT'
INSERT INTO PATIENT ([NOM_PAT], [PREN_PAT], [DN_PAT])
VALUES ('fred', 'alves', '25/11/1990'),
('alvaldo', 'merlin', '11/09/1985');
-- add more rows here
GO

I'm checking your request, I think you can aproach in two way, a way is define a table where you will save the patient id and a column with counter to generate your custom id
Example
Table name like PatientCounter
PatientID, PatientCounter
1 1
2 125
Or in table patient add a column Counter, I prefer the thing separeted, but this also is a option, then in the query you could do something like that.
DECLARE #PatientId as int
DECLARE #PatientCount as int
SET #PatientId = 21
SET #PatientCount = 1
SELECT
CAST(FORMAT(GETDATE(),'yy') as nvarchar(4)) + '/' +
CAST(MONTH(getDATE()) as nvarchar(4)) + '/' +
CAST(#PatientId as nvarchar(10)) +
CAST(REPLICATE('0', 5 - LEN(#PatientCount + 1)) + RTrim(#PatientCount + 1) as nvarchar(50)) as MyCustomId
The result
MyCustomId
22/11/2100002
And after confirming that your application has consumed this custom id, update the counter in the table.
UPDATE PatientTable SET PatientCount = PatientCount + 1 WHERE PatientID = #PatientId
Best Regards

Related

How to generate unique incremental employee code(pattern: E0001, E0002, E0003, ... ) in the INSERT stored procedure in SQL Server?

I want to generate unique employee code at a time of inserting a single record in INSERT stored procedure.
First-time procedure call will generate employee code as E0001, the second call will generate E0002, ...
The expected output should look like,
EmployeeCode EmployeeName
-------------------------------
E0001 A
E0002 B
E0003 C
E0004 D
E0005 E
' '
' '
' '
E9999 ZZZ
You can embed a sequence value into a custom-formatted string in a default, and enforce it with a check constraint. Like this:
--drop table if exists Employee
--drop sequence seq_Employee
--go
create sequence seq_Employee
start with 1
increment by 1
go
CREATE TABLE Employee
(
EmployeeCode char(5) PRIMARY KEY
default 'E' + format((next value for seq_Employee),'0000' )
check (EmployeeCode like 'E[0-9][0-9][0-9][0-9]'),
EmployeeName VARCHAR(50) NOT NULL
)
go
insert into Employee(EmployeeName)
values ('A'),('B'),('C'),('D'),('E')
select *
from Employee
Outputs
EmployeeCode EmployeeName
------------ --------------------------------------------------
E0001 A
E0002 B
E0003 C
E0004 D
E0005 E
Use an identity column to create a unique id. Then use this column for references from other tables. You can create a code if you like as a computed column:
create table employees (
employeeId int identity(1, 1) primary key,
employeeName varchar(255),
employeeCode as (concat('E', format(employeeId, '00000')))
);
Here is a db<>fiddle.
Use a database sequence number in your procedure to provide you the values. In order to get the value 'E' appended, concatenate 'E' with the sequence number in the query.
Use this link to understand the concept of database sequence number in sql server
CREATE SEQUENCE dbo.Seq
START WITH 1
INCREMENT BY 1;
GO
DECLARE #T TABLE (
E_CODE VARCHAR(50) PRIMARY KEY,
E_Name VARCHAR(MAX)
);
INSERT INTO #T VALUES (CONCAT('E',FORMAT(NEXT VALUE FOR dbo.SEQ,'0000')),'A')
INSERT INTO #T VALUES (CONCAT('E',FORMAT(NEXT VALUE FOR dbo.SEQ,'0000')),'B')
INSERT INTO #T VALUES (CONCAT('E',FORMAT(NEXT VALUE FOR dbo.SEQ,'0000')),'C')
SELECT * FROM #T

If a table has an unindexed column with a 1 to many relationship to an indexed column, how to optimize a query for the unindexed column?

If there is a two column table MyTable with enough records that optimization of queries is relevant.
CorporationID int (unindexed)
BatchID int (indexed)
And lets assume there is always a 1 to many relationship between CorporationID and BatchID. In other words for each BatchID there will be only one CorporationID, but for each CorporationID there will be many BatchID values.
We need to get all BatchID values where corporationID = 1.
I know the simplest solution may be to just add an index to CorporationID, but assuming that is not allowed, is there some other way to inform SQL that each BatchID corresponds to only 1 CorporationID, through a query or otherwise?
select distinct batchid from MyTable where corporationID = 1
It seems this is not effective.
select batchid from (select min(corporationid) corporationid, batchid
from MyTable group by batchid) subselect where corporationid = 1
This is also not effective, I assume due to SQL needing to iterate needlessly through all values of corporationid? (Does an aggregate function exist to select any() value which would not have the overhead of min(), max(), sum() or avg()??)
select batchid
from (
select corporationid, batchid
from (
select *, ROW_NUMBER() OVER (PARTITION BY batchid ORDER BY(SELECT NULL)) AS RowNumber
from mytable
) subselect
where RowNumber = 1
) subselect2
where corporationid = 1
Would this work? By arbitrarily selecting the corporationid related to row number 1 after partitioning by batchid with no order?
"assuming it is not allowed to create an index" - this is a highly unlikely assumption. Of course, you should create the index.
The most direct answer to your alternate questions that lie within your question is "no". There is no function or sub query or view or other "read" action you can make to get a list of the batches for a given CorpID. You NEED to access the corpID data to do that... all your sample queries do not work because, at some point, they NEED to access the CorpIDs to know which rows to gather for BatchIDs. Any summary or "rollup" function that might exist would still NEED to access all the pages of data to "see" them. The reading of the pages cannot be avoided.
Without changes to your architecture, it's not physically possible to optimize your query further.
However, with some changes, you could have some options (but Id guess they are much uglier than just adding the index). For instance, you could modify the structure of your BatchID to include data for both the BatchID and the CorpID. Something like "8888899999999"... the 9's are the batchID and the 8's are the CorpID. This doesn't win you much though, you're not saving any index space, but at least you dont have to index the CorpID field :) Somethings like this could be done, but I wont share any others. I dont want the really experienced people here to see this stuff and get ill. :)
You need an index on CorpID if you want to improve performance.
If you don't have a lot of data, I suggest putting an index on the Corporation ID column. But if you have too much data, you can define an index for each Corporation ID
Part 01=>
/*01Create DB*/
IF DB_ID('Test01')>0
BEGIN
ALTER DATABASE Test01 SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE Test01
END
GO
CREATE DATABASE Test01
GO
USE Test01
Go
Part 02=>
/*02Create table*/
CREATE TABLE Table01(
ID INT PRIMARY KEY IDENTITY,
Title NVARCHAR(100),
CreationDate DATETIME,
CorporationID INT ,
MyID INT ,
[GuidId1] [uniqueidentifier] NOT NULL,
[GuidId2] [uniqueidentifier] NOT NULL,
[Code] [nvarchar](50) NULL
)
ALTER TABLE [dbo].[Table01] ADD DEFAULT (GETDATE()) FOR [CreationDate]
GO
ALTER TABLE [dbo].[Table01] ADD DEFAULT (NEWSEQUENTIALID()) FOR [GuidId1]
GO
ALTER TABLE [dbo].[Table01] ADD DEFAULT (NEWID()) FOR [GuidId2]
GO
CREATE TABLE Table02(
ID INT PRIMARY KEY IDENTITY,
Title NVARCHAR(100),
CreationDate DATETIME,
CorporationID INT ,
MyID INT ,
[GuidId1] [uniqueidentifier] NOT NULL,
[GuidId2] [uniqueidentifier] NOT NULL,
[Code] [nvarchar](50) NULL
)
ALTER TABLE [dbo].[Table02] ADD DEFAULT (GETDATE()) FOR [CreationDate]
GO
ALTER TABLE [dbo].[Table02] ADD DEFAULT (NEWSEQUENTIALID()) FOR [GuidId1]
GO
ALTER TABLE [dbo].[Table02] ADD DEFAULT (NEWID()) FOR [GuidId2]
GO
Part 03=>
/*03Add Data*/
DECLARE #I INT = 1
WHILE #I < 1000000
BEGIN
DECLARE #Title NVARCHAR(100) = 'TITLE '+ CAST(#I AS NVARCHAR(10)),
#CorporationID INT = CAST((RAND()*20) + 1 AS INT),
#Code NVARCHAR(50) = 'CODE '+ CAST(#I AS NVARCHAR(10)) ,
#MyID INT = CAST((RAND()*50) + 1 AS INT)
INSERT INTO Table01 (Title , CorporationID , Code , MyID )
VALUES ( #Title , #CorporationID , 'CODE '+ #Code , #MyID)
SET #I += 1
END
INSERT INTO Table02 ([Title], [CreationDate], [CorporationID], [MyID], [GuidId1], [GuidId2], [Code])
SELECT [Title], [CreationDate], [CorporationID], [MyID], [GuidId1], [GuidId2], [Code] FROM Table01
Part 04=>
/*04 CREATE INDEX*/
CREATE NONCLUSTERED INDEX IX_Table01_ALL
ON Table01 (CorporationID) INCLUDE (MyID) ;
DECLARE #QUERY NVARCHAR(MAX) = ''
DECLARE #J INT = 1
WHILE #J < 21
BEGIN
SET #QUERY += '
CREATE NONCLUSTERED INDEX IX_Table02_'+CAST(#J AS NVARCHAR(5))+'
ON Table02 (CorporationID) INCLUDE (MyID) WHERE CorporationID = '+CAST(#J AS NVARCHAR(5))+';'
SET #J+= 1
END
EXEC (#QUERY)
Part 05=>
/*05 READ DATA => PUSH Button CTRL + M ( EXECUTION PLAN) */
SET STATISTICS IO ON
SET STATISTICS TIME ON
SELECT * FROM [dbo].[Table01] WHERE CorporationID = 10 AND MyID = 25
SELECT * FROM [dbo].[Table01] WITH(INDEX(IX_Table01_ALL)) WHERE CorporationID = 10 AND MyID = 25
SELECT * FROM [dbo].[Table02] WITH(INDEX(IX_Table02_10)) WHERE CorporationID = 10 AND MyID = 25
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
Notice IO , TIME , and EXECUTION PLAN .
Good luck

Why Optimizer Does Not Use Index Seek on Join

I wonder why the following SELECT statement (below) does not use Index Seek, but Index Scan. Is it just because the number of rows is too small or am I missing something?
Test data:
-- Init Tables
IF OBJECT_ID ( 'tempdb..#wat' ) IS NOT NULL
DROP TABLE #wat;
IF OBJECT_ID ( 'tempdb..#jam' ) IS NOT NULL
DROP TABLE #jam;
CREATE TABLE #wat (
ID INT IDENTITY(1,1) NOT NULL,
Name VARCHAR(15) NOT NULL,
Den DATETIME NOT NULL
)
CREATE TABLE #jam (
ID INT IDENTITY(1,1) NOT NULL,
Name VARCHAR(15) NOT NULL
)
-- Populate Temp Tables with Random Data
DECLARE #length INT
,#charpool VARCHAR(255)
,#poolLength INT
,#RandomString VARCHAR(255)
,#LoopCount INT
SET #Length = RAND() * 5 + 8
SET #CharPool = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ23456789'
SET #PoolLength = LEN(#CharPool)
SET #LoopCount = 0
SET #RandomString = ''
WHILE (#LoopCount < 500)
BEGIN
INSERT INTO #jam (Name)
SELECT SUBSTRING(#Charpool, CONVERT(int, RAND() * #PoolLength), 5)
SET #LoopCount = #LoopCount + 1
END
-- Insert Rows into Second Temp Table
INSERT INTO #wat( Name, Den )
SELECT TOP 50 Name, GETDATE()
FROM #jam
-- Create Indexes
--DROP INDEX IX_jedna ON #jam
--DROP INDEX IX_dva ON #wat
CREATE INDEX IX_jedna ON #jam (Name) INCLUDE (ID);
CREATE INDEX IX_dva ON #wat (Name) INCLUDE (ID, Den);
-- Select
SELECT *
FROM #jam j
JOIN #wat w
ON w.Name = j.Name
Execution Plan:
There are several ways for optimiser to do jons: nested loops, hash match or merge join (your case) and may be another.
In dependence of your data: count of rows, existed indexes and statistics id decides which one is better.
in your example optimiser assumes that there is many-to-many relation. And you have both tables soret(indexed) by this fields.
why merge join? - it is logically - to move through both tables parallel. And server will have to do that only once.
To make seek as you want, the server have to move thorugh first table once, and have to make seeks in second table a lot of times, since all records have matches in another table. Server will read all records if he seeks. And there no profit when using seek (1000 seeks even more difucult than one simple loop through 1000 records).
if you want seek add some records with no matches and where clause in your query.
UPD
even adding simple
where j.ID = 1
gives you seek

Accessing data from SQL table containing ID, [Value Type] and Value

If this question has been asked before, please share a link to that question. I've done some searching and was not able to find an answer to a question like this. This is possibly due to the difficulty I've encountered trying to word it into a query that displays the results I'm looking for.
Background:
I'm designing a database to hold some information (like databases usually do). This particular database holds, just for the purpose of this question, three tables.
The three tables store different information; one table defines the user by a unique ID and user name, the second stores the types of information that can be stored for a user and the third stores the information about the user .
Users table is defined with:
CREATE TABLE dbo.Users (
Id UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
[User Name] NVARCHAR(1024) NOT NULL
);
The [User Info Data Type] table is defined as:
CREATE TABLE dbo.[User Info Data Type] (
Id UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
[Data Type Name] NVARCHAR(256) NOT NULL
);
Last but not least, the [User Info] table is defined as:
CREATE TABLE dbo.[User Info] (
Id UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
[User Id] UNIQUEIDENTIFIER NOT NULL, --FK from [Users].Id
[User Info Type] UNIQUEIDENTIFIER NOT NULL, --FK from [User Info Data Dype].Id
Value VARBINARY(MAX) NULL
);
Question:
I've seen this done before (at a previous job) but I'm not as advanced in SQL as I wish I was. I'd like to know how I can aggregate the data in the third table against a single record in the first table using the second table to define the header of the data in the third table.
I'll even clarify the results I'm expecting.
Let's say the 1st table has 1 record, it's name column contains Dave.
The 2nd table has an entry for a "Created" type.
The 3rd table has an entry that has Dave's Id, the "Created" Id and the value (in binary) of the created date.
Let's say for arguments sake that "Dave's" record was created August 24, 2015 at 00:00h. The result of the query would yield:
User Name | Created
Dave | 8D2AC16F443C000
The column name would be pulled from the second table and the value from the third.
I understand that this is complicated of an ask but any help would be appreciated.
Thanks in advance!
-Dave
This is an EAV model. As commented by Nick, you should do a research on its pros and cons. However, if you wish to pursue this model, your queries would rely so much on PIVOTs or Crosstabs. Here is an example using dynamic crosstab:
First, we create the sample data. Note, I removed the spaces from your column names.
CREATE TABLE #Users (
Id UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
UserName NVARCHAR(1024) NOT NULL
);
CREATE TABLE #UserInfoDataType (
Id UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
DataTypeName NVARCHAR(256) NOT NULL
);
CREATE TABLE #UserInfo (
Id UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
UserId UNIQUEIDENTIFIER NOT NULL, --FK from [#Users].Id
UserInfoType UNIQUEIDENTIFIER NOT NULL, --FK from [User Info Data Dype].Id
Value VARBINARY(MAX) NULL
);
INSERT INTO #Users(UserName) VALUES('Dave');
INSERT INTO #UserInfoDataType(DataTypeName) VALUES('Created'), ('FirstName'), ('LastName')
INSERT INTO #UserInfo(UserId, UserInfoType, Value)
SELECT
u.Id,
uidt.Id,
Value =
CASE
WHEN uidt.DataTypeName = 'Created' THEN CONVERT(VARBINARY(MAX), CONVERT(VARCHAR(10),GETDATE(), 101))
WHEN uidt.DataTypeName = 'FirstName' THEN CONVERT(VARBINARY(MAX), 'Dave')
WHEN uidt.DataTypeName = 'LastName' THEN CONVERT(VARBINARY(MAX), 'Doe')
END
FROM #Users u
CROSS JOIN #UserInfoDataType uidt
Now, for the dynamic crosstab solution:
DECLARE #sql1 NVARCHAR(2000) = '',
#sql2 NVARCHAR(2000) = '',
#sql3 NVARCHAR(2000) = ''
SELECT #sql1 =
'SELECT
u.UserName' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN i.UserInfoType = ''' + CONVERT(VARCHAR(MAX), Id) + ''' THEN CONVERT(VARCHAR(MAX), i.Value) END) AS ' + QUOTENAME(DataTypeName) + CHAR(10)
FROM #UserInfoDataType
SELECT #sql3 =
'FROM #UserInfo i
INNER JOIN #Users u ON u.Id = i.UserId
INNER JOIN #UserInfoDataType t ON t.Id = i.UserInfoType
GROUP BY i.UserId, u.UserName'
PRINT(#sql1 + #sql2+ #sql3)
EXEC(#sql1 + #sql2+ #sql3)
RESULT:
UserName Created FirstName LastName
-------- ---------- --------- --------
Dave 08/25/2015 Dave Doe
See it here in SEDE.

insert data into several tables

Let us say I have a table (everything is very much simplified):
create table OriginalData (
ItemName NVARCHAR(255) not null
)
And I would like to insert its data (set based!) into two tables which model inheritance
create table Statements (
Id int IDENTITY NOT NULL,
ProposalDateTime DATETIME null
)
create table Items (
StatementFk INT not null,
ItemName NVARCHAR(255) null,
primary key (StatementFk)
)
Statements is the parent table and Items is the child table. I have no problem doing this with one row which involves the use of IDENT_CURRENT but I have no idea how to do this set based (i.e. enter several rows into both tables).
Thanks.
Best wishes,
Christian
Another possible method that would prevent the use of cursors, which is generally not a best practice for SQL, is listed below... It uses the OUTPUT clause to capture the insert results from the one table to be used in the insert to the second table.
Note this example makes one assumption in the fact that I moved your IDENTITY column to the Items table. I believe that would be acceptable, atleast based on your original table layout, since the primary key of that table is the StatementFK column.
Note this example code was tested via SQL 2005...
IF OBJECT_ID('tempdb..#OriginalData') IS NOT NULL
DROP TABLE #OriginalData
IF OBJECT_ID('tempdb..#Statements') IS NOT NULL
DROP TABLE #Statements
IF OBJECT_ID('tempdb..#Items') IS NOT NULL
DROP TABLE #Items
create table #OriginalData
( ItemName NVARCHAR(255) not null )
create table #Statements
( Id int NOT NULL,
ProposalDateTime DATETIME null )
create table #Items
( StatementFk INT IDENTITY not null,
ItemName NVARCHAR(255) null,
primary key (StatementFk) )
INSERT INTO #OriginalData
( ItemName )
SELECT 'Shirt'
UNION ALL SELECT 'Pants'
UNION ALL SELECT 'Socks'
UNION ALL SELECT 'Shoes'
UNION ALL SELECT 'Hat'
DECLARE #myTableVar table
( StatementFk int,
ItemName nvarchar(255) )
INSERT INTO #Items
( ItemName )
OUTPUT INSERTED.StatementFk, INSERTED.ItemName
INTO #myTableVar
SELECT ItemName
FROM #OriginalData
INSERT INTO #Statements
( ID, ProposalDateTime )
SELECT
StatementFK, getdate()
FROM #myTableVar
You will need to write an ETL process to do this. You may want to look into SSIS.
This also can be done with t-sql and possibly temp tables. You may need to store unique key from OriginalTable in Statements table and then when you are inserting Items - join OriginalTable with Statements on that unique key to get the ID.
I don't think you could do it in one chunk but you could certainly do it with a cursor loop
DECLARE #bla char(10)
DECLARE #ID int
DECLARE c1 CURSOR
FOR
SELECT bla
FROM OriginalData
OPEN c1
FETCH NEXT FROM c1
INTO #bla
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO Statements(ProposalDateTime) VALUES('SomeDate')
SET #ID = SCOPE_IDENTITY()
INSERT INTO Items(StateMentFK,ItemNAme) VALUES(#ID,#bla)
FETCH NEXT FROM c1
INTO #bla
END
CLOSE c1
DEALLOCATE c1