I have a query that will potentially return multiple rows for the same ID from my database. This is because it is a payment table and an invoice can be paid on multiple times.
So my results can look like this.
ID Company BillAmount AmountPaid
----- --------- ------------ ------------
123 ABC 1000.00 450.00
123 ABC 1000.00 250.00
456 DEF 1200.00 1200.00
I am building this query to put into Crystal Reports. If I just pull the raw data, I won't be able to do any sub totaling in CR as Bill amount on this will show $3200 when it is really $2200. I'll need to show balance and I can do that in CR but if I am pulling balance on each line returned, the total balance due for all records shown will be wrong as the "duplicate" rows will be counted wrong.
I am not sure what kind of report you need but maybe a query like this might be useful:
select ID, Company, max(BillAmount), sum(AmountPaid)
from Payment
group by ID
-improved after Juan Carlos' suggestion
For this, there are 2 option available.
at Crystal report side
In crystal report, there is facility to group, as suggested in this link, follow steps
for group summary, after add group, put all fields in group footer, check this link
at Sql side the below suggestion (you are not define which sql or db use, I assume Sqlserver 2012 and above)
Get the records with extra 2 column ( TotalBill ,TotalPaid)
declare #Invoice table(id int , Company varchar(25), BillAmount int )
declare #payment table(id int , InvoiceId int, AmountPaid int )
insert into #Invoice values (1, 'ABC', 1000), (2, 'DFE', 1200)
insert into #payment values (1, 1, 450), (2, 1, 250), (3, 2, 1200)
;with cte as
( select sum(BillAmount) TotalBill from #Invoice i )
Select
i.*, p.AmountPaid ,
Sum(AmountPaid) over ( partition by i.id ) InvoiceWiseTotalPaid,
cte.TotalBill,
Sum(AmountPaid) over ( order by i.id ) TotalPaid
from
#Invoice i
Join #payment p on i.id= p.InvoiceId
, cte
Output will be
Related
I have a simple table looks like this one:
company_Id user_Id price sub_price
123456 11111 200 NULL
123456 11111 500 NULL
456789 22222 300 NULL
And I want to consolidate records which has count(*) >= 2 into one row by summing up the price but with note what was summed up in column sub_price. Desired output should look like this one:
company_Id user_Id price sub_price
123456 11111 700 200,500
456789 22222 300 300
Is there any simple approach how to achieve desired output? Many thanks for your help in advance.
You can use listagg to turn the elements of a group into a string:
SELECT ...
, LISTAGG(price, ',') WITHIN GROUP (ORDER BY price) sub_price
FROM ...
Although listagg is SQL standard, it is not yet supported by all databases. However, most database offer similar functionality by a different name—e.g. string_agg in PostgreSQL and SQL Sever (since 2017) or group_concat in MySQL.
More info: http://modern-sql.com/feature/listagg (also showing alternatives if listagg is not supported)
This is one possible solution;
More info about concatenating multiple rows into single row you can find here
DECALRE #tbl AS table (
company_Id int
,user_Id int
,price int
,sub_price varchar(25)
)
INSERT INTO #tbl values (123456, 11111, 200, NULL)
INSERT INTO #tbl values (123456, 11111, 500, NULL)
INSERT INTO #tbl values (456789, 22222, 300, NULL)
SELECT
company_Id
,user_Id
,SUM(price) AS price
,STUFF(
(SELECT ',' + cast(price as varchar)
FROM #tbl
WHERE company_Id = a.company_id
AND user_Id = a.user_Id
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'') AS sub_price
FROM #tbl a
GROUP BY company_Id, user_Id
A department store uses the following table to track sales. The data model does not include information about the floor and the category of the goods been sold.
CREATE TABLE A1
(
date datetime,
category nvarchar(30),
turnover money
);
INSERT INTO A1 VALUES (SYSDATETIME(), 100, 1000);
INSERT INTO A1 VALUES (SYSDATETIME(), 201, 1700);
...
Data model/ table cannot be changed or edited. Information on the product group and floor can be diverted from the category.
CREATE VIEW sort_department_productgroups_view
AS
WITH sort_department_productgroups
AS (SELECT date,
category,
turnover,
CASE category
WHEN 100 THEN 1
WHEN 201 THEN 1
WHEN 303 THEN 1
WHEN 101 THEN 2
WHEN 102 THEN 2
ELSE 9
END
floor,
CASE category
WHEN 100 THEN 'sport'
WHEN 102 THEN 'sport'
WHEN 201 THEN 'leisure'
WHEN 303 THEN 'business'
WHEN 101 THEN 'sport'
WHEN 202 THEN 'leisure'
ELSE 'unknown'
END
productgroup
FROM a1)
SELECT *
FROM sort_department_productgroups;
go
Example query on the new view:
SELECT * FROM sort_department_productgroups_view where productgroup='sport';
Are there better ways to deal with such a task? Would this work on a big database?
This is really crying for a lookup table. But I must admit, that a category column of type nvarchar filled with numeric values makes me wonder...
Try it like this:
EDIT: Changed the related columen to NVARCHAR(30) as OP mentioned, that this is unchangeable...
CREATE TABLE CATEGORY(ID INT IDENTITY CONSTRAINT PK_CATEGORY PRIMARY KEY
,category NVARCHAR(30)
,CategoryName VARCHAR(100)
,[Floor] INT );
INSERT INTO CATEGORY VALUES
(100,'sport',1)
,(101,'sport',2)
,(102,'sport',2)
,(201,'leisure',1)
,(202,'leisure',9)
,(303,'business',9)
--add more...
CREATE TABLE A1
(
date datetime,
category NVARCHAR(30),
turnover money
);
INSERT INTO A1 VALUES (SYSDATETIME(), 100, 1000);
INSERT INTO A1 VALUES (SYSDATETIME(), 201, 1700);
GO
CREATE VIEW sort_department_productgroups_view
AS
SELECT A1.date
,a1.turnover
,ISNULL(CATEGORY.Floor,9) AS [Floor]
,ISNULL(CATEGORY.CategoryName,'unknown') AS [productgroup]
FROM A1
LEFT JOIN CATEGORY ON CATEGORY.category=A1.category;
GO
SELECT * FROM sort_department_productgroups_view
I agree with Shnugo that a reference table is much preferable.
I would define the view more simply as:
CREATE VIEW sort_department_productgroups_view AS
SELECT date, category, turnover,
(CASE WHEN category IN ('100', '201', '202') THEN 1
WHEN category IN ('101', '102') THEN 2
ELSE 9
END) as floor,
(CASE WHEN category IN ('100', '101', '102') THEN 'sport'
WHEN category IN ('201', '202') THEN 'leisure'
WHEN category IN ('303') THEN 'business'
ELSE 'unknown'
END) as productgroup
FROM a1;
Two important differences:
This no longer uses a searched case, so it keeps the definition of each group in one condition.
The values are in quotes because category is a string.
Finally, SQL Server allows computed columns, so you can incorporate this "column" into the table definition:
alter table a1
add floor as (CASE WHEN category IN ('100', '201', '202') THEN 1
WHEN category IN ('101', '102') THEN 2
ELSE 9
END);
And similarly for the product group.
I have a table looks like given below query, I add products price in this table daily, with different sellers name :
create table Product_Price
(
id int,
dt date,
SellerName varchar(20),
Product varchar(10),
Price money
)
insert into Product_Price values (1, '2012-01-16','Sears','AA', 32)
insert into Product_Price values (2, '2012-01-16','Amazon', 'AA', 40)
insert into Product_Price values (3, '2012-01-16','eBay','AA', 27)
insert into Product_Price values (4, '2012-01-17','Sears','BC', 33.2)
insert into Product_Price values (5, '2012-01-17','Amazon', 'BC',30)
insert into Product_Price values (6, '2012-01-17','eBay', 'BC',51.4)
insert into Product_Price values (7, '2012-01-18','Sears','DE', 13.5)
insert into Product_Price values (8, '2012-01-18','Amazon','DE', 11.1)
insert into Product_Price values (9, '2012-01-18', 'eBay','DE', 9.4)
I want result like this for n number of sellers(As more sellers added in table)
DT PRODUCT Sears[My Site] Amazon Ebay Lowest Price
1/16/2012 AA 32 40 27 Ebay
1/17/2012 BC 33.2 30 51.4 Amazon
1/18/2012 DE 7.5 11.1 9.4 Sears
I think this is what you're looking for.
SQLFiddle
It's kind of ugly, but here's a little breakdown.
This block allows you to get a dynamic list of your values. (Can't remember who I stole this from, but it's awesome. Without this, pivot really isn't any better than a big giant case statement approach to this.)
DECLARE #cols AS VARCHAR(MAX)
DECLARE #query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(SellerName)
FROM Product_Price
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '')
Your #cols variable comes out like so:
[Amazon],[eBay],[Sears]
Then you need to build a string of your entire query:
select #query =
'select piv1.*, tt.sellername from (
select *
from
(select dt, product, SellerName, sum(price) as price from product_price group by dt, product, SellerName) t1
pivot (sum(price) for SellerName in (' + #cols + '))as bob
) piv1
inner join
(select t2.dt,t2.sellername,t1.min_price from
(select dt, min(price) as min_price from product_price group by dt) t1
inner join (select dt,sellername, sum(price) as price from product_price group by dt,sellername) t2 on t1.min_price = t2.price) tt
on piv1.dt = tt.dt
'
The piv1 derived table gets you the pivoted values. The cleverly named tt derived table gets you the seller who has the minimum sales for each day.
(Told you it was kind of ugly.)
And finally, you run your query:
execute(#query)
And you get:
DT PRODUCT AMAZON EBAY SEARS SELLERNAME
2012-01-16 AA 40 27 32 eBay
2012-01-17 BC 30 51.4 33.2 Amazon
2012-01-18 DE 11.1 9.4 13.5 eBay
(sorry, can't make that bit line up).
I would think that if you have a reporting tool that can do crosstabs, this would be a heck of a lot easier to do there.
The problem is this requirement:
I want result like this for n number of sellers
If you have a fixed, known number of columns for your results, there are several techniques to PIVOT your data. But if the number of columns is not known, you're in trouble. The SQL language really wants you to be able to describe the exact nature of the result set for the select list in terms of the number and types of columns up front.
It sounds like you can't do that. This leaves you with two options:
Query the data to know how many stores you have and their names, and then use that information to build a dynamic sql statement.
(Preferred option) Perform the pivot in client code.
This is something that would probably work well with a PIVOT. Microsoft's docs are actually pretty useful on PIVOT and UNPIVOT.
http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
Basically it allows you to pick a column, in your case SellerName, and pivot that out so that the elements of the column themselves become columns in the new result. The values that go in the new "Ebay", "Amazon", etc. columns would be an aggregate that you choose - in this case the MAX or MIN or AVG of the price.
For the final "Lowest Price" column you'd likely be best served by doing a subquery in your main query which finds the lowest value per product/date and then joining that back in to get the SellerName. Something like:
SELECT
Product_Price.Date
,Product_Price.Product
,Product_Price.MinimumSellerName
FROM
(SELECT
MIN(Price) AS min_price
,Product
,Date
FROM Product_Price
GROUP BY
Product
,Date) min_price
INNER JOIN Product_Price
ON min_price.Product = Product_Price.Product
AND min_price.Date = Product_Price.Date
Then just put the pivot around that and include the MinimumSellerName columnm, just like you include date and product.
sorry if this has already been posted but I've been through umpteen posts on pivoting the past day and still havn't been able to get the result i want.
Background:
In short, I am developing a set of tables that will store a questionnaire dynamically.
I wont go into detail of it probably isnt relative.
I basically want to query the table that stores the user input for a set question.
These questions branch off each other allowing me to show columns and rows per question etc.
Anyway this query:
SELECT qr.*, Question
FROM QuestionRecord qr
INNER JOIN
QuestionRecord P
ON P.ID = qr.ParentQuestionRecordId
JOIN Questions q ON q.ID = qr.QuestionID
Produces this result set :
ID FormRecordId QuestionId ParentQuestionRecordId Value Question
---------------------------------------------------------------------------------------
2 1 31 1 Consultancy Eligible project costs
3 1 32 2 NULL Date
4 1 33 2 25000 Cash Costs £
5 1 34 2 NULL In Kind Costs £
6 1 35 2 25000 Total Costs
7 1 31 1 Orchard day x2 Eligible project costs
8 1 32 7 NULL Date
9 1 33 7 15000 Cash Costs £
10 1 34 7 NULL In Kind Costs £
11 1 35 7 15000 Total Costs
I basically want to Pivot(I think) these rows to look like so:
Eligible project costs Date Cash Costs £ In Kind Costs Total Costs
--------------------------------------------------------------------------------
Consultancy NULL 25000 NULL 25000
Orchard day x2 NULL 15000 NULL 15000
I have tried:
SELECT [Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs]
FROM
(
SELECT qr.*, Question
FROM QuestionRecord qr
INNER JOIN
QuestionRecord P
ON P.ID = qr.ParentQuestionRecordId
JOIN Questions q ON q.ID = qr.QuestionID
)pvt
PIVOT
(
MIN(Value)
FOR Question IN
([Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs])
)pivotTable
but this returns each column on a seperate row:
Eligible project costs Date Cash Costs £ In Kind Costs Total Costs
--------------------------------------------------------------------------------
Consultancy NULL NULL NULL NULL
NULL NULL NULL NULL NULL
NULL NULL 25000 NULL NULL
NULL NULL NULL NULL NULL
NULL NULL NULL NULL 25000
So that's as close as i have managed to get with it, i was wondering if you guys/girls could help me out :)
Thanks!
Try the following changes to your script (strikethrough = deleted, bold = added):
SELECT [Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs]
FROM
(
SELECT qr.*,
grp = ROW_NUMBER() OVER (PARTITION BY qr.QuestionId ORDER BY qr.ID),
Value,
Question
FROM QuestionRecord qr
INNER JOIN
QuestionRecord P
ON P.ID = qr.ParentQuestionRecordId
JOIN Questions q ON q.ID = qr.QuestionID
)pvt
PIVOT
(
MIN(Value)
FOR Question IN
([Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs])
)pivotTable
I think it must give your the result you are after.
Change SELECT qr.*, Question to SELECT Value, Question. PIVOT groups by the remaining columns.
what you need, like andriy kinda pointed out, is something to make each record unique depending on how you want them grouped. now, if this is a survey system i'm going to guess that you've got some sort of id to identify who the record belongs to. the reason why it's returning on seperate rows is that you have unique records for each row based on those ids, what you need is to add the respondent id to your derived table and get rid of your other id's.
see my example:
declare #table table (ID int identity(1,1), QuestionID int, value varchar(50), Respondent int)
declare #questions table (QID int, name varchar(50))
insert into #questions values (31,'Eligible project costs')
insert into #questions values (32,'Date')
insert into #questions values (33,'Cash Costs')
insert into #questions values (34,'In Kind Costs')
insert into #questions values (35,'Total Costs')
insert into #table values (31,'Consultancy',1)
insert into #table values (32,null,1)
insert into #table values (33,25000,1)
insert into #table values (34,null,1)
insert into #table values (35,25000,1)
insert into #table values (31,'Orchard day x2',2)
insert into #table values (32,null,2)
insert into #table values (33,15000,2)
insert into #table values (34,null,2)
insert into #table values (35,15000,2)
select
[Eligible project costs],[Date],[Cash Costs],[In Kind Costs],[Total Costs]
from
(
select
Respondent,
q.name,
t.Value
from #table t
inner join #questions q
on t.QuestionID=QID
) a
pivot
(
min(Value)
for name in ([Eligible project costs],[Date],[Cash Costs],[In Kind Costs],[Total Costs])
) p
Say I have some data stored in an audit table, where triggers on the main data table write all invoice record updates to this audit table. The audit table contains this data:
InvoiceID CustomerID ItemSold AmountSold SalesPerson ModifyDate
1001 96 Widget 800 Robert 2001-1-1
1006 85 Thinger 350 Phil 2001-1-8
1001 96 Widget 800 Bobby 2001-1-9
1005 22 Widget 400 Robert 2001-1-10
1006 44 Thinger 500 Mike 2001-2-5
1001 96 Widget 250 Robert 2001-6-4
And I want to write a query which will identify whenever the SalesPerson field changes, for any particular InvoiceID (eg: whenever a salesman changes the sale to his name).
So in the example above, I'd like to identify the change which took place on 2001-1-9, where the sale for InvoiceID 1001 went from Robert to Bobby, and the change on 2001-6-4 where it went back to Robert from Bobby...so two changes for that particular ID. And I'd also like to identify the change on 2001-2-5 where the sale for InvoiceID 1006 went from Phil to Mike.
How can I write a SQL query which will identify/highlight these changes?
The table doesn't currently contain a primary key, but I can add one if needed.
If you add a primary key (which you should do, it will make some of the querying you need on this table easier in the long run)
Then what you need is a self join. Something like this might do it:
select a.invoiceId, a.SalesPerson as FirstSalesPerson,
a.Modifydate as FirstModifyDate, b.SalesPerson as SecondSalesPerson,
B.Modifydate as SecondModifyDate
from myaudittable a
join myadudittable b
on a.InvoiceID = b.InvoiceID
where a.AuditIDd <>b.AuditID and a.ModifyDate < b.ModifyDate
and a.SalesPerson<>b.SalesPerson
order by InvoiceID
This should do it.
declare #Audit table (
InvoiceID int,
CustomerID int,
ItemSold varchar(10),
AmountSold int,
SalesPerson varchar(10),
ModifyDate datetime
)
insert into #Audit
(InvoiceID, CustomerID, ItemSold, AmountSold, SalesPerson, ModifyDate)
values
(1001, 96, 'Widget', 800, 'Robert', '2001-1-1'),
(1006, 85, 'Thinger', 350, 'Phil', '2001-1-8'),
(1001, 96, 'Widget', 800, 'Bobby', '2001-1-9'),
(1005, 22, 'Widget', 400, 'Robert', '2001-1-10'),
(1006, 44, 'Thinger', 500, 'Mike', '2001-2-5'),
(1001, 96, 'Widget', 250, 'Robert', '2001-6-4')
select a2.InvoiceID, a2.SalesPerson, a2.ModifyDate
from #Audit a1
inner join #Audit a2
on a1.InvoiceID = a2.InvoiceID
and a1.ModifyDate < a2.ModifyDate
and a1.SalesPerson <> a2.SalesPerson
Here's a more complete answer, I think. It assumes:
at least SQL Server 2005
that the ModifyDate column is the time at which the record is created in the audit log.
the existence of an identity primary key, AuditID
declare #Audit table
(
AuditID int identity(1,1),
InvoiceID int,
CustomerID int,
ItemSold varchar(10),
AmountSold int,
SalesPerson varchar(10),
ModifyDate datetime
)
;with orders (InvoiceID, SalesPerson, ModifyDate, idx)
as
(
select
InvoiceID,
SalesPerson,
ModifyDate,
row_number() over (partition by InvoiceID order by AuditID desc)
from #Audit
)
select o2.InvoiceID, o2.SalesPerson, o2.ModifyDate from orders o1 inner join orders o2
on
o1.InvoiceID = o2.InvoiceID and
o1.SalesPerson <> o2.SalesPerson and
o1.idx = o2.idx-1
order by InvoiceID, ModifyDate desc
I used some bits and pieces from the posted answers, but the only way I was able to isolate the actual changes in salesperson was to use a subquery. Otherwise I was getting too many results and it was difficult to isolate the actual dates that the record changed salespersons.
select InvoiceId,SalesPerson,auditdate from myaudittable where InvoiceId in
(select distinct a.InvoiceId
from myaudittable a inner join myaudittable b on a.InvoiceId = b.InvoiceId and
a.SalesPerson <> b.SalesPerson)
group by InvoiceId,SalesPerson