SQL pivot not handling absence of data - sql-server

I have a table
Create table dbo.temp(ID int, ExpenseID int, ExpenseName Varchar(100) null, ExpenseDate date null, Value decimal(19,4) null)
Insert into dbo.temp values(1,100,'CostClothes','2015-01-01',100.0000)
Insert into dbo.temp values(1,200,'Discount','2015-01-01',1.0000)
Insert into dbo.temp values(2,100,'CostClothes','2016-01-01',250.0000)
Insert into dbo.temp values(2,200,'Discount','2016-01-01',1.0000)
Insert into dbo.temp values(1,100,'CostClothes','2014-01-01',500.0000)
Insert into dbo.temp values(2,200,'Discount','2014-01-01',5.0000)
Now I want to pivot this table on ExpenseID and the expected output is
Create table dbo.output(ID int, CostClothes decimal(19,4) null, Discount decimal(19,4) null, ExpenseDate date null)
insert into dbo.output values (1,100.0000,1.0000,'2015-01-01')
insert into dbo.output values (1,500.0000,NULL,'2014-01-01')
insert into dbo.output values (2,NULL,5.0000,'2014-01-01')
insert into dbo.output values (2,100.0000,1.0000,'2016-01-01')
This is what I have and i am not getting correct output
SELECT ID,ISNULL([100],0) as CostClothes,ISNULL([200],0) as Discount,expenseDate
FROM
(
SELECT * FROM dbo.temp
) AS p
PIVOT
(
MAX(Value) for ExpenseID in ([100],[200])
) AS PV1
How do I change the query
Thanks
MR

Given the sample data, you can either do a pivot with an aggregation, or just a simple conditional aggregation.
select
ID
,max(case when ExpenseName = 'CostClothes' then Value end) as CostClothes
,max(case when ExpenseName = 'Discount' then Value end) as Discount
,ExpenseDate
from
dbo.temp
group by
ID
,ExpenseDate
order by
ID
Pivot Method
select
ID
,CostClothes = max(CostClothes)
,Discount = max(Discount)
,ExpenseDate
from
dbo.temp
pivot(
max(Value) for ExpenseName in (CostClothes, Discount)
) p
group by
ID
,ExpenseDate
order by
ID
Demo: http://rextester.com/ENHTIK13664

Your problem is that you're selecting too much information in your derived table.
If you're going to use Select *, you don't even need a derived table. When using a PIVOT, you should select only the fields you need in your final result. The same way you would write the conditional aggregation query.
SELECT ID,
ISNULL([100], 0) AS CostClothes,
ISNULL([200], 0) AS Discount,
expenseDate
FROM
(
SELECT ID,
ExpenseID,
ExpenseDate,
Value
FROM temp
) AS p PIVOT(MAX(Value) FOR ExpenseID IN([100],[200])) AS PV1
This should give you the result you want by eliminating the ExpenseName column. Or you could use the ExpenseName column and eliminate the ExpenseID column

Related

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
)

When inserting a row, can the `ParentId` be set as the `Id` from a previous inserted row within the same query?

Suppose I have a table called #tblTemp like this:
DECLARE #tblTemp TABLE
(
Id INT NOT NULL IDENTITY,
Name VARCHAR(MAX) NOT NULL,
ParentId INT NULL
)
and my XML structure (assigned to #Xml) was:
<Data>
<MyRow Name="I am the Parent"/>
<MyRow Name="I am the child" ParentName="I am the Parent"/>
</Data>
Question: would it be possible to insert into the ParentId column within the same query?
SQL Script
INSERT INTO #tblTemp ([Name], [ParentId])
SELECT
Rw.value('#Name','VARCHAR(MAX)'), -- Name
(SELECT [Id] -- Select ID From Parent Name
FROM #tblTemp AS [TT]
WHERE [TT].[Name] = Rw.value('#ParentName', 'VARCHAR(MAX)'))
FROM
#Xml.nodes('Data/MyRow') AS Data(Rw)
SELECT *
FROM #tblTemp AS [TT]
The script inserts NULL into the ParentId column as I suspect the previous inserts haven't been committed yet so the table will be empty.
Alternative: if it isn't possible to insert into the ParentId column within the same query, then my alternative would be to do the insert then update the table where required.
Try it like this:
DECLARE #tblTemp TABLE
(
Id INT NOT NULL,
Name VARCHAR(MAX) NOT NULL,
ParentId INT NULL
)
DECLARE #xml XML=
N'<Data>
<MyRow Name="I am the Parent"/>
<MyRow Name="I am the child" ParentName="I am the Parent"/>
<MyRow Name="another child" ParentName="I am the Parent"/>
<MyRow Name="baby" ParentName="I am the child"/>
</Data>';
WITH DerivedTable AS
(
SELECT r.value(N'#Name',N'nvarchar(max)') AS [Name]
,r.value(N'#ParentName',N'nvarchar(max)') AS [ParentName]
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNmbr
FROM #xml.nodes(N'/Data/MyRow') AS A(r)
)
,recCTE AS
(
SELECT 1 AS Lvl
,[Name]
,[ParentName]
,RowNmbr
,CAST(NULL AS BIGINT) AS ParentRowNmbr
,CAST(N'' AS NVARCHAR(MAX)) AS [ParentPath]
FROM DerivedTable
WHERE ParentName IS NULL
UNION ALL
SELECT r.Lvl+1
,t.[Name]
,t.[ParentName]
,t.RowNmbr
,r.RowNmbr
,r.[ParentPath]+t.[ParentName]+N'|'
FROM DerivedTable AS t
INNER JOIN recCTE AS r ON r.[Name]=t.[ParentName]
)
--Use this SELECT to see all columns returned by the recursive CTE
--SELECT * FROM recCTE
INSERT INTO #tblTemp(ID,[Name],ParentId)
SELECT RowNmbr, [Name],ParentRowNmbr
FROM recCTE;
SELECT * FROM #tblTemp;
The result
Id Name ParentId
1 I am the Parent NULL
2 I am the child 1
3 another child 1
4 baby 2
Short explanation:
The first CTE will read the values as derived table and use ROW_NUMBER() to give a running number to each row as ID.
The second CTE is recursively travelling down the road.
The result can be inserted directly into your table.
Attention
I changed your table from ID is IDENTITY to a normal INT column. You can use SELECT MAX(ID) first to get the highest existing ID and add this to ROW_NUMBER() in the first CTE. Otherwise it might happen, that the IDs given by ROW_NUMBER() are not the same as the ID given by IDENTITY.

SQL:How to keep inserting record number in the target table in INSERT INTO statement

I have query like this:
declare #guidd nvarchar(10)
set #guidd = '11233'
create table rrr_temp(value nvarchar(10), value2 int)
create table rrr_tempA(valueA nvarchar(10), guidd nvarchar(10), ranks int)
insert into rrr_temp values('AAA', 200)
insert into rrr_temp values ('BBB', 400)
insert into rrr_temp values ('CCC', 300)
INSERT INTO rrr_tempA(valueA , guidd , ranks )
SELECT RT.value, #guidd , row_number() over (order by (select NULL))
FROM rrr_temp(nolock) RT
INNER JOIN
(SELECT value, min(value2) AS lastLeg
FROM rrr_temp(nolock) RTL
GROUP BY value) GrpRoute
ON RT.value = GrpRoute.value
ORDER BY value2
select * from rrr_tempA
With the above INSERT iNTO statement, i am able to insert only the record number of source table(rrr_temp) for 'ranks' column of Target table by using 'row_number() over (order by (select NULL))'. But, i want the number to be incremented when target table got inserted. i cannot use IDENTITY. Thanks.
Are you asking about something like this?
select #max_rank = max(ranks)
from rrr_tempA
set #max_rank = IsNull(#max_rank, 0)
INSERT INTO rrr_tempA(valueA , guidd , ranks )
SELECT RT.value, #guidd , #max_rank + row_number() over (order by (select NULL))

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.

sql server data insertion, first row closing balance should be next row opening balance

I am facing the problem while inserting data this way.
How to insert data this way using stored procedure.
INSERT INTO [dbo].tbl_Transaction
(
[FK_GameID] ,
[SpotID] ,
TransactionReason ,
TransactionType ,
TransactionAmount
--PrevAmountBalance,
-- CurrentBalance
)
SELECT tblTransaction.Row.value('#FK_GameID','BIGINT'),
tblTransaction.Row.value('#SpotID','SMALLINT'),
tblTransaction.Row.value('#TransactionReason','SMALLINT'),
tblTransaction.Row.value('#TransactionType','Varchar(50)'),
tblTransaction.Row.value('#TransactionAmount','MONEY')
--#OpeningBalance,
-- (#OpeningBalance-tblTransaction.Row.value('#TransactionAmount','MONEY'))
FROM #TransactionTable.nodes('/row') AS tblTransaction(Row)
create table #balances(
id int not null identity(1,1),
opening_balance int,
closing_balance int,
t_type varchar(50))
to start the table (if you don't have this you will have to deal with NULLs)
insert into #balances values (100 , 100 , 'start')
query:
insert into #balances values(
(select top 1 closing_balance from #balances order by id desc),
300, --new value
'credit')

Resources