Best way to normalize a table - sql

Need suggestions on what would be the best way to normalize the following table:
Earlier, I had a table:
personId year dob column1 column2 column3
-------- ---- --- ------- ------- -------
Here, (personId+year) was the primary key, and columns dob, column1, column2 and column3 had unique values.
Now, according to a new requirement column1, column2 and column3 will be storing multiple values. In a very naive sense, it is supposed to hold values like:
personId year dob column1 column2 column3
-------- ---- ------ ------- ------- -------
1 2018 2.1.20 A1, A2 B1 C1, C2, C3
I simply do not want to normalize it till First Normal Form, but want to break it into more tables, like:
Table 1:
personId year dob
-------- ---- ------
1 2018 2.1.20
Table 2:
personId year column1
-------- ---- ------
1 2018 A1
1 2018 A2
Table 3:
personId year column2
-------- ---- ------
1 2018 B1
Table 4:
personId year column3
-------- ---- ------
1 2018 C1
1 2018 C2
1 2018 C3
Now Table 1 looks fine to me as it still has a PK of (personId+year), but Tables 2-3 don't look very elegant as they lack a primary key.
Are there better ways to achieve this?

You are close. You should introduce a new primary key for the first table and I would recommend primary keys for the others as well:
Table 1:
t1Id personId year dob
1 1 2018 2.1.20
Table 2:
t2Id t1Id column1
1 1 A1
2 1 A2
Table 3:
t3Id t1id column2
1 1 B1
Table 4:
t4Id t1id column3
1 1 C1
2 1 C2
3 1 C3
If ordering is important, you may also want to include a sequential numbering column in all but the first table.
In addition, the data columns in the last three tables should perhaps be ids to their own tables.

Related

Generate frequency table from sql using Count with user defined condition

Basically I need to generate a frequency table using sql, and I have a sample table like this:
user_id user_label code1 date
------ ----------- ----- ------
1 x a 01-01
1 x a 01-01
1 x a 01-02
1 x b 01-01
1 x c 01-02
1 y a 01-01
2 x a 01-01
etc
The rule to count occurrences is if two rows have the same user_id ,user_label and date ,then repeated codes should only be counted once.
For example, for the first two rows the frequency table should be :
user_id user_label code1 count_code_1
-------- ----------- ----- ------------
1 x a 1
Because even though there are two instances of a, but they happen on the same date so should only be counted once and I need do this for every unique codes in code_1 column
for all combinations of user_id + user_label
After processing the third row , the frequency table should be :
user_id user_label code_1 count_code_1
-------- ----------- ------ ------------
1 x a 2
Since although is the same code ('a') but it happens on a different date (01-02)
In the end, for the sample table given above, the desired result should be
user_id user_label code_1 count_code_1
-------- ----------- ------ -------------
1 x a 2
1 x b 1
1 x c 1
1 y a 1
2 x a 1
What I have so far is
select t.user_id, t.user_label, t.code_1, count(###)
from t
group by t.code_1,t.user_id, t.user_label
The problem is
1. I don't know what to put inside the count 2. I don't know how to incorporate the condition on date in to this query.
Any suggestion, correction would be greatly appreciated.
You seem to want count(distinct date):
select t.user_id, t.user_label, t.code_1,
count(distinct date)
from t
group by t.code_1,t.user_id, t.user_label

Insert columns into rows for each ID

I have two tables:
table 1:
ID name
--------- -----------
1 john
2 salma
3 tony
table2:
ID Tasks amount
--------- ----------- ----------
1 write 2
1 memorize 3
1 read 6
1 sing NULL
2 write 1
2 memorize NULL
2 read 5
2 sing NULL
3 write NULL
3 memorize 8
3 read 2
3 sing NULL
I want to insert new columns in table1 for each task mentioned in table2.
Table1:
ID name write memorize read sing
--------- ----------- -------- --------- ------- --------
1 john 2 3 6 NULL
2 salma 1 NULL 5 NULL
3 tony NULL 8 2 NULL
I can do the insert in Table1 for one ID at a time, but not for all of them. Thanks in advance!
First, I inserted the row values in a temporary table as columns using pivot:
select * into #Results
from
(
select ID,Tasks,amount
from #Table2
) tb2
pivot
(
max(amount)
for ID in ([1], [2], [3])
) as piv
Then, I did an inner join with Table1:
select * from Table1 tb1 inner join #Results r on r.ID =tb1.ID
Thanks #JamesL for the seggustion to use pivot!

Complicated pivot in MS SQL Server

I have the following data structure, where value1 and value2 - some aggregated values for each segment and date(I can get unaggregated data, if it helps)
date segment value1 value2
--- ------ ------- ------
What do I need is a report,which looks like this:
2015-01-01 2015-01-02
value1 value2 value1 value2
------ ------ ------ ------
segment1 19 5 18 7
segment2 20 5 21 7
for each date in a given period at the same time. How can I do that?
If I understand the question, you want the segment followed by a sum of the columns Value 1 and value 2, if So here is an easy group to do that:
select segment
, sum(Value1) as Value1
, sum(value2) as Value2
From YourTable
group by segment

How to insert data into a table based on conditions in oracle/sql

I have a table1
Date Sec_ID Version value col5 col6 col7
20131111 001 1 100
20131112 002 2 99
I have a stored procedure to insert new data into the table1
so if I insert new date rows:
20131111 001 2 111
20130101 003 1 88
20131111 004 1 90
table1 will be something like:
Date Sec_ID Version value col5 col6 col7
20131111 001 2 111
20131112 002 2 99
20130101 003 1 88
20131111 004 1 90
Requirement: Date and Sec_ID formed a primary key.
for data that have same date and same Sec_ID, update its version and other columns.
in this case, for:
20131111 001 1 100
when new data:
20131111 001 2 111
is inserted
it'll keep
20131111 001 2 111
only.
Thanks!
It looks like you want to MERGE. If your table was called t42 you could do something like this:
select * from t42;
DT SEC_ID VERSION VALUE
--------- ---------- ---------- ----------
11-NOV-13 1 1 100
12-NOV-13 2 2 99
merge into t42
using (
select date '2013-11-11' as dt, 1 as sec_id, 2 as version, 111 as value
from dual
union all select date '2013-01-01', 3, 1, 88 from dual
union all select date '2013-11-11', 4, 1, 90 from dual
) new_data
on (new_data.dt = t42.dt and new_data.sec_id = t42.sec_id)
when matched then
update set t42.version = new_data.version, t42.value = new_data.value
when not matched then
insert (t42.dt, t42.sec_id, t42.version, t42.value)
values (new_data.dt, new_data.sec_id, new_data.version, new_data.value);
3 rows merged.
select * from t42;
DT SEC_ID VERSION VALUE
--------- ---------- ---------- ----------
11-NOV-13 1 2 111
12-NOV-13 2 2 99
01-JAN-13 3 1 88
11-NOV-13 4 1 90
SQL Fiddle
The new_data is coming from fixed values here, but could come from another table, or as a single row if you're passing values into your stored procedure. The merge itself can be standalone SQL or embedded in a PL/SQL block.
If the new_data fields dt (since date is a reserved word, and a bad name for a column) and sec_id match an existing record, that record is updated with the new version and value. If there is no match a new record is inserted.

How to get previous row value

How to get a value from previous result row of a SELECT statement
If we have a table called cardevent and has rows [ID(int) , Value(Money) ] and we have some rows in it, for example
ID --Value
1------70
1------90
2------100
2------150
2------300
3------150
3------200
3-----250
3-----280
so on...
How to make one Query that get each row ID,Value and the previous Row Value in which data appear as follow
ID --- Value ---Prev_Value
1 ----- 70 ---------- 0
1 ----- 90 ---------- 70
2 ----- 100 -------- 90
2 ------150 -------- 100
2 ------300 -------- 150
3 ----- 150 -------- 300
3 ----- 200 -------- 150
3 ---- 250 -------- 200
3 ---- 280 -------- 250
so on.
So can anyone help me to get the best solution for such a problem ?
Need Query Help
SELECT t.*,
LAG(t.Value) OVER (ORDER BY t.ID)
FROM table AS t
This should work.
The Lag function gets the previous row value for a specific column. I think this is what you want here.
You can use LAG() and LEAD() Function to get previous and Next values.
SELECT
LAG(t.Value) OVER (ORDER BY t.ID) PreviousValue,
t.value Value,
LEAD(t.value) OVER (ORDER BY t.ID) NextValue
FROM table t
GO
You would have to join the table with itself, I'm not sure if this is 100% legitimate SQL, but I have no SQL-Server to try this at the moment, but try this:
SELECT (ID, Value) from table as table1
inner join table as table2
on table1.ID = (table2.ID -1)
select t1.value - t2.value from table t1, table t2
where t1.primaryKey = t2.primaryKey - 1
Try this.