Add rows to a table then loop back and add more rows for a different userid - sql

I have a table with 4 columns - ID, ClubID, FitnessTestNameID and DisplayName
I have another table called Club and it has ID and Name
I want to add two rows of data to the 1st table for each club
I can write a statement like this, but can someone tell me how to create a loop so that I can insert the two rows, set the #clubid + 1 and then loop back again?
declare #clubid int
set #clubid = 1
insert FitnessTestsByClub (ClubID,FitnessTestNameID,DisplayName)
values (#clubid,'1','Height (cm)')
insert FitnessTestsByClub (ClubID,FitnessTestNameID,DisplayName)
values (#clubid,'2','Weight (kg)')

You can probably do this with one statement only. No need for loops:
INSERT INTO FitnessTestsByClub
(ClubID, FitnessTestNameID, DisplayName)
SELECT
c.ID, v.FitnessTestNameID, v.DisplayName
FROM
Club AS c
CROSS JOIN
( VALUES
(1, 'Height (cm)'),
(2, 'Weight (kg)')
) AS v (FitnessTestNameID, DisplayName)
WHERE
NOT EXISTS -- a condition so no duplicates
( SELECT * -- are inserted
FROM FitnessTestsByClub AS f -- and the statement can be run again
WHERE f.ClubID = c.ID -- in the future, when more clubs
) -- have been added.
;
The Table Value Constructor syntax above (the (VALUES ...) construction) is valid from version 2008 and later.
There is a nice article with lots of useful examples of how to use them, by Robert Sheldon: Table Value Constructors in SQL Server 2008

Related

Loop through each column name from one table and insert that name into another table?

I have two tables. One table has a list of 500 columns. Another table references each column name like this
Select Top 1 * from MyReferenceTable
Which returns the results
(69, 'FirtName', 1, NULL)
(69, 'LastName', 2, NULL)
Where 'FirstName' is the name of the column from an actual table.
So I want to fill this reference table with the column names from the other table as so
Insert Into MyReferenceTable
FileId, ColumnName1, ColumnOrder, DefaultValue
Values(69, Select ColumnName From OtherTable? ,
Select Next Sequential Identity?, NULL)
My issue is how can I loop through the other table get the column name for each row, also insert an identity in sequentialOrder as ColumnOrder?
Typing out that insert statement manually for over 500 columns would take many moons.
This is a terrible idea, but the answer to your question is straight-forward:
INSERT INTO MyReferenceTabel (FileId, ColumnName1, ColumnOrder, DefaultValue)
SELECT 69, [name], [column_id], NULL
FROM sys.columns
WHERE [object_id] = OBJECT_ID('MyOtherTable')
Basically you craft a SELECT statement that returns the values you want, and then just add the INSERT statement over it.
But again, this smells of a terrible design choice that will bite you in the end. But it's still good to know how to get this information, so I'm posting this example here.

SQL query with pivot tables?

I'm trying to wrap by brain around how to use pivot tables for a query I need. I have 3 database tables. Showing relevant columns:
TableA: Columns = pName
TableB: Columns = GroupName, GroupID
TableC: Columns = pName, GroupID
TableA contains a list of names (John, Joe, Jack, Jane)
TableB contains a list of groups with an ID#. (Soccer|1, Hockey|2, Basketball|3)
TableC contains a list of the names and the group they belong to (John|1, John|3, Joe|2, Jack|1, Jack|2, Jack|3, Jane|3)
I need to create a matrix like grid view using a SQL query that would return a list of all the names from TableA (Y-axis) and a list of all the possible groups (X-axis). The cell values would be either true or false if they belong to the group.
Any help would be appreciated. I couldn't quite find an existing answer that helped.
You might try it like this
Here I set up a MCVE, please try to create this in your next question yourself...
DECLARE #Name TABLE (pName VARCHAR(100));
INSERT INTO #Name VALUES('John'),('Joe'),('Jack'),('Jane');
DECLARE #Group TABLE(gName VARCHAR(100),gID INT);
INSERT INTO #Group VALUES ('Soccer',1),('Hockey',2),('Basketball',3);
DECLARE #map TABLE(pName VARCHAR(100),gID INT);
INSERT INTO #map VALUES
('John',1),('John',3)
,('Joe',2)
,('Jack',1),('Jack',2),('Jack',3)
,('Jane',3);
This quer will collect the values and perform PIVOT
SELECT p.*
FROM
(
SELECT n.pName
,g.gName
,'x' AS IsInGroup
FROM #map AS m
INNER JOIN #Name AS n ON m.pName=n.pName
INNER JOIN #Group AS g ON m.gID=g.gID
) AS x
PIVOT
(
MAX(IsInGroup) FOR gName IN(Soccer,Hockey,Basketball)
) as p
This is the result.
pName Soccer Hockey Basketball
Jack x x x
Jane NULL NULL x
Joe NULL x NULL
John x NULL x
Some hints:
You might use 1 and 0 instead of x as SQL Server does not know a real boolean
You should add a pID to your names. Never join tables on real data (unless it is something unique and unchangeable [which means never acutally!!!])
UPDATE dynamic SQL (thx to #djlauk)
If you want a query which deals with any amount of groups you have to to this dynamically. But please be aware, that you loose the chance to use this in ad-hoc-SQL like in VIEW or inline TVF, which is quite a big backdraw...
CREATE TABLE #Name(pName VARCHAR(100));
INSERT INTO #Name VALUES('John'),('Joe'),('Jack'),('Jane');
CREATE TABLE #Group(gName VARCHAR(100),gID INT);
INSERT INTO #Group VALUES ('Soccer',1),('Hockey',2),('Basketball',3);
CREATE TABLE #map(pName VARCHAR(100),gID INT);
INSERT INTO #map VALUES
('John',1),('John',3)
,('Joe',2)
,('Jack',1),('Jack',2),('Jack',3)
,('Jane',3);
DECLARE #ListOfGroups VARCHAR(MAX)=
(
STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(gName)
FROM #Group
FOR XML PATH('')
),1,1,''
)
);
DECLARE #sql VARCHAR(MAX)=
(
'SELECT p.*
FROM
(
SELECT n.pName
,g.gName
,''x'' AS IsInGroup
FROM #map AS m
INNER JOIN #Name AS n ON m.pName=n.pName
INNER JOIN #Group AS g ON m.gID=g.gID
) AS x
PIVOT
(
MAX(IsInGroup) FOR gName IN(' + #ListOfGroups + ')
) as p');
EXEC(#sql);
GO
DROP TABLE #map;
DROP TABLE #Group;
DROP TABLE #Name;
I suspect it may be laborious to keep the pivot up to date if categories are added. Or maybe I just prefer Excel (if you ignore one major advantage). The following approach could be helpful too, assuming you do have Office 365.
I added the three tables using 3 CREATE TABLE statements and 3 INSERT statements based on the code I saw above. (The solutions make use of temporary tables to insert specific values, but I believe you already have the data in your three tables, called TableA, TableB, TableC).
CREATE TABLE TestName (pName VARCHAR(100));
INSERT INTO TestName VALUES('John'),('Joe'),('Jack'),('Jane');
CREATE TABLE TestGroup (gName VARCHAR(100),gID INT);
INSERT INTO TestGroup VALUES ('Soccer',1),('Hockey',2),('Basketball',3);
CREATE TABLE Testmap (pName VARCHAR(100),gID INT);
INSERT INTO Testmap VALUES
('John',1),('John',3)
,('Joe',2)
,('Jack',1),('Jack',2),('Jack',3)
,('Jane',3);
Then, in MS Excel, I added (there may be a shorter sequence but I'm still exploring) the three tables as queries from database > sql server database. After adding them, I added all three to the Data Model (I can elaborate if you ask).
I then inserted PivotTable from the ribbon, chose External data source, but opened the Tables tab (instead of Connections tab), to find my data model (mine was top of the list) and I clicked Open. At some point Excel prompted me to create relationships between tables and it did a good job of auto generating them for me.
After minor tweaks my PivotTable came out like this (I could also ask Excel to show the data as a PivotChart).
Pivot showing groups as columns and names as rows.
The advantage is that you don't have to revisit the PIVOT code in SQL if the list (of groups) changes. As I think someone else mentioned, consider using ids for pName as well, or another way to ensure that you are not stuck the next day if you have two persons named John or Jack.
In Excel you can choose when to refresh the data (or the pivot) and, after refresh, any additional categories will be added and counted.

merge statement when not matched by source then insert to another table

I have created two tables customersrc and customertemp with the columns:
customertemp
ID name age addr cityid isactive
34 Gi 24 Chennai 1 1
customersrc
CustomerId CustomerName CustomerAge CustomerAddress
1 Gi 24 madurai
2 Pa 23 Tirupur
3 MI 27 Tirupur
Now I need to insert pa and mi data value to the temp table bcz it is not matched with the rows of customertemp. And the row gi data will be updated which was matched.
I used the following MERGE statement
DECLARE #cityid INT SET #cityid=1
MERGE Temp.dbo.customersrc as src_customer
USING ( SELECT CustomerName,CustomerAge,CustomerAddress FROM customertemp) as temp_customer
ON src_customer.name=temp_customer.CustomerName
AND
src_customer.cityid=#cityid
WHEN MATCHED THEN
UPDATE SET
src_customer.age=temp_customer.CustomerAge,
src_customer.addr=temp_customer.CustomerAddress,
src_customer.isactive=1
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET src_customer.isactive=0 ; -- here i need the insert statement to insert in another table
Questions:
is it possible to write insert statement inside the when not matched by source query?
if it is not possible then how to achieve this using merge?
in a simple set theory I need to put the customersrc(table_B)-customertemp (table_A). B-A value into the another or temp table.
One of the main usages of the MERGE statement is to perform so called "UPSERTS" (Update matching records, insert new records), so it is definitely possible to do what you want. Just add the following to the last part of your MERGE statement:
WHEN NOT MATCHED BY TARGET THEN
INSERT (name, age, addr, cityid, isactive)
VALUES (CustomerName, CustomerAge, CustomerAddress, #cityid, 1)
If you also need to insert data into a 3rd table, depending on whether rows are updated or inserted, you can use the OUTPUT clause of the merge statement. Check out the documentation: http://technet.microsoft.com/en-us/library/ms177564.aspx
Me: Why do you want to insert to another table?
You: To show the user who are not in the customertemp table.
So your requirement is not to insert into another table. Your requirement is to get the missing users.
You could do that with a dummy UPDATE (SET SomeCol = SomeCol) and OUTPUT. But that is a hack that I would try to avoid.
It is probably easier to do this in two statements. Here's how you'd get the missing rows:
SELECT temp_customer.*
FROM (SELECT CustomerName,CustomerAge,CustomerAddress FROM customertemp) as temp_customer
LEFT JOIN customersrc ON src_customer.name=temp_customer.CustomerName AND src_customer.cityid=#cityid
WHERE customersrc.cityid IS NULL

Inserting SCOPE_IDENTITY() into a junction table

Consider the following little script:
create table #test
(testId int identity
,testColumn varchar(50)
)
go
create table #testJunction
(testId int
,otherId int
)
insert into #test
select 'test data'
insert into #testJunction(testId,otherId)
select SCOPE_IDENTITY(),(select top 10 OtherId from OtherTable)
--The second query here signifies some business logic to resolve a many-to-many
--fails
This, however, will work:
insert into #test
select 'test data'
insert into #testJunction(otherId,testId)
select top 10 OtherId ,(select SCOPE_IDENTITY())
from OtherTable
--insert order of columns is switched in #testJunction
--SCOPE_IDENTITY() repeated for each OtherId
The second solution works and all is well. I know it doesn't matter, but for continuity's sake I like having the insert done in the order in which the columns are present in the database table. How can I acheieve that? The following attempt gives a subquery returned more than 1 value error
insert into #test
select 'test data'
insert into #testJunction(otherId,testId)
values ((select SCOPE_IDENTITY()),(select top 10 drugId from Drugs))
EDIT:
On a webpage a new row is entered into a table with a structure like
QuizId,StudentId,DateTaken
(QuizId is an identity column)
I have another table with Quiz Questions like
QuestionId,Question,CorrectAnswer
Any number of quizzes can have any number of questions, so in this example testJunction
resolves that many to many. Ergo, I need the SCOPE_IDENTITY repeated for however many questions are on the quiz.
The version that fails
insert into #testJunction(testId,otherId)
select SCOPE_IDENTITY(),(select top 10 OtherId from OtherTable)
will insert one row with scope_identity() in the first column and a set of 10 values in the second column. A column can not have sets so that one fails.
The one that works
insert into #testJunction(otherId,testId)
select top 10 OtherId ,(select SCOPE_IDENTITY())
from OtherTable
will insert 10 rows from OtherTable with OtherId in the first column and the scalar value of scope_identity() in the second column.
If you need to switch places of the columns it would look like this instead.
insert into #testJunction(testId,otherId)
select top 10 SCOPE_IDENTITY(), OtherId
from OtherTable
You need the output clause. Look it up in BOL.
Try this way:
Declare #Var int
insert into #test
select 'test data'
select #var=scope_identity()
insert into #testJunction(otherId,testId)
select top 10 #var,drugId from Drugs

Oracle SQL: merge with more conditions than matched/not matched

I'm needing some help with a MERGE command in Oracle. Basically I have this command but I want to optimize it a little more:
MERGE INTO swap USING dual ON (SELECT id FROM student WHERE number = '123')
WHEN MATCHED THEN
UPDATE SET swapped = 1, last_swap = sysdate
WHEN NOT MATCHED THEN
INSERT (student_id, swapped, last_swap) VALUES ((SELECT id FROM student WHERE number= '123'), 1, sysdate)
Right now this will insert or update a register on SWAP table. However, I would like to protect it from inserting NULL on the student_id if there's no student with that number on STUDENT table (I don't want to simply not allow NULL values on sudent_id of SWAP table).
Other thing, I'm repeating SELECT id FROM student WHERE number = '123' two times, how can I change this to execute it only once (store the result in an alias or something)?
Thanks a lot in advance!
Your merge statement should use the "Select from student" as the table you are merging from.
Can you post some sample data as well?
The below query is assuming that ID is the column on which you are merging and it cannot be null.
MERGE INTO swap
USING (SELECT id FROM student WHERE number = '123' and id is not null) stu
on (stu.id = swap.id)
WHEN MATCHED THEN
UPDATE SET swapped = 1, last_swap = sysdate
WHEN NOT MATCHED THEN
INSERT (student_id, swapped, last_swap) VALUES (stu.id , 1, sysdate);