SQL Server - Insert with multiple sub queries (3 tables) - sql

I have a table called BankHoliday which contains a Date field along with the Id field.. This currently has all of the dates for this year's and next year's Bank Holidays.
I also have a table called Staff which contains StaffID, StaffName, Active
Now using SQL I am trying to write an insert statement into a table called Calendar in which these Bank Holiday Dates are inserted. This table contains CalendarDate, CalendarID and StaffID,
The StaffID is where my problem is...
The Question
How can I write an INSERT statement in SQL Server where it will fetch a list of the Active Staff Members along with each individual Bank Holiday Date, and then INSERT them into the Calendar table?
Example Data would return:
CalendarID CalendarDate StaffID
1 31/08/2015 1
2 31/08/2015 2
3 31/08/2015 3
4 31/08/2015 4
5 31/08/2015 5
6 25/12/2015 1
7 25/12/2015 2
8 25/12/2015 3
9 25/12/2015 4
10 25/12/2015 5
So to clarify the CalendarDate above will contain a list of BankHoliday Dates that are not currently in this table (WHERE NOT EXISITS)
The StaffID is retrieved from the Staff table where a list contains only Active members

I think you are looking for a CROSS JOIN.
INSERT INTO Calendar
SELECT b.CalendarDate, s.StaffID FROM BankHoliday b
CROSS JOIN Staff s
WHERE s.Active = 1
This will get a row for every bank holiday and every active staff member.

Ther is an accepted answer already, so I'm to late - never mind. Just one hint: I think you could improve the design:
You should create a date-table not only for bank holidays, but a
running list of all dates. These dates you can mark as
"IsBankHoliday". Such a date list is very usefull in many cases.
I don't know what you are going to do with this, but normally you
would not write data into a physical table, which you can retrieve
that easily. So - probably! - it's not the best idea to do this...
Just my solution to show you the connection of the tables. In fact this is nothing else then the other solution with CROSS JOIN... Person4 is not active, therefore is missing:
declare #bankholidays TABLE(ID INT,TheDate DATE);
insert into #bankholidays values(1,{ts'2015-08-31 00:00:00'}),(2,{ts'2015-12-25 00:00:00'});
declare #staff TABLE(StaffID INT,StaffName VARCHAR(100could),Active BIT);
insert into #staff VALUES(1,'Person1',1),(2,'Person2',1),(3,'Person3',1),(4,'Person4',0),(5,'Person5',1);
declare #target TABLE(targetID INT,CalendarID INT,CalendarDate DATE,StaffID INT);
INSERT INTO #target
SELECT ROW_NUMBER() OVER(ORDER BY bh.TheDate,s.StaffID)
,bh.ID
,bh.TheDate
,s.StaffID
FROM #staff AS s,#bankholidays AS bh
WHERE s.Active=1;
SELECT * FROM #target

Related

How to compare records in the same table using SQL to find difference?

I want to compare two dispense transactions and find out if same drug was dispensed twice within 5 day window. How can I write SQL to compare these transactions and report?
patient_id
doctor_id
rx_number
pharmacy_id
drug_id
ship_date
1234
1234
rx_1234
ph_1234
1234
2022-06-28
1234
1234
rx_1234
ph_1234
1234
2022-07-01
EXISTS clause can be used for this purpose:
This one returns the row from the table if there is another row for the same drug within 5 days of ship_date:
select *
from MyTbl T1
where exists
(select *
from MyTbl T2
where T2.drug_id=T1.drug_id
and abs(datediff(day,T1.ship_date, T2.ship_date))<=5
)
The syntax for the 'number of days between two dates' is for SQL Server and it can change from one RDBMS product to other. You didn't specify what you use.
This query returns both rows (the earlier and the later shipment); you didn't specify the desired output.
I assumed ship_date=dispensed date.

Use table to do replacements

Total and complete SQL rookie here, using general SQL I need to replace department numbers in a temp table identifying the new dept numbers.
Here is the full context: We have year over year reports that show expenses by department. Some (not all) of these departments have been eliminated for the upcoming year and consolidated into different departments.
For example, I have departments 1-10. 1-5 are not changing, but 6 & 7 will be department 7. 8, 9, & 10 will be 8 in the new year. So I will have a table with columns 'old dept' and 'new dept' with values:
6,7
9,8
10,8
(I don't need 7,7 or 8,8)
I pull all department expenses into a temp table with 'Dept' being one of the columns. When I first query the data for 2018 & 2019, the departments will be what they historically were (9 & 10 will show 9 & 10). I need to cycle through each record and replace 9 with 8, and 10 with 8, etc etc etc.
I was told I could accomplish the same thing with a join, but I am not sure how this is done.
Sorry for the rambling mess; as you can see I still have not passed SQL 101.
Thanks in advance.
You can do this with a translations table, which you then join into every query that you need to report on:
create table translations
(old int,
new int);
insert into translations values
(1,1)
,(2,2)
,(3,3)
,(4,4)
,(5,5)
,(6,7)
,(7,7)
,(8,8)
,(9,8)
,(10,8);
create table example_data
(dept int, revenue int);
insert into example_data values
(1,1000)
,(2,2000)
,(3,3000)
,(4,4000)
,(5,5000)
,(6,6000)
,(7,7000)
,(8,8000)
,(9,9000)
,(10,10000);
select new, sum(revenue)
from translations
inner join example_data on dept=old
group by new;

Auto Increment for non primary key column in Oracle

I want to insert the records for a particular column that would be increment by 1 whenever the new row gets inserted into table based on the following condition
For current year the first row with value :1
For current year the column value should be increment by 1
meaning 1 for 1st record of that current year and next available number for the matching year
Year Value
2016 1
2016 2
2016 3
2017 1
2017 2
...
My approach will be somewhat like this:
INSERT INTO ABC(ANALYSIS_YEAR,ANALYSIS_NUMBER)
values (EXTRACT(YEAR FROM sysdate),
case when ANALYSIS_YEAR=EXTRACT(YEAR FROM sysdate) then AutoIcreamt with starting value 1 else 1;
)
Any solution that looks at current table values will not work in a 'real' environment with multiple users and multiple sessions and parallel transactions.
I think you need to separate out the two requirements:
Have the ability to sequence records based on when they were created
Have the ability to report on record sequencing within a year.
The first is handled using a sequence as these are designed exactly for this and handle concurrency (multiple users, multiple transactions, ...).
The second is a reporting requirement and has a number of options depending on performance requirements.
First of all create a sequence:
create sequence seq_analysis_id start with 1 increment by 1 nocache nocycle;
Not let's create a base table and a trigger to handle the auto-increment:
create table analysis_data (
analysis_id integer not null,
analysis_date date not null
);
alter table analysis_data add constraint pk_analysis_data primary key (analysis_id);
create or replace trigger trg_analysis_data
before insert on analysis_data
for each row
begin
:new.analysis_id := seq_analysis_id.nextval();
end trg_analysis_data;
/
insert into analysis_data (analysis_date) values (to_date('2015-12-28', 'YYYY-MM-DD'));
insert into analysis_data (analysis_date) values (to_date('2015-12-29', 'YYYY-MM-DD'));
insert into analysis_data (analysis_date) values (to_date('2015-12-30', 'YYYY-MM-DD'));
insert into analysis_data (analysis_date) values (to_date('2015-12-31', 'YYYY-MM-DD'));
insert into analysis_data (analysis_date) values (to_date('2016-01-01', 'YYYY-MM-DD'));
insert into analysis_data (analysis_date) values (to_date('2016-01-02', 'YYYY-MM-DD'));
insert into analysis_data (analysis_date) values (to_date('2016-01-03', 'YYYY-MM-DD'));
commit;
select * from analysis_data;
ANALYSIS_ID ANALYSIS_DATE
1 28/12/2015
2 29/12/2015
3 30/12/2015
4 31/12/2015
5 01/01/2016
6 02/01/2016
7 03/01/2016
Ok - so that all works fine but doesn't give you what you asked for :)
This is the second part - the reporting requirement:
The first option is just to get the numbers you need dynamically:
select
analysis_id,
analysis_date,
extract(year from analysis_date) analysis_year,
row_number()
over (partition by trunc(analysis_date, 'YYYY')
order by analysis_date, analysis_id) analysis_number
from
analysis_data;
Using Analytic Functions (row_number in this case) is a great way to handle this sort of thing.
ANALYSIS_ID ANALYSIS_DATE ANALYSIS_YEAR ANALYSIS_NUMBER
1 28/12/2015 2015 1
2 29/12/2015 2015 2
3 30/12/2015 2015 3
4 31/12/2015 2015 4
5 01/01/2016 2016 1
6 02/01/2016 2016 2
7 03/01/2016 2016 3
I've ordered by analysis_date, analysis_id in the row_number function. This probably isn't necessary but would be needed if you had to handle updates to the analysis_date (in which case the sequence no longer works for in-year ordering on its own).
You could make this a little more straightforward for reporting by wrapping it in a view:
create or replace view analysis_data_v as
select
analysis_id,
analysis_date,
extract(year from analysis_date) analysis_year,
row_number()
over (partition by trunc(analysis_date, 'YYYY')
order by analysis_date, analysis_id) analysis_number
from
analysis_data;
This may be all you need, but if you have large data sets then you may need to pre-calculate some of these values. You have virtual columns in 11g, but these don't work for analytic functions. My option here would be to use a materialized view - lots of ways to handle materialized view refreshes and the simplest would be:
create materialized view analysis_data_mv
build immediate
refresh complete on demand
as
select
analysis_id,
analysis_date,
analysis_year,
analysis_number
from
analysis_data_v;
select * from analysis_data_mv order by analysis_year, analysis_number;
ANALYSIS_ID ANALYSIS_DATE ANALYSIS_YEAR ANALYSIS_NUMBER
1 28/12/2015 2015 1
2 29/12/2015 2015 2
3 30/12/2015 2015 3
4 31/12/2015 2015 4
5 01/01/2016 2016 1
6 02/01/2016 2016 2
7 03/01/2016 2016 3
In this case the materialized view would be manually refreshed:
exec dbms_mview.refresh('analysis_data_mv');
Hope this helps.
You will probably need a trigger on insert that looks like this (I guess you can't change the value of Year, in that case it will get more complicated).
Like #ibre5041 This won't work on a multi-user enviroment since you could have duplicates if a couple of transactions are executed at the same time on the same year:
CREATE OR REPLACE TRIGGER trg_bi_table1
before insert
ON table_1
Begin
select nvl(max(value),0)+1 as value
into :new.value
from table_1
where Year = :new.Year;
end;
/
In a multi-user enviroment you should use select for update

SQL Query to show attendance between two dates

I've got a .Net application with an attendance table which has fields for a Start and End date. I'm struggling to show a graph of attendance for a given period. I can easily find how many rows are applicable on any given day using between but I can't get my head around pivoting results so that I can graph a count of rows per day. I could run a SQL query for every day individually and then graph the results but is there any way of doing this with T-SQL that I could then use to graph with?
Edit:-
Apologies as this is the first time I've asked a question here, but as huMpty duMpty has stated the question probably needs more clarification. I've got both a startdate and enddate column in the sql db and I need to count per day if the range between these dates falls between the range of the selection criteria. e.g if I've got a start date of 2013-01-01 and end date 2013-01-10 and I report on a period of 2013-01-09 to 2013-01-11 then i'm looking at getting a result for 1 for 2013-01-09 and 1 for 2013-01-10... Hope this make more sense and thanks for your assistance
I think you have a table with start and end dates; for a given date range, you would like to know the given number of records that fall on each date.
I believe this problem may be solved with a numbers table. I created a numbers table on the fly in a stored procedure, but I recommend creating a permanent numbers table in your production code. Here's the SQL Fiddle.
Create Table Attendance (
id int primary key identity(1,1) not null
,start_date date not null
,end_date date not null
);
Go
Insert Attendance(start_date, end_date)
Values ('1/1/2013', '1/10/2013')
,('1/10/2013', '1/15/2013')
,('2/20/2013', '3/1/2013');
Go
-- Create numbers table. See: Method 3 of http://stackoverflow.com/a/1407488/772086
With Numbers(Number) As
(
Select 1 As Number
Union All
Select Number + 1 From Numbers Where Number < 10000
)
Select
AttendanceDate = Convert(date, DateAdd(day,Numbers.number, '1/1/2000'))
,AttendanceCount = Count(*)
From dbo.Attendance
Join Numbers
On Numbers.Number
Between DateDiff(day, '1/1/2000', Attendance.start_date)
And DateDiff(day, '1/1/2000', Attendance.end_date)
-- Reporting range between 1/9 and 1/11
Where DateAdd(day,Numbers.number, '1/1/2000') Between '1/9/2013'
And '1/11/2013'
Group By Convert(date, DateAdd(day,Numbers.number, '1/1/2000'))
Option(MaxRecursion 10000);
All dates are in US format (m/d/yy) - you may want to switch those to the internationalized standard (yyyy-mm-dd) in your production code.
You said you wanted a count by day in a date range. That can be done with a COUNT with a GROUP BY clause.
I don't know your schema, but a solution might look like this:
declare #MyTable table
(
ID int identity(1,1) primary key clustered,
MyDate smalldatetime
)
insert into #MyTable (MyDate)
values
('2012-12-31'), -- before the date range, so not included in results
('2013-01-10'),
('2013-01-10'), -- appears twice
('2013-01-11'), -- appears once
('2013-01-12') -- after the date range, so not included in results
select * from #MyTable
select
MyDate,
count(*)
from #MyTable
where MyDate between '2013-01-09' and '2013-01-11'
group by MyDate

What is wrong with this SQL statement for inserting concatenated IDS from a table into a field?

INSERT INTO Activity_Feed (userID,Type,DataIDs,PodID)
VALUES ( 1437
, 'eventattend'
, (SELECT LEFT(EventID, LEN(eventID) - 1 as nvarchar)
FROM (
SELECT EventiD + ', '
FROM events
FOR XML PATH ('')) c (EventID))
, 5)
Basically I want to take a bunch of IDs from a table and insert them as a comma delimited string into a varchar field.
E.g.
Activity_Feed (table)
activityID 1
userID 2
DataIDs 1,3,4,56,367 // This would be the (Select Ids FROM bit)
I want to take a bunch of RSVP IDs from a table and stick their IDs in the field...
To further explain I wanted to avoid normalizing the query because of the nature of this query. Let me know if I should still separate out the data...
The activity feed works like this...
I create a new entry in activity with a type of event_attending (which is an event I am attending.
I timestamp it, I enter the ID for the event in the dataIDs field any new activity matching event_attending in a 6 hour period fires an update record rather than insert
I keep the timestamp the same but update the ID's that are associated with that time period so basically update the IDs with the latest event attendance within that time period.
I thought normalizing seemed like overkill :D is there such thing as overkill with normalization?
Always normalize your database.
This is totally not wrong but very poor in database design.
Reasons why this is very poor:
hard to join tables
hard to find values
Why not create table like this,
Activity
ActivityID
ActivityName
Feed
FeedID
FeedName
Activity_Feed
FeedID
ActivityID
so Activity_Feed table will contain something like this
FeedID ActivityID
=====================
1 1
1 2
1 3
2 1
2 3
3 2
3 1
and you can now join the tables,
SELECT a.ActivityName, c.FeedName
FROM Activity a
INNER JOIN Activity_Feed b
ON a.ActivityID = b.ActivityID
INNER JOIN Feed c
ON b.FeedID = c.FeedID
-- WHERE a.ActivityName = 1 -- or something like this