SQL Server dynamic pivot table - sql

In SQL Server, I have two tables TableA and TableB, based on these I need to generate a report which is kind of very complex and after doing some research I come to a conclusion that I have to go with SQL Pivot table. Also, I tried this sample link but in my case the TableB table can have any number of child rows which makes it very complicated so, can anyone help me on this. Please see the details below:
Code
Create table TableA(
ProjectID INT NOT NULL,
ControlID INT NOT NULL,
ControlCode Varchar(2) NOT NULL,
ControlPoint Decimal NULL,
ControlScore Decimal NULL,
ControlValue Varchar(50)
)
Sample Data
ProjectID | ControlID | ControlCode | ControlPoint | ControlScore | ControlValue
P001 1 A 30.44 65 Invalid
P001 2 C 45.30 85 Valid
Code
Create table TableB(
ControlID INT NOT NULL,
ControlChildID INT NOT NULL,
ControlChildValue Varchar(200) NULL
)
Sample Data
ControlID | ControlChildID | ControlChildValue
1 100 Yes
1 101 No
1 102 NA
1 103 Others
2 104 Yes
2 105 SomeValue
Output should be in a single row for a given ProjectID with all its Control values first & followed by child control values (based on the ControlCode (i.e.) ControlCode_Child (1, 2, 3...) and it should look like this

Related

Is it possible to create two tables with disjoint identifiers?

By "disjoint" I mean mutually exclusive sets of ID values. No overlap between both tables.
For example, the sequence generator for the id column on both tables should work in conjunction to make sure they are always disjoint. I am not sure if this is possible. So, I thought I would just ask here.
Table A
id
name
0
abc
1
cad
2
pad
3
ial
Table B
id
name
40
pal
50
sal
A different hack-around:
CREATE TABLE odd(
id INTEGER GENERATED ALWAYS AS IDENTITY (START 1 INCREMENT 2)
, val integer
);
CREATE TABLE even(
id INTEGER GENERATED ALWAYS AS IDENTITY (START 2 INCREMENT 2)
, val integer
);
INSERT INTO odd (val)
SELECT GENERATE_SERIES(1,10);
INSERT INTO even (val)
SELECT GENERATE_SERIES(1,20);
SELECT * FROM odd;
SELECT * FROM even;
Result:
CREATE TABLE
CREATE TABLE
INSERT 0 10
INSERT 0 20
id | val
----+-----
1 | 1
3 | 2
5 | 3
7 | 4
9 | 5
11 | 6
13 | 7
15 | 8
17 | 9
19 | 10
(10 rows)
id | val
----+-----
2 | 1
4 | 2
6 | 3
8 | 4
10 | 5
12 | 6
14 | 7
16 | 8
18 | 9
20 | 10
22 | 11
24 | 12
26 | 13
28 | 14
30 | 15
32 | 16
34 | 17
36 | 18
38 | 19
40 | 20
(20 rows)
A very simple way is to share the same SEQUENCE:
CREATE TABLE a (
id serial PRIMARY KEY
, name text
);
CREATE TABLE b (
id int PRIMARY KEY
, name text
);
SELECT pg_get_serial_sequence('a', 'id'); -- 'public.a_id_seq'
ALTER TABLE b ALTER COLUMN id SET DEFAULT nextval('public.a_id_seq'); -- !
db<>fiddle here
This way, table a "owns" the sequence, while table b draws from the same source. You can also create an independent SEQUENCE if you prefer.
Note: this only guarantees mutually exclusive new IDs (even under concurrent write load) while you don't override default values and also don't update them later.
Related:
Creating a PostgreSQL sequence to a field (which is not the ID of the record)
Safely rename tables using serial primary key columns
Auto increment table column
PostgreSQL next value of the sequences?
Welcome to the painful world of inter-table constraints or assertions - this is something that ISO SQL and pretty much every RDBMS out there does not handle ergonomically...
(While ISO SQL does describe both deferred-constraints and database-wide assertions, as far as I know only PostgreSQL implements deferred-constraints, and no production-quality RDBMS supports database-wide assertions).
One approach is to have a third-table which is the only table with SERIAL (aka IDENTITY aka AUTO_INCREMENT) with a discriminator column which combined forms the table's primary-key, then the other two tables have an FK constraint to that PK - but they'll also need the same discriminator column (enforced with a CHECK constraint), but you will never need to reference that column in most queries.
As your post doesn't tell us what the real table-names are, I'll use my own.
Something like this:
CREATE TABLE postIds (
postId int NOT NULL SERIAL,
postType char(1) NOT NULL, /* This is the discriminator column. It can only contain ONLY either 'S' or 'G' which indicates which table contains the rest of the data */
CONSTRAINT PK_postIds PRIMARY KEY ( postId, postType ),
CONSTRAINT CK_type CHECK ( postType IN ( 'S', 'G' ) )
);
CREATE TABLE shitposts (
postId int NOT NULL,
postType char(1) DEFAULT('S'),
foobar nvarchar(255) NULL,
etc int NOT NULL,
CONSTRAINT PK_shitpostIds PRIMARY KEY ( postId, postType ),
CONSTRAINT CK_type CHECK ( postType = 'S' ),
CONSTRAINT FK_shitpost_ids FOREIGN KEY ( postId, postType ) REFERENCES postIds ( postId, postType )
);
CREATE TABLE goldposts (
postId int NOT NULL,
postType char(1) DEFAULT('G'),
foobar nvarchar(255) NULL,
etc int NOT NULL,
CONSTRAINT PK_goldpostIds PRIMARY KEY ( postId, postType ),
CONSTRAINT CK_type CHECK ( postType = 'G' ),
CONSTRAINT FK_goldpost_ids FOREIGN KEY ( postId, postType ) REFERENCES postIds ( postId, postType )
)
With this design, it is impossible for any row in shitposts to share a postId value with a post in goldposts and vice-versa.
However it is possible for a row to exist in postIds without having any row in both goldposts and shitposts. Fortunately, as you are using PostgreSQL you could add a new FK constraint from postIds to both goldposts and shitposts but use it with deferred-constraints.

Identity-like column but based on Group By criteria

In my SQL Server 2012 database, I'm creating a "Tasks" table that will have a compound primary key composed of three fields:
Issue_ID [int] NOT NULL,
Issue_Sub_ID [int] NOT NULL,
Task_ID [int] NOT NULL
Issue_ID and Issue_Sub_ID are foreign keys to other tables. In the table I'm creating, there can be many tasks associated with each Issue_ID / Issue_Sub_ID combination.
What I would like to do is establish a default value for the Task_ID column, similar to if I used IDENTITY(1,1), but that will auto-increment based on the Issue_ID / Issue_Sub_ID group. For example, the Task_ID results would look as follows, given the provided Issue_ID / Issue_Sub_ID values:
Issue_ID Issue_Sub_ID Task_ID
======== ============ =======
12345 1 1
12345 1 2
12345 1 3
12345 2 1
12345 2 2
67890 2 1
67890 2 2
67890 2 3
I'm familiar with the ROW_NUMBER() OVER(PARTITION BY Issue_ID, Issue_Sub_ID ORDER BY Issue_ID, Issue_Sub_ID) possible solution but, as I'd like this column to be a part of the compound primary key of the table, I don't think that will work.
Thanks all in advance.
I agree with Sean - add an identity column, and then just use a computed column for the task id.
Even though I've answered a question very much like this one here,
I'm not sure about marking this one as a duplicate. The reason for this is that you want to use the task_id as a part of the primary key.
However, I'm not sure that's possible, since in order to include a computed column in the primary key it must be persisted, and for some reason (I think it's because of the use of a UDF) SQL Server will not allow me to mark it as persisted.
Anyway, here is my proposed solution for this:
First, create a function that will calculate the task id:
CREATE FUNCTION dbo.GenerateTaskId
(
#Row_Id int,
#Issue_Id int,
#Issue_Sub_Id int
)
RETURNS Int
AS
BEGIN
RETURN
(
SELECT COUNT(*)
FROM dbo.Tasks
WHERE Issue_Id = #Issue_Id
AND Issue_Sub_ID = #Issue_Sub_ID
AND Row_Id <= #Row_Id
)
END
GO
Then, create the table with the task id as a computed column:
CREATE TABLE dbo.Tasks
(
Row_Id [int] IDENTITY(1,1),
Issue_ID [int] NOT NULL,
Issue_Sub_ID [int] NOT NULL,
Task_Id AS dbo.GenerateTaskId(Row_Id, Issue_Id, Issue_Sub_Id),
CONSTRAINT PK_Tasks PRIMARY KEY (Row_Id)
)
GO
Now, test it:
INSERT INTO Tasks VALUES
(12345, 1),
(12345, 1),
(12345, 1),
(12345, 2),
(12345, 2),
(67890, 2),
(67890, 2),
(67890, 2)
SELECT *
FROM Tasks
Results:
Row_Id Issue_ID Issue_Sub_ID Task_Id
1 12345 1 1
2 12345 1 2
3 12345 1 3
4 12345 2 1
5 12345 2 2
6 67890 2 1
7 67890 2 2
8 67890 2 3
You can see a live demo on rextester.

How to better duplicate a set of data in SQL Server

I have several related tables that I want to be able to duplicate some of the rows while updating the references.
I want to duplicate a row in Table1, and all of it's related rows from Table2 and Table3, and I'm trying to figure out an efficient way of doing it short of iterating through rows.
So for example, I have a table of baskets:
+----------+---------------+
| BasketId | BasketName |
+----------+---------------+
| 1 | Home Basket |
| 2 | Office Basket |
+----------+---------------+
Each basket has fruit:
+---------+----------+-----------+
| FruitId | BasketId | FruitName |
+---------+----------+-----------+
| 1 | 1 | Apple |
| 2 | 1 | Orange |
| 3 | 2 | Mango |
| 4 | 2 | Pear |
+---------+----------+-----------+
And each fruit has some properties:
+------------+---------+--------------+
| PropertyId | FruitId | PropertyText |
+------------+---------+--------------+
| 1 | 2 | Is juicy |
| 2 | 2 | Hard to peel |
| 3 | 1 | Is red |
+------------+---------+--------------+
For this example, my properties are specific to the individual fruit row, these "apple" properties aren't properties of all apples in all baskets, just for that specific apple in that specific basket.
What I want to do is duplicate a basket. So given basket 1, I want to create a new basket, duplicate the fruit rows it contains, and duplicate the properties pointing to those fruits. In the end I'm hoping to have data like so:
+----------+---------------+
| BasketId | BasketName |
+----------+---------------+
| 1 | Home Basket |
| 2 | Office Basket |
| 3 | Friends Basket|
+----------+---------------+
+---------+----------+-----------+
| FruitId | BasketId | FruitName |
+---------+----------+-----------+
| 1 | 1 | Apple |
| 2 | 1 | Orange |
| 3 | 2 | Mango |
| 4 | 2 | Pear |
| 5 | 3 | Apple |
| 6 | 3 | Orange |
+---------+----------+-----------+
+------------+---------+--------------+
| PropertyId | FruitId | PropertyText |
+------------+---------+--------------+
| 1 | 2 | Is juicy |
| 2 | 2 | Hard to peel |
| 3 | 1 | Is red |
| 4 | 6 | Is juicy |
| 5 | 6 | Hard to peel |
| 6 | 5 | Is red |
+------------+---------+--------------+
Duplicating the basket and it's fruit were pretty straightforward, but duplicating the properties of the fruit seems to me to lead to iterating over rows and I'm hoping there's a better solution in TSQL.
Any ideas?
Why dont you join on the FruitName to get a table with old and new FruitId's? Considering information would be added at the same time.... it may not be the best option but you wont be using any cycles.
INSERT INTO BASKET(BASKETNAME)
VALUES ('COPY BASKET')
DECLARE #iBasketId int
SET #iBasketId = ##SCOPE_IDENTITY;
insert into Fruit (BasketId, FruitName)
select #iBasketId, FruitName
from Fruit
where BasketId = #originalBasket
declare #tabFruit table (originalFruitId int, newFruitId int)
insert into #tabFruit (originalFruitId, newFruitId)
select o.FruitId, n.FruitId
from (SELECT FruitId, FruitName from Fruit where BasketId = #originalBasket) as o
join (SELECT FruitId, FruitName from Fruit where BasketId = #newBasket) as n
on o.FruitName = n.FruitName
insert into Property (FruitId, PropertyText)
select NewFruitId, PropertyText
from Fruit f join #tabFruit t on t.originalFruitId = f.FruitId
(ab)use MERGE with OUTPUT clause.
MERGE can INSERT, UPDATE and DELETE rows. In our case we need only to INSERT. 1=0 is always false, so the NOT MATCHED BY TARGET part is always executed. In general, there could be other branches, see docs. WHEN MATCHED is usually used to UPDATE; WHEN NOT MATCHED BY SOURCE is usually used to DELETE, but we don't need them here.
This convoluted form of MERGE is equivalent to simple INSERT, but unlike simple INSERT its OUTPUT clause allows to refer to the columns that we need.
I will write down the definitions of table explicitly. Each primary key in the tables is IDENTITY. I've configured foreign keys as well.
Baskets
CREATE TABLE [dbo].[Baskets](
[BasketId] [int] IDENTITY(1,1) NOT NULL,
[BasketName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Baskets] PRIMARY KEY CLUSTERED
(
[BasketId] ASC
)
Fruits
CREATE TABLE [dbo].[Fruits](
[FruitId] [int] IDENTITY(1,1) NOT NULL,
[BasketId] [int] NOT NULL,
[FruitName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Fruits] PRIMARY KEY CLUSTERED
(
[FruitId] ASC
)
ALTER TABLE [dbo].[Fruits] WITH CHECK
ADD CONSTRAINT [FK_Fruits_Baskets] FOREIGN KEY([BasketId])
REFERENCES [dbo].[Baskets] ([BasketId])
ALTER TABLE [dbo].[Fruits] CHECK CONSTRAINT [FK_Fruits_Baskets]
Properties
CREATE TABLE [dbo].[Properties](
[PropertyId] [int] IDENTITY(1,1) NOT NULL,
[FruitId] [int] NOT NULL,
[PropertyText] [varchar](50) NOT NULL,
CONSTRAINT [PK_Properties] PRIMARY KEY CLUSTERED
(
[PropertyId] ASC
)
ALTER TABLE [dbo].[Properties] WITH CHECK
ADD CONSTRAINT [FK_Properties_Fruits] FOREIGN KEY([FruitId])
REFERENCES [dbo].[Fruits] ([FruitId])
ALTER TABLE [dbo].[Properties] CHECK CONSTRAINT [FK_Properties_Fruits]
Copy Basket
At first copy one row in Baskets table and use SCOPE_IDENTITY to get the generated ID.
BEGIN TRANSACTION;
-- Parameter of the procedure. What basket to copy.
DECLARE #VarOldBasketID int = 1;
-- Copy Basket, one row
DECLARE #VarNewBasketID int;
INSERT INTO [dbo].[Baskets] (BasketName)
VALUES ('Friends Basket');
SET #VarNewBasketID = SCOPE_IDENTITY();
Copy Fruits
Then copy Fruits using MERGE and remember a mapping between old and new IDs in a table variable.
-- Copy Fruits, multiple rows
DECLARE #FruitIDs TABLE (OldFruitID int, NewFruitID int);
MERGE INTO [dbo].[Fruits]
USING
(
SELECT
[FruitId]
,[BasketId]
,[FruitName]
FROM [dbo].[Fruits]
WHERE [BasketId] = #VarOldBasketID
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT
([BasketId]
,[FruitName])
VALUES
(#VarNewBasketID
,Src.[FruitName])
OUTPUT Src.[FruitId] AS OldFruitID, inserted.[FruitId] AS NewFruitID
INTO #FruitIDs(OldFruitID, NewFruitID)
;
Copy Properties
Then copy Properties using remembered mapping between old and new Fruit IDs.
-- Copy Properties, many rows
INSERT INTO [dbo].[Properties] ([FruitId], [PropertyText])
SELECT
F.NewFruitID
,[dbo].[Properties].PropertyText
FROM
[dbo].[Properties]
INNER JOIN #FruitIDs AS F ON F.OldFruitID = [dbo].[Properties].FruitId
;
Check results, change rollback to commit once you confirmed that the code works correctly.
SELECT * FROM [dbo].[Baskets];
SELECT * FROM [dbo].[Fruits];
SELECT * FROM [dbo].[Properties];
ROLLBACK TRANSACTION;
I had the same need as the OP: cloning sql server tables where they are hierarchical sql server tables that contain foreign keys to one another. Or in other words, cloning sql server tables that have parent-child relationships.
Starting with #Tony_O 's answer/SQL, I converted it to my needs but discovered that the last line '..from Fruit f join..' should be '..from Property f join..'. Also, #newBasket should be #iBasketId.
So along with some other minor housekeeping fixes I found were needed for it to execute, plus using #Vladimir_Baranov 's DDL (with some missing parenthesis added), as well as making both of their SQL's object names consistent, since I had done the work I thought I would post it as a refinement of their work that will allow someone to to quickly test whether this solution solves their need. Just do 'find/replace' of the table names here with values from yours.
And note that if your Properties table has more fields than the single 'PropertyText' one in this example, just make sure to join on those additional fields as noted in the comment in the script.
-----------------
--create tables--
-----------------
--Baskets
CREATE TABLE [dbo].[Baskets](
[BasketId] [int] IDENTITY(1,1) NOT NULL,
[BasketName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Baskets] PRIMARY KEY CLUSTERED
(
[BasketId] ASC
)
)
--Fruits
CREATE TABLE [dbo].[Fruits](
[FruitId] [int] IDENTITY(1,1) NOT NULL,
[BasketId] [int] NOT NULL,
[FruitName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Fruits] PRIMARY KEY CLUSTERED
(
[FruitId] ASC
)
)
ALTER TABLE [dbo].[Fruits] WITH CHECK
ADD CONSTRAINT [FK_Fruits_Baskets] FOREIGN KEY([BasketId])
REFERENCES [dbo].[Baskets] ([BasketId])
ALTER TABLE [dbo].[Fruits] CHECK CONSTRAINT [FK_Fruits_Baskets]
--Properties
CREATE TABLE [dbo].[Properties](
[PropertyId] [int] IDENTITY(1,1) NOT NULL,
[FruitId] [int] NOT NULL,
[PropertyText] [varchar](50) NOT NULL,
CONSTRAINT [PK_Properties] PRIMARY KEY CLUSTERED
(
[PropertyId] ASC
)
)
ALTER TABLE [dbo].[Properties] WITH CHECK
ADD CONSTRAINT [FK_Properties_Fruits] FOREIGN KEY([FruitId])
REFERENCES [dbo].[Fruits] ([FruitId])
ALTER TABLE [dbo].[Properties] CHECK CONSTRAINT [FK_Properties_Fruits]
-------------------------
--Fill tables with data--
-------------------------
SET IDENTITY_INSERT [dbo].[Baskets] ON
GO
INSERT [dbo].[Baskets] ([BasketId], [BasketName]) VALUES (1, N'Home Basket')
GO
SET IDENTITY_INSERT [dbo].[Baskets] OFF
GO
SET IDENTITY_INSERT [dbo].[Fruits] ON
GO
INSERT [dbo].[Fruits] ([FruitId], [BasketId], [FruitName]) VALUES (1, 1, N'Apple')
GO
INSERT [dbo].[Fruits] ([FruitId], [BasketId], [FruitName]) VALUES (2, 1, N'Orange')
GO
SET IDENTITY_INSERT [dbo].[Fruits] OFF
GO
SET IDENTITY_INSERT [dbo].[Properties] ON
GO
INSERT [dbo].[Properties] ([PropertyId], [FruitId], [PropertyText]) VALUES (1, 2, N'is juicy')
GO
INSERT [dbo].[Properties] ([PropertyId], [FruitId], [PropertyText]) VALUES (2, 2, N'hard to peel')
GO
INSERT [dbo].[Properties] ([PropertyId], [FruitId], [PropertyText]) VALUES (3, 1, N'is red')
GO
SET IDENTITY_INSERT [dbo].[Properties] OFF
GO
--------------------------------------------------------------
-- Copy 'Home Basket' to new basket named 'COPY BASKET' --
-- i.e., Copy Basket (and all fruits and their fruit properties) having basket id
-- #origBasketId to a new basket with name 'COPY BASKET'.
--------------------------------------------------------------
DECLARE #originalBasket int
select #originalBasket = 1
begin tran
INSERT INTO BASKETS(BASKETNAME)
VALUES ('COPY BASKET')
DECLARE #newBasketId int
SET #newBasketId = SCOPE_IDENTITY();
insert into Fruits (BasketId, FruitName)
select #newBasketId, FruitName
from Fruits
where BasketId = #originalBasket
declare #tabFruit table (originalFruitId int, newFruitId int)
insert into #tabFruit (originalFruitId, newFruitId)
select o.FruitId, n.FruitId
from (SELECT FruitId, FruitName from Fruits where BasketId = #originalBasket) as o
join (SELECT FruitId, FruitName from Fruits where BasketId = #newBasketId) as n
on o.FruitName = n.FruitName --if your table equivalent to Fruits has other fields, match on those as well here.
insert into Properties (FruitId, PropertyText)
select NewFruitId, PropertyText
from Properties p join #tabFruit t on t.originalFruitId = p.FruitId
commit tran
---------------
--See results--
---------------
select *
from dbo.Baskets b inner join dbo.Fruits f on b.BasketId=f.BasketId
inner join properties p on p.FruitId=f.FruitId
order by b.BasketId, f.FruitId, p.PropertyId

improve database table design depending on a value of a type in a column

I have the following:
1. A table "patients" where I store patients data.
2. A table "tests" where I store data of tests done to each patient.
Now the problem comes as I have 2 types of tests "tests_1" and "tests_2"
So for each test done to particular patient I store the type and id of the type of test:
CREATE TABLE IF NOT EXISTS patients
(
id_patient INTEGER PRIMARY KEY,
name_patient VARCHAR(30) NOT NULL,
sex_patient VARCHAR(6) NOT NULL,
date_patient DATE
);
INSERT INTO patients values
(1,'Joe', 'Male' ,'2000-01-23');
INSERT INTO patients values
(2,'Marge','Female','1950-11-25');
INSERT INTO patients values
(3,'Diana','Female','1985-08-13');
INSERT INTO patients values
(4,'Laura','Female','1984-12-29');
CREATE TABLE IF NOT EXISTS tests
(
id_test INTEGER PRIMARY KEY,
id_patient INTEGER,
type_test VARCHAR(15) NOT NULL,
id_type_test INTEGER,
date_test DATE,
FOREIGN KEY (id_patient) REFERENCES patients(id_patient)
);
INSERT INTO tests values
(1,4,'test_1',10,'2004-05-29');
INSERT INTO tests values
(2,4,'test_2',45,'2005-01-29');
INSERT INTO tests values
(3,4,'test_2',55,'2006-04-12');
CREATE TABLE IF NOT EXISTS tests_1
(
id_test_1 INTEGER PRIMARY KEY,
id_patient INTEGER,
data1 REAL,
data2 REAL,
data3 REAL,
data4 REAL,
data5 REAL,
FOREIGN KEY (id_patient) REFERENCES patients(id_patient)
);
INSERT INTO tests_1 values
(10,4,100.7,1.8,10.89,20.04,5.29);
CREATE TABLE IF NOT EXISTS tests_2
(
id_test_2 INTEGER PRIMARY KEY,
id_patient INTEGER,
data1 REAL,
data2 REAL,
data3 REAL,
FOREIGN KEY (id_patient) REFERENCES patients(id_patient)
);
INSERT INTO tests_2 values
(45,4,10.07,18.9,1.8);
INSERT INTO tests_2 values
(55,4,17.6,1.8,18.89);
Now I think this approach is redundant or not to good...
So I would like to improve queries like
select * from tests WHERE id_patient=4;
select * from tests_1 WHERE id_patient=4;
select * from tests_2 WHERE id_patient=4;
Is there a better approach?
In this example I have 1 test of type tests_1 and 2 tests of type tests_2 for patient with id=4.
Here is a fiddle
Add a table testtype (id_test,name_test) and use it an FK to the id_type_test field in the tests table. Do not create seperate tables for test_1 and test_2
It depends on the requirement
For OLTP I would do something like the following
STAFF:
ID | FORENAME | SURNAME | DATE_OF_BIRTH | JOB_TITLE | ...
-------------------------------------------------------------
1 | harry | potter | 2001-01-01 | consultant | ...
2 | ron | weasley | 2001-02-01 | pathologist | ...
PATIENT:
ID | FORENAME | SURNAME | DATE_OF_BIRTH | ...
-----------------------------------------------
1 | hermiony | granger | 2013-01-01 | ...
TEST_TYPE:
ID | CATEGORY | NAME | DESCRIPTION | ...
--------------------------------------------------------
1 | haematology | abg | arterial blood gasses | ...
REQUEST:
ID | TEST_TYPE_ID | PATIENT_ID | DATE_REQUESTED | REQUESTED_BY | ...
----------------------------------------------------------------------
1 | 1 | 1 | 2013-01-02 | 1 | ...
RESULT_TYPE:
ID | TEST_TYPE_ID | NAME | UNIT | ...
---------------------------------------
1 | 1 | co2 | kPa | ...
2 | 1 | o2 | kPa | ...
RESULT:
ID | REQUEST_ID | RESULT_TYPE_ID | DATE_RESULTED | RESULTED_BY | RESULT | ...
-------------------------------------------------------------------------------
1 | 1 | 1 | 2013-01-02 | 2 | 5 | ...
2 | 1 | 2 | 2013-01-02 | 2 | 5 | ...
A concern I have with the above is with the unit of the test result, these can sometimes (not often) change. It may be better to place the unit un the result table.
Also consider breaking these into the major test categories as my understanding is they can be quite different e.g. histopathology and xrays are not resulted in the similar ways as haematology and microbiology are.
For OLAP I would combine request and result into one table adding derived columns such as REQUEST_TO_RESULT_MINS and make a single dimension from RESULT_TYPE and TEST_TYPE etc.
You can do this in a few ways. without knowing all the different type of cases you need to deal with.
The simplest would be 5 tables
Patients (like you described it)
Tests (like you described it)
TestType (like Declan_K suggested)
TestResultCode
TestResults
TestRsultCode describe each value that is stored for each test. TestResults is a pivoted table that can store any number of test-results per test,:
Create table TestResultCode
(
idTestResultCode int
, Code varchar(10)
, Description varchar(200)
, DataType int -- 1= Real, 2 = Varchar, 3 = int, etc.
);
Create Table TestResults
(
idPatent int -- FK
, idTest int -- FK
, idTestType int -- FK
, idTestResultCode int -- FK
, ResultsI real
, ResultsV varchar(100)
, Resultsb int
, Created datetime
)
so, basically you can fit the results you wanted to add into the tables "tests_1" and "tests_2" and any other tests you can think of.
The application reading this table, can load each test and all its values. Of course the application needs to know how to deal with each case, but you can store any type of test in this structure.

Write SQL script to insert data

In a database that contains many tables, I need to write a SQL script to insert data if it is not exist.
Table currency
| id | Code | lastupdate | rate |
+--------+---------+------------+-----------+
| 1 | USD | 05-11-2012 | 2 |
| 2 | EUR | 05-11-2012 | 3 |
Table client
| id | name | createdate | currencyId|
+--------+---------+------------+-----------+
| 4 | tony | 11-24-2010 | 1 |
| 5 | john | 09-14-2010 | 2 |
Table: account
| id | number | createdate | clientId |
+--------+---------+------------+-----------+
| 7 | 1234 | 12-24-2010 | 4 |
| 8 | 5648 | 12-14-2010 | 5 |
I need to insert to:
currency (id=3, Code=JPY, lastupdate=today, rate=4)
client (id=6, name=Joe, createdate=today, currencyId=Currency with Code 'USD')
account (id=9, number=0910, createdate=today, clientId=Client with name 'Joe')
Problem:
script must check if row exists or not before inserting new data
script must allow us to add a foreign key to the new row where this foreign related to a row already found in database (as currencyId in client table)
script must allow us to add the current datetime to the column in the insert statement (such as createdate in client table)
script must allow us to add a foreign key to the new row where this foreign related to a row inserted in the same script (such as clientId in account table)
Note: I tried the following SQL statement but it solved only the first problem
INSERT INTO Client (id, name, createdate, currencyId)
SELECT 6, 'Joe', '05-11-2012', 1
WHERE not exists (SELECT * FROM Client where id=6);
this query runs without any error but as you can see I wrote createdate and currencyid manually, I need to take currency id from a select statement with where clause (I tried to substitute 1 by select statement but query failed).
This is an example about what I need, in my database, I need this script to insert more than 30 rows in more than 10 tables.
any help
You wrote
I tried to substitute 1 by select statement but query failed
But I wonder why did it fail? What did you try? This should work:
INSERT INTO Client (id, name, createdate, currencyId)
SELECT
6,
'Joe',
current_date,
(select c.id from currency as c where c.code = 'USD') as currencyId
WHERE not exists (SELECT * FROM Client where id=6);
It looks like you can work out if the data exists.
Here is a quick bit of code written in SQL Server / Sybase that I think answers you basic questions:
create table currency(
id numeric(16,0) identity primary key,
code varchar(3) not null,
lastupdated datetime not null,
rate smallint
);
create table client(
id numeric(16,0) identity primary key,
createddate datetime not null,
currencyid numeric(16,0) foreign key references currency(id)
);
insert into currency (code, lastupdated, rate)
values('EUR',GETDATE(),3)
--inserts the date and last allocated identity into client
insert into client(createddate, currencyid)
values(GETDATE(), ##IDENTITY)
go