SQL Server 2008: complex Insert - sql

I have a table called Employees:
BeginYear | EndYear | Name
1974 1983 Robert
For each record in Employees I need to insert each year into a new table called EmployeeYears
So:
For Each Record in Employees
For i as int = Begin Year to End year
INSERT i, Name into EmployeeYears
Any Way to do this in SQL...possibly with cursors?

The gist of it is using a WITH statement to create all the records and use them to insert into your final table.
;WITH q AS (
SELECT Year = BeginYear
, Name
FROM Employees
UNION ALL
SELECT q.Year + 1
, q.Name
FROM q
INNER JOIN Employees e ON e.Name = q.Name
AND e.EndYear > q.Year
)
INSERT INTO EmployeeYears
SELECT * FROM q
OPTION(MAXRECURSION 0)
Testdata
CREATE TABLE Employees (BeginYear INTEGER, EndYear INTEGER, Name VARCHAR(32))
CREATE TABLE EmployeeYears (Year INTEGER, Name VARCHAR(32))
INSERT INTO Employees
SELECT 1974, 1976, 'Robert'
UNION ALL SELECT 1972, 1975, 'Lieven'
Results
SELECT *
FROM EmployeeYears
ORDER BY Name, Year
1972 Lieven
1973 Lieven
1974 Lieven
1975 Lieven
1974 Robert
1975 Robert
1976 Robert

If you have a numbers table you can join on it to get the individual year records and avoid using a cursor. I just poulated the numbers table with number from 1965 to 968, but a realife numbers table (which also would not be a temp table as shown below for example purposes, but one that lives in your schema) would probably have several million records as it is useful for a lot of comparing.
create table #Numbers (Number int)
insert into #Numbers
select 1965
union
select 1966
union
select 1967
union
select 1968
create table #employees (name varchar (50), beginyear int, endyear int)
insert into #employees
select 'Dick', 1966, 1968
union all
select 'harry', 1965, 1967
union all
select 'tom', 1955, 1966
insert into EmployeeYears (Name, [Year])
select Name, n.number
from #Employees e
join #Numbers n on n.number between e.beginyear and e.endyear
order by name

Yes, you actually have to do a loop... I'd prefer not using CURSORS, but this case sorta makes sense... anyway, here's the code as just a straight loop to show you that you can do that kind of code in SQL:
DECLARE #Employee VARCHAR(100)
DECLARE #BeginYear INT, #EndYear INT, #i INT
SET #Employee = ''
WHILE (1=1)
BEGIN
SET #Employee = (SELECT TOP 1 Name FROM Employees ORDER BY Name WHERE Name > #Employee)
IF #Employee IS NULL BREAK
SELECT #BeginYear = BeginYear, #EndYear = EndYear FROM Employees WHERE Name = #Employee
SET #i = #BeginYear
WHILE (#i <= #EndYear)
BEGIN
INSERT INTO EmployeeYears (Year, Name) VALUES (#i, #Employee)
SET #i = #i + 1
END
END

You can use a recursive CTE:
;WITH CTE AS
(
SELECT BeginYear, EndYear, Name
FROM Employees
UNION ALL
SELECT BeginYear+1, EndYear, Name
FROM CTE
WHERE BeginYear < EndYear
)
INSERT INTO EmployeeYears (Year, Name)
SELECT BeginYear, Name
FROM CTE
ORDER BY Name, BeginYear
OPTION(MAXRECURSION 0)

You can use a recursive procedure. Llike the one bellow:
CREATE Procedure InsertYear
#Name ....
#BeginYear ...
#EndYear ...
AS
{
INSERT INTO EmployeeYears VALUES(#BeginYear, #Name);
SET #BeginYear = #BeginYear + 1
IF #BeginYear < #EndYear
BEGIN
InsertYear(#Name, #BeginYear, #EndYear)
END
RETURN
}

You could do this but it will fail if Begin or end exceeds 2047
INSERT INTO EmployeeYears (number, name)
SELECT v.number, e.name
FROM
Employees e
INNER JOIN master..spt_values v on
v.number between beginYear and endYear

Related

Insert a random string from a list into a table

I'm trying to insert a random department name into an SQL Server table. Currently I have the code below. I want any of the four department values listed (comp sci, biology, psychology,chemistry) to be inserted randomly when I populate my table with sample data. Any help would be appreciated
Declare #SID int
Set #SID = 1
/* Create temporary table to insert random department name
declare #myList table (Dept varchar(50))
insert into #myList values ('Computer Science'), ('Biology'), ('Psychology'), ('Chemistry')*/
While #SID <= 12000
Begin
Insert Into Student values ('Student', CAST(#SID as nvarchar(10)), 'Department' + CAST(#SID as nvarchar(10)), '50')
Print #SID
Set #SID = #SID + 1
End
Use a select (rather than values) to select from the list you have created and insert the top 1 ordered by newid().
--insert into dbo.Student (Name, id, Department, Position)
select top 1 'Student', CAST(#SID as nvarchar(10)), Dept, '50'
from #myList
order by newid();
Note: Its best practice to list all columns being inserted into.
Since you required 12,000 of random data, use #myList to CROSS JOIN itself. And use row_number() to generate the sequential number. The random part is handle by NEWID()
with stud as
(
select SID = row_number() over (order by newid()),
l1.Dept
from #myList l1
cross join #myList l2
cross join #myList l3
cross join #myList l4
cross join #myList l5
cross join #myList l6
cross join #myList l7
)
select *
from stud
where SID <= 12000
For TSQL, you can generate a random number, then use the first digit to determine which value to use, eg
declare #ran int = (select rand() * 10)
declare #subject varchar(100)=''
if #ran <= 3
set #subject = 'computer science'
else if #ran <= 6
set #subject = 'Biology'
else if #ran <= 8
set #subject = 'Psychology'
else
set #subject = 'Chemistry'
insert into table ...... etc etc

Updating a SQL Server table with random names

I am trying the following:
update [Employees] set Last_User =
(select top 1 name from
(select 'John' as name
union select 'Tim' as name
union select 'Jane' as name
union select 'Jack' as name
union select 'Steve' as name
union select 'Ann' as name
)
as names order by newid())
but keep getting the same name for all rows. how can I make it vary?
Note: names are entered in query and do not come from another table.
Thanks
You are trying to update the whole column instead of updating each row, hence the first value which is getting generated is updated to all the rows, you can do the intended task by using T-SQL
DECLARE #counter int = 1
WHILE #counter <= (SELECT COUNT(1) FROM [Employees]) --or any speific row set you want to modify
BEGIN
UPDATE a
set Last_User =
(SELECT top 1 name from
(SELECT 'John' as name
UNION SELECT 'Tim' AS name
UNION SELECT 'Jane' AS name
UNION SELECT 'Jack' AS name
UNION SELECT 'Steve' AS name
UNION SELECT 'Ann' AS name
)
AS names ORDER BY NEWID())
FROM (SELECT *,ROW_NUMBER() OVER (ORDER BY Last_User)rnum FROM [Employees])a
WHERE a.rnum = #counter
SET #counter = #counter + 1
END
You can try this approach:
declare #Employees table (last_user nvarchar(100))
insert #Employees values('a')
insert #Employees values('b')
insert #Employees values('c')
declare #temp table(name nvarchar(100))
insert into #temp values('John')
insert into #temp values('Tim')
insert into #temp values('Jane')
insert into #temp values('Jack')
insert into #temp values('Steve')
insert into #temp values('Ann')
update #Employees set Last_User =
(select top 1 name from
#temp as names order by newid())
select * from #Employees

comparing two colums in sqlserver and returing the remaining data

I have two tables. First one is student table where he can select two optional courses and other table is current semester's optional courses list.
When ever the student selects a course, row is inserted with basic details such as roll number, inserted time, selected course and status as "1". When ever a selected course is de-selected the status is set as "0" for that row.
Suppose the student has select course id 1 and 2.
Now using this query
select SselectedCourse AS [text()] FROM Sample.dbo.Tbl_student_details where var_rollnumber = '020803009' and status = 1 order by var_courseselectedtime desc FOR XML PATH('')
This will give me the result as "12" where 1 is physics and 2 is social.
the second table holds the value from 1-9
For e.g course id
1 = physics
2 = social
3 = chemistry
4 = geography
5 = computer
6 = Spoken Hindi
7 = Spoken English
8 = B.EEE
9 = B.ECE
now the current student has selected 1 and 2. So on first column, i get "12" and second column i need to get "3456789"(remaining courses).
How to write a query for this?
This is not in single query but is simple.
DECLARE #STUDENT AS TABLE(ID INT, COURSEID INT)
DECLARE #SEM AS TABLE (COURSEID INT, COURSE VARCHAR(100))
INSERT INTO #STUDENT VALUES(1, 1)
INSERT INTO #STUDENT VALUES(1, 2)
INSERT INTO #SEM VALUES(1, 'physics')
INSERT INTO #SEM VALUES(2, 'social')
INSERT INTO #SEM VALUES(3, 'chemistry')
INSERT INTO #SEM VALUES(4, 'geography')
INSERT INTO #SEM VALUES(5, 'computer')
INSERT INTO #SEM VALUES(6, 'Spoken Hindi')
INSERT INTO #SEM VALUES(7, 'Spoken English')
INSERT INTO #SEM VALUES(8, 'B.EEE')
INSERT INTO #SEM VALUES(9, 'B.ECE')
DECLARE #COURSEIDS_STUDENT VARCHAR(100), #COURSEIDS_SEM VARCHAR(100)
SELECT #COURSEIDS_STUDENT = COALESCE(#COURSEIDS_STUDENT, '') + CONVERT(VARCHAR(10), COURSEID) + ' ' FROM #STUDENT
SELECT #COURSEIDS_SEM = COALESCE(#COURSEIDS_SEM , '') + CONVERT(VARCHAR(10), COURSEID) + ' ' FROM #SEM WHERE COURSEID NOT IN (SELECT COURSEID FROM #STUDENT)
SELECT #COURSEIDS_STUDENT COURSEIDS_STUDENT, #COURSEIDS_SEM COURSEIDS_SEM
try this:
;WITH CTE as (select ROW_NUMBER() over (order by (select 0)) as rn,* from Sample.dbo.Tbl_student_details)
,CTE1 As(
select rn,SselectedCourse ,replace(stuff((select ''+courseid from course_details for xml path('')),1,1,''),SselectedCourse,'') as rem from CTE a
where rn = 1
union all
select c2.rn,c2.SselectedCourse,replace(rem,c2.SselectedCourse,'') as rem
from CTE1 c1 inner join CTE c2
on c2.rn=c1.rn+1
)
select STUFF((select ''+SselectedCourse from CTE1 for xml path('')),1,0,''),(select top 1 rem from CTE1 order by rn desc)

Need multiple copies of one resultset in sql without using loop

Following is the sample data. I need to make 3 copies of this data in t sql without using loop and return as one resultset. This is sample data not real.
42 South Yorkshire
43 Lancashire
44 Norfolk
Edit: I need multiple copies and I have no idea in advance that how many copies I need I have to decide this on the basis of dates. Date might be 1st jan to 3rd Jan OR 1st jan to 8th Jan.
Thanks.
Don't know about better but this is definatley more creative! you can use a CROSS JOIN.
EDIT: put some code in to generate a date range, you can change the date range, the rows in the #date are your multiplier.
declare #startdate datetime
, #enddate datetime
create table #data1 ([id] int , [name] nvarchar(100))
create table #dates ([date] datetime)
INSERT #data1 SELECT 42, 'South Yorkshire'
INSERT #data1 SELECT 43, 'Lancashire'
INSERT #data1 SELECT 44, 'Norfolk'
set #startdate = '1Jan2010'
set #enddate = '3Jan2010'
WHILE (#startdate <= #enddate)
BEGIN
INSERT #dates SELECT #startdate
set #startdate=#startdate+1
END
SELECT [id] , [name] from #data1 cross join #dates
drop table #data1
drop table #dates
You could always use a CTE to do the dirty work
Replace the WHERE Counter < 4 with the amount of duplicates you need.
CREATE TABLE City (ID INTEGER PRIMARY KEY, Name VARCHAR(32))
INSERT INTO City VALUES (42, 'South Yorkshire')
INSERT INTO City VALUES (43, 'Lancashire')
INSERT INTO City VALUES (44, 'Norfolk')
/*
The CTE duplicates every row from CTE for the amount
specified by Counter
*/
;WITH CityCTE (ID, Name, Counter) AS
(
SELECT c.ID, c.Name, 0 AS Counter
FROM City c
UNION ALL
SELECT c.ID, c.Name, Counter + 1
FROM City c
INNER JOIN CityCTE cte ON cte.ID = c.ID
WHERE Counter < 4
)
SELECT ID, Name
FROM CityCTE
ORDER BY 1, 2
DROP TABLE City
This may not be the most efficient way of doing it, but it should work.
(select ....)
union all
(select ....)
union all
(select ....)
Assume the table is named CountyPopulation:
SELECT * FROM CountyPopulation
UNION ALL
SELECT * FROM CountyPopulation
UNION ALL
SELECT * FROM CountyPopulation
Share and enjoy.
There is no need to use a cursor. The set-based approach would be to use a Calendar table. So first we make our calendar table which need only be done once and be somewhat permanent:
Create Table dbo.Calendar ( Date datetime not null Primary Key Clustered )
GO
; With Numbers As
(
Select ROW_NUMBER() OVER( ORDER BY S1.object_id ) As [Counter]
From sys.columns As s1
Cross Join sys.columns As s2
)
Insert dbo.Calendar([Date])
Select DateAdd(d, [Counter], '19000101')
From Numbers
Where [Counter] <= 100000
GO
I populated it with a 100K dates which goes into 2300. Obviously you can always expand it. Next we generate our test data:
Create Table dbo.Data(Id int not null, [Name] nvarchar(20) not null)
GO
Insert dbo.Data(Id, [Name]) Values(42,'South Yorkshire')
Insert dbo.Data(Id, [Name]) Values(43, 'Lancashire')
Insert dbo.Data(Id, [Name]) Values(44, 'Norfolk')
GO
Now the problem becomes trivial:
Declare #Start datetime
Declare #End datetime
Set #Start = '2010-01-01'
Set #End = '2010-01-03'
Select Dates.[Date], Id, [Name]
From dbo.Data
Cross Join (
Select [Date]
From dbo.Calendar
Where [Date] >= #Start
And [Date] <= #End
) As Dates
By far the best solution is CROSS JOIN. Most natural.
See my answer here: How to retrieve rows multiple times in SQL Server?
If you have a Numbers table lying around, it's even easier. You can DATEDIFF the dates to give you the filter on the Numbers table

SQL Recursive Coalesce

I'm trying to create a column that contains all cities of the referenced addresses.
DECLARE #AddressList nvarchar(max)
SELECT #AddressList = COALESCE(#AddressList + ' ', '') + City FROM [Address]
SELECT
Employee.*,
(SELECT #AddressList) AS AddressCities
FROM Employee
But I dont know where to put the WHERE clause.
...
(SELECT #AddressList WHERE EmployeeId = Employee.EmployeeId) AS AddressCities
...
The above test doesnt work..
Table schemas are:
Employee
EmployeeId
Name
Address
Street
City
EmployeeId
If i understand you correctly, you wish to show all Cities in a single column for the employee. So you wish to GROUP BY and CONCAT.
Using Sql Server 2005, try this (working example)
DECLARE #Employee TABLE(
EmployeeId INT,
NAME VARCHAR(100)
)
INSERT INTO #Employee (EmployeeId,[NAME]) SELECT 1, 'A'
INSERT INTO #Employee (EmployeeId,[NAME]) SELECT 2, 'B'
DECLARE #Address TABLE(
Street VARCHAR(50),
City VARCHAR(50),
EmployeeId INT
)
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'A','A', 1
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'B','B', 1
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'C','C', 1
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'D','D', 2
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'E','E', 2
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'F','F', 2
SELECT e.EmployeeId,
e.[NAME],
(
SELECT al.City + ','
FROM #Address al
WHERE al.EmployeeId = e.EmployeeId
FOR XML PATH('')
)
FROM #Employee e
GROUP BY e.EmployeeId,
e.[NAME]
Do need more information about what you mean by 'column that contains all cities'. How is what you want different to the following might help you phrase the question
SELECT e.EmployeeId,e.Name,a.City
FROM Employee e
INNER JOIN Address a ON a.EmployeeId = e.EmployeeId
GROUP BY e.EmployeeId,e.Name
-- update
I think I see what you mean, do you want like:
EmployeeID | Name | Address
1 | John | 'London','Paris','Rome'
2 | Jane | 'New York','Miami'
?