I have 4 tables in Microsoft SQL Server 2019 Express:
Table Invoices
InvoiceID InvoiceType InvoiceDate InvoiceSeries InvoiceApology
-----------|-----------|-----------|-------------|--------------
1 InvType1 1/1/2020 InvSeries1 InvApology1
2 InvType2 1/2/2020 InvSeries2 InvApology2
Table InvoiceDescriptions
InvoiceDescriptionID InvoiceID AccountingCodeID BookTitleID Amount VatAmount TotalAmount
--------------------|---------|----------------|-----------|-------|----------|-------------
1 1 3 1 100,00 4,00 104,00
2 2 1 2 50,00 2,00 52,00
Table AccountingCodes
AccountingCodeID AccountingCode AccountingCodeDescription
-----------------|--------------|-------------------------
1 64.01.0000 AccDescription1
2 64.02.0000 AccDescription2
3 64.03.0000 AccDescription3
Table BookTitles
BookTitleID BookTitle
------------|------------
1 BookTitle1
2 BookTitle2
What i am trying to achieve without success is to make a query to show me a table like the one below
Result Table(not create the actual table, only to show it with the query and the BookTitles are going to be more day by day and need to be updated dynamically)
InvoiceID InvoiceDate InvoiceNumber InvoiceApology TotalAmount BookTitle1 BookTitle2
----------|------------|------------ |--------------|------------|----------|----------
1 1/1/2020 1 InvApology1 104,00 100,00
2 1/2/2020 2 InvApology2 52,00 50,00
Finally I found the solution after many tries with this block of code
IF OBJECT_ID('tempdb..##TempAccountingBook') IS NOT NULL
DROP TABLE ##TempAccountingBook
IF OBJECT_ID('tempdb..##TempAccountingBookReport') IS NOT NULL
DROP TABLE ##TempAccountingBookReport
SELECT
InvoiceDescriptions.InvoiceID, Invoices.InvcoiceDate, InvoiceSeries,
InvoiceNumber, InvoiceApology, TotalAmount, Amount, BookTitle, VatValue
INTO
##TempAccountingBook
FROM
Invoices
JOIN
InvoiceDescriptions ON InvoiceDescriptions.InvoiceID = Invoices.InvoiceID
JOIN
BookTitles ON InvoiceDescriptions.BookTitleID = BookTitles.BookTitleID
DECLARE #SQLQUERY AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
SELECT #PivotColumns = COALESCE(#PivotColumns + ',','') + QUOTENAME(BookTitle)
FROM [dbo].[BookTitles]
SET #SQLQUERY = N'SELECT [InvoiceID], [InvcoiceDate], [InvoiceSeries], [InvoiceNumber], [InvoiceApology], [TotalAmount],' + #PivotColumns +' , [VatValue]
INTO ##TempAccountingBookReport
FROM ##TempAccountingBook
PIVOT (MAX([Amount])
FOR [BookTitle] IN (' + #PivotColumns +')) AS Q'
EXEC sp_executesql #SQLQUERY
SELECT * FROM ##TempAccountingBookReport
Related
Please help me with this. I have the data as this:
ID Name TotalCost IsCorporate
---- ---------------- ---------- -----------
1 Wash, Dry & Fold 175.00 1
2 Hand Wash and Fold 275.00 0
3 Pressing Only 25.00 0
4 Hand Wash and Fold 205.00 1
5 Pressing Only 100.00 0
If IsCorporate = 0 then the Total Cost will align to the Corporate column like this:
ID Wash, Dry & Fold Hand Wash and Fold Pressing Only Corporate
---- ---------------- ----------------- -------------- -----------
1 175.00
2 275.00
3 25.00
4 205.00
5 100.00
This is my stored procedure code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ReportSales]
AS
BEGIN
SELECT
IDJO, ISCORP, ST.[Wash, Dry & Fold], ST.[Pressing Only],
ST.[Dry Clean], ST.[Hand Wash and Fold], ST.[Wash, Dry & Press],
ST.[Stain Removal], ST.[Hand Wash and Press], CORPORATE
FROM
(SELECT
JO.Id AS IDJO, CI.Active AS ISCORP, ST.Name,
ISNULL(JO.TotalCost, 0) AS TC
FROM
JobOrders JO
INNER JOIN
ClientInformations CI ON JO.ClientId = CI.Id
INNER JOIN
JobOrderDetails JOD ON JO.Id = JOD.JOrderId
INNER JOIN
ServiceTypes ST ON JOD.ServiceId = ST.Id
INNER JOIN
Payments P ON JO.Id = P.JobOrderId
INNER JOIN
PaymentStatus PS ON JO.PaymentStatusId = PS.Id
INNER JOIN
Status S ON JO.StatusId = s.Id) AS J
PIVOT
(SUM(TC) for Name IN ([Wash, Dry & Fold], [Pressing Only], [Dry
Clean], [Hand Wash and Fold], [Wash, Dry & Press], [Stain Removal],
[Hand Wash and Press], [Corporate]) ) AS ST
END
Use dynamic column collection to select PIVOT Data, because it gives you any new column value added in table, suppose after 2-3 days if new Name say for XYZ added in your table even that it show your new column in PIVOT result:
CREATE TABLE ReportSales
(
ID INT,
Name VARCHAR(50),
TotalCost DECIMAL(10,2),
IsCorporate BIT
)
INSERT INTO ReportSales VALUES(1,'Wash, Dry & Fold',175.00,1)
,(2,'Hand Wash and Fold',275.00,0)
,(3,'Pressing Only',25.00,0)
,(4,'Hand Wash and Fold',205.00,1)
,(5,'Pressing Only',100.00,0)
DECLARE #Name AS NVARCHAR(MAX),#Query AS NVARCHAR(MAX);
SET #Name = STUFF((SELECT distinct ',' + QUOTENAME(Name)
FROM ReportSales c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #Query =
'IF OBJECT_ID(''tempdb..#tblRS'') IS NOT NULL
DROP TABLE #tblRS
SELECT
ID,
RS.Name,
TotalCost,
ISCorporate,
CASE RS.IsCorporate WHEN 1 THEN 0 ELSE RS.TotalCost END AS Corporate
INTO #tblRS
FROM ReportSales RS
SELECT ID, ' + #Name + ',Corporate from
(
SELECT
*,
CASE ISCorporate WHEN 1 THEN TotalCost ELSE 0 END AS NewTotalCost
FROM #tblRS
) x
pivot
(
SUM(TotalCost)
FOR Name in (' + #Name + ')
) p '
PRINT(#query)
EXECUTE(#query)
#Name : It will give you your column list on which you wants to apply SUM
I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2
Is it possible in SQL Server to "group" a result from a single query based on data in a specific column as if I ran multiple select queries?
I'm trying to find a lazy way out to extract data such as the below:
StoreId | ClientId
1 | 4
1 | 5
2 | 5
2 | 6
2 | 7
3 | 8
whereby every store ID result is grouped into its own table.
Whilst I can create a select statement for every store id to have it grouped, the list is too long to do so.
I can't imagine that this is really helpful but you can use dynamic sql to do something like this. I can't say I would recommend this approach for generating excel documents but whatever.
create table #Something
(
StoreID int
, ClientID int
)
insert #Something
select 1, 4 union all
select 1, 5 union all
select 2, 5 union all
select 2, 6 union all
select 2, 7
declare #sql nvarchar(max) = ''
select #sql = #sql + 'select StoreID, ClientID from #Something where StoreID = ' + CAST(StoreID as varchar(4)) + ';'
from #Something
group by StoreID
select #sql
exec sp_executesql #sql
drop table #Something
I have a PIVOT situation.
Source table columns:
Title Description Datetime RecordsCount
A California 2015-07-08 10:44:39.040 5
A California 2015-07-08 12:44:39.040 6
A California 2015-05-08 15:44:39.040 3
B Florida 2015-07-08 16:44:39.040 2
B Florida 2015-05-08 19:44:39.040 4
Now I need this pivoted as
2015-07-08 2015-05-08
Title Description
A California 11 3
B Florida 2 4
if we have two record counts on same dates (no matter of time) then sum them, else display in different column.
Trying to write something like this, but it throws errors.
Select * from #DataQualTest
PIVOT (SUM(RecordCount) FOR DateTime IN (Select Datetime from #DataQualTest) )
AS Pivot_Table
Please help me out with this.
Thanks
Not exactly the word for word solution but this should give you a direction.
create table #tmp
(
country varchar(max)
, date1 datetime
, record int
)
insert into #tmp values ('California', '2010-01-01', 2)
insert into #tmp values ('California', '2010-01-01', 5)
insert into #tmp values ('California', '2012-01-01', 1)
insert into #tmp values ('Florida', '2010-01-01', 3)
insert into #tmp values ('Florida', '2010-01-01', 5)
select * from #tmp
pivot (sum(record) for date1 in ([2010-01-01], [2012-01-01])) as avg
output
country 2010-01-01 2012-01-01
California 7 1
Florida 8 NULL
If you want to be more flexible, you need some pre-processing to get from full timestamps to days (in order for later on the PIVOT's grouping to actually have the anticipated effect):
CREATE VIEW DataQualTestView AS
SELECT
title
, description
, DATEFROMPARTS (DATEPART(yyyy, date_time),
DATEPART(mm, date_time),
DATEPART(dd, date_time)) AS day_from_date_time
, recordsCount
FROM DataQualTest
;
From there you could continue:
DECLARE #query AS NVARCHAR(MAX)
DECLARE #columns AS NVARCHAR(MAX)
SELECT #columns = ISNULL(#columns + ',' , '')
+ QUOTENAME(day_from_date_time)
FROM (SELECT DISTINCT
day_from_date_time
FROM DataQualTestView) AS TheDays
SET #query =
N'SELECT
title
, description
, ' + #columns + '
FROM DataQualTestView
PIVOT(SUM(recordsCount)
FOR day_from_date_time IN (' + #columns + ')) AS Pivoted'
EXEC SP_EXECUTESQL #query
GO
... and would get:
| title | description | 2015-05-08 | 2015-07-08 |
|-------|-------------|------------|------------|
| A | California | 3 | 11 |
| B | Florida | 4 | 2 |
See it in action: SQL Fiddle.
Please comment, if and as this requires adjustment / further detail.
I have below mentioned table :
drn RecNum Name Value
----------------------------------------------
1 1 ad1_pk 1
2 1 ad1_address1 P.O. Box 5036
3 1 ad1_address2 NULL
4 1 ad1_address3 NULL
5 1 ad1_ctyfk 56
6 1 ad1_postalcode 80155-5036
7 1 ad1_active Y
8 1 ad1_irstat A
9 1 ad1_irdata NULL
10 1 ad1_at1fk 1
1 2 ad1_pk 2
2 2 ad1_address1 1871 S. Broadway
3 2 ad1_address2 NULL
4 2 ad1_address3 NULL
5 2 ad1_ctyfk 1
6 2 ad1_postalcode 80210
7 2 ad1_active Y
8 2 ad1_irstat A
9 2 ad1_irdata NULL
10 2 ad1_at1fk 1
I am creating the pivot using the below mentioned query:
declare #var nvarchar(max)
declare #sql nvarchar(max)
set #var = stuff((select distinct ',' + name from temp
for xml path('')),1,1,'') -- **this is giving distinct column list but the order of columns get changed..**
set #sql = 'select * from temp
pivot(max(value) for name in (' + #var + ')) as pvt'
exec sp_executesql #sql
Is there a way to keep the order of the columns unchanged? I want the order of columns listed in #var to be same as in the table.
Add a GROUP BY and an ORDER BY clause (to replace the DISTINCT) where you build your column list as follows:
set #var = stuff((select ',' + min(name) from temp GROUP BY drn ORDER BY drn
for xml path('')),1,1,'')
And don't forget the the necessary aggregation (I've used MIN()). Thanks #Ionic.
This is because you're using a DISTINCT in your SELECT query. If you look at the execution plan, you can see DISTINCT SORT operation. This sorts your result based on the DISTINCT columns you specify, in this case it's Name:
To retain the order, you can try this:
set #var = stuff((
select ',' + name
from(
select
name,
drn,
rn = row_number() over(partition by name order by drn)
from temp
)t
where rn = 1
order by drn
for xml path('')),
1,1,'')