SQL Server pivot specified columns - sql-server

I'm struggling with a query output issue. Please see sample data :
CREATE TABLE #Subject (ID INT PRIMARY KEY IDENTITY, Name NVARCHAR(50))
CREATE TABLE #Student (ID INT PRIMARY KEY IDENTITY, Name NVARCHAR(50))
CREATE TABLE #Grade (ID INT PRIMARY KEY IDENTITY,
StudentID INT REFERENCES #Student(ID),
SubjectID INT REFERENCES #Subject(ID),
Grade NVARCHAR(50), GradeText NVARCHAR(50))
INSERT INTO #Subject ( Name ) VALUES
(N'Maths'),
(N'Physics'),
(N'English')
INSERT INTO #Student ( Name ) VALUES
(N'Joe'),
(N'Tom'),
(N'Sally'),
(N'Fred'),
(N'Kim')
INSERT INTO #Grade
( StudentID, SubjectID, Grade, GradeText ) VALUES
(1,1,'Current','A'),
(2,3,'Expected','C'),
(3,2,'Mid','F'),
(4,1,'Final','B'),
(5,2,'Pre','C'),
(2,3,'Start','A'),
(3,1,'Current','A'),
( 1,2,'Expected','B'),
( 4,1,'Final','D'),
( 5,3,'Mid','E')
SELECT * FROM #Student
SELECT * FROM #Subject
SELECT * FROM #Grade
For the grade output I want to set some of the important grade types in the grade column to be their OWN columns. i.e. Current, Final I would like to be created as their own columns with associated grades, but the others can just be listed as they're not as important. This is a very simple example, the data I'm working with is much more complicated.
Is their a way to specify important columns to be created as their own columns and other data to just be listed as per normal? Also, all the pivot examples I've seen are querying from one table. What happens when you're query has many joins?

Are you aiming at something like this:
;with x as (
select *
from (
select StudentID, SubjectID, Grade, GradeText, Grade as grade_1, GradeText as GradeText_1
from (
select *
from #Grade
) as x
) as source
pivot (
max(GradeText_1)
for Grade_1 in ([Current], [Final])
) as pvt
)
select sub.Name as Subject, st.Name as Student, Grade, GradeText, [Current], Final
from x
inner join #Subject sub on x.SubjectID = sub.ID
inner join #Student st on x.StudentID = st.ID

Related

How to combine two FOR XML AUTO into 1 XML?

We are using SQL Server 2012.
Table myTbl has a one to many relationship to table myAllocation
Table ABC_myTbl has a one to many relationship to table ABC_myAllocation
The below query combined 2 FOR XML AUTO into 1 XML, but the problem is ID, SystemSource, Manager are included in element TradeTicket instead of on their own, and accountManager, unitPrice are included in element allocationRow instead of on their own.
Thank you
SELECT '<?xml version="1.0"?>'+
(SELECT
( SELECT trTicket.[id],trTicket.[manager],'PFM' as SystemSource
,allocationRow.accountNumber,allocationRow.unitPrice
FROM myTbl AS trTicket
LEFT JOIN myAllocation AS allocationRow ON allocationRow.trade_ticket_id=trTicket.id
WHERE trTicket.ID = 8779631
ORDER BY trTicket.id,allocationRow.AccountNumber
FOR XML AUTO, type)
,
(
SELECT trTicket.[id],trTicket.[manager],'CRD' as SystemSource
,allocationRow.accountNumber,allocationRow.unitPrice
FROM ABC_myTbl AS trTicket
LEFT JOIN ABC_myAllocation AS allocationRow ON allocationRow.trade_ticket_id=trTicket.id
WHERE trTicket.ID = 8
ORDER BY trTicket.id,allocationRow.AccountNumber
FOR XML AUTO, type)
FOR XML PATH('trTickets'), ELEMENTS) AS XMLResult
This is the current result:
<?xml version="1.0"?>
<trTickets>
<trTicket id="8779631" SystemSource="PFM" manager="MCM">
<allocationRow accountNumber="292 " unit_Price="300"/>
</trTicket>
<trTicket id="8" SystemSource="CRD" manager="DOYLE">
<allocationRow unitPrice="100" accountNumber="F11 "/>
<allocationRow unitPrice="200" accountNumber="F22 "/>
</trTicket>
</trTickets>
This is the desired result that I am looking for:
<?xml version="1.0"?>
<trTickets>
<trTicket>
<id>8</id>
<manager>DOYLE</manager>
<SystemSource>CRD</SystemSource>
<allocationRow>
<accountNumber>F11</accountNumber>
<unitPrice>100</unitPrice>
</allocationRow>
<allocationRow>
<accountNumber>F22</accountNumber>
<unitPrice>200</unitPrice>
</allocationRow>
</trTicket>
<trTicket>
<id>8779631</id>
<manager>MCM</manager>
<SystemSource>PFM</SystemSource>
<allocationRow>
<accountNumber>292</accountNumber>
<unitPrice>300</unitPrice>
</allocationRow>
</trTicket>
</trTickets>
Data sample:
Table ABC_myTbl:
ID Manager
-----------
8 DOYLE
Table ABC_myAllocation:
accountNumber unitPrice
-------------------------
F11 100
F22 200
Table myTbl:
ID Manager
---------------
8779631 MCM
Table myAllocation:
accountNumber unitPrice
--------------------------
292 300
DDL for the tables and their data:
CREATE TABLE dbo.ABC_myTbl
(
ID INT NOT NULL,
MANAGER VARCHAR(10) NOT NULL
)
CREATE TABLE dbo.myTbl
(
ID INT NOT NULL,
MANAGER VARCHAR(10) NOT NULL
)
CREATE TABLE dbo.ABC_myAllocation
(
accountNumber VARCHAR(10) NOT NULL,
unitprice NUMERIC(10, 3) NOT NULL
)
CREATE TABLE dbo.myAllocation
(
accountNumber VARCHAR(10) NOT NULL,
unitprice NUMERIC(10, 3) NOT NULL
)
INSERT INTO dbo.ABC_myTbl VALUES (8,'DOYLE')
INSERT INTO dbo.ABC_myAllocation VALUES ('F11',100)
INSERT INTO dbo.ABC_myAllocation VALUES ('F22',200)
INSERT INTO dbo.myTbl VALUES (8779631,'MCM')
INSERT INTO dbo.myAllocation VALUES ('292',300)
I didn't wait for your DDL and sample data population. So I created a conceptual sample for you. Please pay attention that the tables have implied relationships and they are used in the WHERE clauses.
SQL
-- DDL and sample data population, start
DECLARE #tbl1 TABLE (ID INT PRIMARY KEY, Manager VARCHAR(20));
INSERT INTO #tbl1 (ID, Manager) VALUES
(8, 'DOYLE'),
(9, 'XYZ');
DECLARE #tbl1Child TABLE (accountNumber CHAR(3) PRIMARY KEY, ParentID INT, unitPrice DECIMAL(10,2));
INSERT INTO #tbl1Child (accountNumber, ParentID, unitPrice) VALUES
('F11', 8, 100)
,('F22', 8, 200)
,('F70', 9, 770);
DECLARE #tbl2 TABLE (ID INT PRIMARY KEY, Manager VARCHAR(20));
INSERT INTO #tbl2 (ID, Manager) VALUES
(8779631, 'MCM')
,(8779555, 'TTT');
DECLARE #tbl2Child TABLE (accountNumber CHAR(3) PRIMARY KEY, ParentID INT, unitPrice DECIMAL(10,2));
INSERT INTO #tbl2Child (accountNumber, ParentID, unitPrice) VALUES
('292', 8779631, 300)
,('255', 8779555, 500);
-- DDL and sample data population, end
SELECT TOP(1) NULL
, (
SELECT *
, (
SELECT * FROM #tbl1Child AS c
WHERE p.ID = c.ParentID
FOR XML PATH('allocation_row'), TYPE
)
FROM #tbl1 AS p
FOR XML PATH('tradeTicket'), TYPE
)
, (
SELECT *
, (
SELECT * FROM #tbl2Child AS c
WHERE p.ID = c.ParentID
FOR XML PATH('allocation_row'), TYPE
)
FROM #tbl2 AS p
FOR XML PATH('tradeTicket'), TYPE
)
FROM #tbl1
FOR XML PATH(''), TYPE, ROOT('tradeTickets');
Output
<tradeTickets>
<tradeTicket>
<ID>8</ID>
<Manager>DOYLE</Manager>
<allocation_row>
<accountNumber>F11</accountNumber>
<ParentID>8</ParentID>
<unitPrice>100.00</unitPrice>
</allocation_row>
<allocation_row>
<accountNumber>F22</accountNumber>
<ParentID>8</ParentID>
<unitPrice>200.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>9</ID>
<Manager>XYZ</Manager>
<allocation_row>
<accountNumber>F70</accountNumber>
<ParentID>9</ParentID>
<unitPrice>770.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>8779555</ID>
<Manager>TTT</Manager>
<allocation_row>
<accountNumber>255</accountNumber>
<ParentID>8779555</ParentID>
<unitPrice>500.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>8779631</ID>
<Manager>MCM</Manager>
<allocation_row>
<accountNumber>292</accountNumber>
<ParentID>8779631</ParentID>
<unitPrice>300.00</unitPrice>
</allocation_row>
</tradeTicket>
</tradeTickets>

How to get records which has more than one entries on another table

An example scenario for my question would be:
How to get all persons who has multiple address types?
Now here's my sample data:
CREATE TABLE #tmp_1 (
ID uniqueidentifier PRIMARY KEY
, FirstName nvarchar(max)
, LastName nvarchar(max)
)
CREATE TABLE #tmp_2 (
SeedID uniqueidentifier PRIMARY KEY
, SomeIrrelevantCol nvarchar(max)
)
CREATE TABLE #tmp_3 (
KeyID uniqueidentifier PRIMARY KEY
, ID uniqueidentifier REFERENCES #tmp_1(ID)
, SeedID uniqueidentifier REFERENCES #tmp_2(SeedID)
, SomeIrrelevantCol nvarchar(max)
)
INSERT INTO #tmp_1
VALUES
('08781F73-A06B-4316-B6A5-802ED58E54BE', 'AAAAAAA', 'aaaaaaa'),
('4EC71FCE-997C-46AA-B119-6C5A2545DDC2', 'BBBBBBB', 'bbbbbbb'),
('B0726ABF-738E-48BC-95CB-091C9D731A0E', 'CCCCCCC', 'ccccccc'),
('6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', 'DDDDDDD', 'ddddddd')
INSERT INTO #tmp_2
VALUES
('4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'Value1'),
('4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'Value2'),
('6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'Value3')
INSERT INTO #tmp_3
VALUES
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdgdfbgcv'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'asdfadsas'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'xxxxxeeeeee'),
(NEWID(), '4EC71FCE-997C-46AA-B119-6C5A2545DDC2', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdfsd'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'zxczxcz'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'eerwerwe'),
(NEWID(), '6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'vbcvbcvbcv')
Which gives you:
This is my attempt:
SELECT
t1.*
, Cnt -- not really needed. Just added for visual purposes
FROM #tmp_1 t1
LEFT JOIN (
SELECT
xt.ID
, COUNT(1) Cnt
FROM (
SELECT
#tmp_3.ID
, COUNT(1) as Cnt
FROM #tmp_3
GROUP BY ID, SeedID
) xt
GROUP BY ID
) t2
ON t1.ID = t2.ID
WHERE t2.Cnt > 1
Which gives:
ID FirstName LastName Cnt
B0726ABF-738E-48BC-95CB-091C9D731A0E CCCCCCC ccccccc 2
08781F73-A06B-4316-B6A5-802ED58E54BE AAAAAAA aaaaaaa 2
Although this gives me the correct results, I'm afraid that this query is not the right way to do this performance-wise because of the inner queries. Any input is very much appreciated.
NOTE:
A person can have multiple address of the same address types.
"Person-Address" is not the exact use-case. This is just an example.
The Cnt column is not really needed in the result set.
The way you have named your sample tables and data help little in understanding the problem.
I think you want all IDs which have 2 or more SomeIrrelevantCol values in the last table?
This can be done by:
select * from #tmp_1
where ID in
(
select ID
from #tmp_3
group by ID
having count(distinct SomeIrrelevantCol)>=2
)

SQL Server: Insert Into 2 Tables in one query

I have seen a few questions similar to this but none gave me the answer I was looking for.
So here is the example
[Table A]:
ID pk/auto-increment
Name
Age
...
[Table B]:
ID pk/auto-increment
FK_A_ID fk
Comment
I have an import of data that contains over 700 rows (and growing)
[Table Import]
Name / Age / ... / Comment
Is it possible to use a query similar to:
INSERT INTO [TABLE A] (Name, Age, ...), [Table B] (FK_A_ID, Comments)
SELECT
Name, Age, ..., ##IDENTITY, Comment
FROM
[TABLE Import]
Or a shorter question, is it possible to insert into 2 tables in the same query referencing the first insert? - when I right it out like that it seems unlikely.
Thanks
You can't. But you can use transaction, like this:
START TRANSACTION;
INSERT INTO tableA
SELECT Name, Age, ... FROM tableImport;
INSERT INTO tableB
SELECT A.ID, I.Comment
FROM tableA A INNER JOIN tableImport I
ON A.Name = I.Name AND A.Age = I.Age AND ...;-- (if columns not unique)
COMMIT;
I think you can do it with some temporary tables, and the row_number feature, then perform separate inserts in to TABLE A and TABLE B from the temporary table
UNTESTED
create table source
(
Name varchar(50),
age int,
comment varchar(100)
)
go
insert into source
(name, age, comment)
values
('adam',12,'something'),
('steve',12,'everything'),
('paul',12,'nothing'),
('john',12,'maybe')
create table a
(
id int identity(1,1) not null,
name varchar(50),
age int,
rowid int
)
go
create table b
(
id int identity(1,1) not null,
comment varchar(50),
fkid int not null
)
go
declare #tempa table
(
RowID int,
Name varchar(50),
age int,
comment varchar(100)
)
go
insert into #tempa
(rowid, name, age, comment)
SELECT ROW_NUMBER() OVER(ORDER BY name DESC) AS RowId,
name, age, comment
FROM source
go
insert into a
(name, age, rowid)
select name, age, rowid
from #tempa
insert into b
(comment, fkid)
select t.comment,
a.id as fkid
from #tempa t inner join a a
on t.rowid = a.rowid
In my honest opinion, the best way to do this is create a stored procedure and rollback in case of failure. If you do so, you don't need a transaction because until you supply the "COMMIT" command nothing will be inserted.

Inserting Multiple values of int type into one column

Hey so I am new to sql and am building an inventory management system. So for the database i am getting stuck at this point where I need to insert the various user id's to different Teams in the company, hence my problem arises when I am trying to assign multiple int values to a particular team.The DB is made in a manner where it requires a TeamId and the corresponding UserId's to go with it.
Possible this be helpful for you -
DECLARE #temp TABLE (ID INT)
-- For 2008 and higher
INSERT INTO #temp (ID)
VALUES (1), (2), (3)
-- For 2005 and higher
INSERT INTO #temp (ID)
SELECT ID
FROM (
SELECT ID = 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
) t
SELECT *
FROM #temp
Update (comment #Sivakumar: "1,19 is not an integer. It is a varchar."):
DECLARE #temp TABLE (txt varchar(500))
INSERT INTO #temp (txt)
VALUES ('1,19'), ('2,18')
SELECT t.c.value('.', 'INT')
FROM (
SELECT txml = CAST('<t>' + REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
FROM #temp
) a
CROSS APPLY txml.nodes('/t') AS t(c)
It sounds like a one-to-many relationship: a Team can have zero or more Users.
So create a foreign key column in User that references the Team primary key.
Something like this (check my syntax):
create table team (
id int not null auto_increment,
primary key(id);
);
create table user (
id int not null auto_increment,
tid int,
primary key(id),
foreign key tid references(team);
);
select *
from team t
join user u
on t.id = u.tid;

Insert from single table into multiple tables, invalid column name error

I am trying to do the following but getting an "Invalid Column Name {column}" error. Can someone please help me see the error of my ways? We recently split a transaction table into 2 tables, one containing the often updated report column names and the other containing the unchanging transactions. This leave me trying to change what was a simple insert into 1 table to a complex insert into 2 tables with unique columns. I attempted to do that like so:
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.Date, INSERTED.Amount, INSERTED.Id INTO dbo.Transactions
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
The "INSERTED.Date, INSERTED.Amount" are the source of the errors, with or without the "INSERTED." in front.
-----------------UPDATE------------------
Aaron was correct and it was impossible to manage with an insert but I was able to vastly improve the functionality of the insert and add some other business rules with the Merge functionality. My final solution resembles the following:
DECLARE #TransactionsTemp TABLE
(
[Date] DATE NOT NULL,
Amount MONEY NOT NULL,
ReportColumnsId INT NOT NULL
)
MERGE INTO dbo.ReportColumns AS Trgt
USING ( SELECT
{FK}
,[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}) AS Src
ON Src.{FK} = Trgt.{FK}
WHEN MATCHED THEN
UPDATE SET
Trgt.FullName = Src.FullName,
Trgt.Type= Src.Type,
Trgt.Classification = Src.Classification
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
FullName,
Type,
Classification
)
VALUES
(
Src.FullName,
Src.Type,
Src.Classification
)
OUTPUT Src.[Date], Src.Amount, INSERTED.Id INTO #TransactionsTemp;
MERGE INTO dbo.FinancialReport AS Trgt
USING (SELECT
[Date] ,
Amount ,
ReportColumnsId
FROM #TransactionsTemp) AS Src
ON Src.[Date] = Trgt.[Date] AND Src.ReportColumnsId = Trgt.ReportColumnsId
WHEN NOT MATCHED BY TARGET And Src.Amount <> 0 THEN
INSERT
(
[Date],
Amount,
ReportColumnsId
)
VALUES
(
Src.[Date],
Src.Amount,
Src.ReportColumnsId
)
WHEN MATCHED And Src.Amount <> 0 THEN
UPDATE SET Trgt.Amount = Src.Amount
WHEN MATCHED And Src.Amount = 0 THEN
DELETE;
Hope that helps someone else in the future. :)
Output clause will return values you are inserting into a table, you need multiple inserts, you can try something like following
declare #staging table (datecolumn date, amount decimal(18,2),
fullname varchar(50), type varchar(10),
Classification varchar(255));
INSERT INTO #staging
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
Declare #temp table (id int, fullname varchar(50), type varchar(10));
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.id, INSERTED.fullname, INSERTED.type INTO #temp
SELECT
FullName
,Type
,Classification
FROM #stage
INSERT into dbo.transacrions (id, date, amount)
select t.id, s.datecolumn, s.amount from #temp t
inner join #stage s on t.fullname = s.fullname and t.type = s.type
I am fairly certain you will need to have two inserts (or create a view and use an instead of insert trigger). You can only use the OUTPUT clause to send variables or actual inserted values ti another table. You can't use it to split up a select into two destination tables during an insert.
If you provide more information (like how the table has been split up and how the rows are related) we can probably provide a more specific answer.

Resources