SQL Server: pivoting across multiple colums - sql-server

I have a serious problem. Say my data is arranged in this format:
Name Businees_unit Forecast Upside
Jack.N India 100 50
Jack.N China 250 20
I have to pivot Forecast and Upside for Business_Unit so the table will look like this
Name Forecast_India Upside_India Forecast_China Upside_China
Jack 100 50 250 20
Can this be done in one query?
Its my first entry, so any help is very welcomed.
Thanks

A generic solution:
select name,
sum(case when Businees_unit = 'India' then Forecast else 0 end) Forecast_India,
sum(case when Businees_unit = 'India' then Upside else 0 end) Upside_India,
sum(case when Businees_unit = 'China' then Forecast else 0 end) Forecast_China,
sum(case when Businees_unit = 'China' then Upside else 0 end) Upside_China
from My_table
group by name

I would use self join:
SELECT DISTINCT SomeTable.Name, China.Forecast as Forecast_China, China.Upside as Upside_China
, India.Forecast as Forecast_India, India.Upside as Upside_India
FROM SomeTable
inner join SomeTable India on India.Name = SomeTable.Name AND India.Business_unit = 'India'
inner join SomeTable China on China.Name = SomeTable.Name AND China.Business_unit = 'China'

Related

Get data from two date ranges within same table (MSSQL)

I have this query:
SELECT
COUNT(DISTINCT ProdTr.OrdNo) AS Orders,
ProdTr.YrPr AS Period,
SUM(ProdTr.DAm) AS Total,
SUM(ProdTr.IncCst) AS Cost
FROM ProdTr
WHERE ProdTr.TrTp = 1 AND ProdTr.CustNo != 0
AND ProdTr.YrPr BETWEEN (201901) AND (201912)
GROUP BY ProdTr.YrPr
ORDER BY ProdTr.YrPr ASC
And it works well. It yields the expected result, sales data from the date period 2019-01 to 2019-12. Result:
I would like to add an extra column that shows the same data - but from last year. For period 2019-01 it should show sales data for 2018-01 (1 year back). I managed to do this with a subquery, but it is slow - and seems like a bad idea.
Are there any better ways to achieve this? Database version is MSSQL 2016.
Thank you very much for your time.
You can do it with conditional aggregation:
SELECT
COUNT(DISTINCT CASE WHEN LEFT(YrPr, 4) = '2019' THEN OrdNo END) AS Orders2019,
'2019' + RIGHT(YrPr, 2) AS Period2019,
SUM(CASE WHEN LEFT(YrPr, 4) = '2019' THEN DAm END) AS Total2019,
SUM(CASE WHEN LEFT(YrPr, 4) = '2019' THEN IncCst END) AS Cost2019,
SUM(CASE WHEN LEFT(YrPr, 4) = '2018' THEN DAm END) AS Total2018
FROM ProdTr
WHERE TrTp = 1 AND CustNo != 0
AND YrPr BETWEEN (201801) AND (201912)
GROUP BY RIGHT(YrPr, 2)
ORDER BY Period2019 ASC
You could do it like this:
WITH TwoYears AS (
SELECT COUNT(DISTINCT ProdTr.OrdNo) AS Orders
, ProdTr.YrPr AS Period
, SUM(ProdTr.DAm) AS Total
, SUM(ProdTr.IncCst) AS Cost
FROM ProdTr
WHERE ProdTr.TrTp = 1
AND ProdTr.CustNo != 0
AND ProdTr.YrPr BETWEEN 201801 AND 201912
GROUP BY ProdTr.YrPr
), CurrentYear AS (
SELECT Orders, Period, Total, Cost
FROM TwoYears
WHERE Period >= 201901
), PreviousYear AS (
SELECT Orders, Period, Total, Cost
FROM TwoYears
WHERE Period < 201901
)
SELECT c.Orders, c.Period, c.Total, c.Cost
, p.Orders AS PrevOrders, p.Period AS PrevPeriod, p.Total AS PrevTotal, p.Cost AS PrevCost
FROM CurrentYear c
FULL JOIN PreviousYear p ON p.Period = c.Period - 100
ORDER BY COALESCE(c.Period, p.Period + 100)

Define a relationship as a union

In several parts of my code I have for example these lines:
$user->movements()->get();
...
$user->movements()->where(...)->get();
...
$user->movements()->where(...)->select(... sum, count, avg ...)->get();
...
But now I'm facing an important change on the movements table structure.
Now I need two tables with a similar structure (the data HAVE to be in separate tables), movements and ticket_movements.
These tables are consulted by two system, one of then needs the data to be separated and the other needs the data to be as in one table.
So, in one of the systems, I would like to define the relationship movements() as an union of movements and ticket_movements tables.
So, having the relationship movements defined as:
public function movements()
{
$movements = $this->hasMany('App\Model\Movement')
->select(\DB::raw("
id,
user_id,
movement_type_id,
amount,
description
"));
$tickets_movements = $this->hasMany('App\Model\TicketMovement')
->select(\DB::raw("
id,
user_id,
movement_type_id,
amount,
description
"));
return $movements->union($tickets_movements->getQuery());
}
If I do this:
$user->movements()
->whereIn('movement_type_id', [1, 2])
->select(\DB::raw('
SUM(CASE WHEN movement_type_id = 1 THEN 1 ELSE 0 END) as credit,
SUM(CASE WHEN movement_type_id = 2 THEN 1 ELSE 0 END) as debit
'))
->first();
The query I get is:
SELECT
SUM(CASE WHEN movement_type_id = 1 THEN 1 ELSE 0 END) as credit,
SUM(CASE WHEN movement_type_id = 2 THEN 1 ELSE 0 END) as debit
FROM "movements"
WHERE "movements"."user_id" = 2
AND "movements"."user_id" is not null
AND "movement_type_id" in (1, 2)
UNION
SELECT id, user_id, movement_type_id, amount, description
FROM "ticket_movements"
WHERE "ticket_movements"."user_id" = 2
AND "ticket_movements"."user_id" is not null limit 1
Besides it's not the query I need, it give me an error because of the columns:
Syntax error: 7 ERROR:
each UNION query must have the same number of columns
The query I need is something like this:
SELECT
SUM(CASE WHEN movement_type_id = 1 THEN 1 ELSE 0 END) as credit,
SUM(CASE WHEN movement_type_id = 2 THEN 1 ELSE 0 END) as debit
FROM (
SELECT id, user_id, movement_type_id, amount, description
FROM "movements"
UNION
SELECT id, user_id, movement_type_id, amount, description
FROM "ticket_movements" ) as movements
WHERE "movements"."user_id" = 2
AND "movements"."movement_type_id" in (1, 2)
Without modifying each line where I do $user->movements()...
I don't know is that is possible...

How to flatten this multiple rows into one?

I have data like this: (table name: Activities)
ActivityId CreatedOn TypeId
1 2017-01-01 1
1 2017-01-02 1
1 2017-01-02 2
2 2017-01-01 3
Where Type is a lookup value: (table name: Types)
TypeId Name
1 Question
2 Answer
3 Comment
Basically it's an activity history table.
I want to turn the above tables into a grouped sum row for types, for each ActivityId, like this:
ActivityId QuestionCount AnswerCount CommentCount
1 2 1 0
2 0 0 1
I know the answer is probably pretty simple, but it's eluding me for some reason.
Any help? Thanks in advance.
A simple join and conditional aggregation should do the trick (I suspect you were over-thinking it)
Select ActivityID
,QuestionCount = sum(case when Name='Question' then 1 else 0 end)
,AnswerCount = sum(case when Name='Answer' then 1 else 0 end)
,CommentCount = sum(case when Name='Comment' then 1 else 0 end)
From Activities A
Join Types B on A.TypeId=B.TypeId
Group By ActivityId
Returns
ActivityID QuestionCount AnswerCount CommentCount
1 2 1 0
2 0 0 1
You could also do it without the Join... Just less readable
Select ActivityID
,QuestionCount = sum(case when TypeId=1 then 1 else 0 end)
,AnswerCount = sum(case when TypeId=2 then 1 else 0 end)
,CommentCount = sum(case when TypeId=3 then 1 else 0 end)
From #Activities A
Group By ActivityId
You could also try a PIVOT
Select ActivityID
,[1] as QuestionCount
,[2] as AnswerCount
,[3] as CommentCount
From (Select ActivityId,TypeID,1 as Cnt From #Activities) A
Pivot (count(Cnt) For TypeId in ([1],[2],[3]) ) p

Query returns two lines

I have a table that has a column called type which has either a value of invoiced or order and then another column holding the value along with a column holding the customer number etc.
I have written a script :-
select
customer,
(CASE WHEN TYPE = 'INVOICED' THEN SUM(INVTOTAL) else 0 END) AS INVTOTAL,
(CASE WHEN TYPE = 'ORDERS' THEN SUM(INVTOTAL) else 0 END) AS ORDERTOTAL
from
salestable
Why does it return the following?
customer INVTOTAL ORDERTOTAL
Joe Bloggs 1000 0
Joe Bloggs 0 1300
instead of
customer INVTOTAL ORDERTOTAL
Joe Bloggs 1000 1300
Sorry to ask such a novice question but I am new to SQL and learning it...
Thanks for any help!
Your query was missing a group by. Also use sum around case to avoid multiple rows.
select customer,
sum(CASE WHEN TYPE = 'INVOICED' THEN INVTOTAL else 0 END) AS INVTOTAL,
sum(CASE WHEN TYPE = 'ORDERS' THEN INVTOTAL else 0 END) AS ORDERTOTAL
from salestable
group by customer
You need to do a group by with the customer that will avoid the multiple rows.check the fiddle below.
create table tb1
(customer varchar(25),
type varchar(25),
invoice numeric(18,2)
);
insert into tb1(customer,type,invoice) values('Joe Bloggs','INVOICED',1000);
insert into tb1(customer,type,invoice) values('Joe Bloggs','ORDERS',1000);
select customer,
sum(CASE WHEN TYPE = 'INVOICED' THEN sum(invoice) else 0 END) AS INVTOTAL,
sum(CASE WHEN TYPE = 'ORDERS' THEN sum(invoice) else 0 END) AS ORDERTOTAL
from tb1
group by customer
fiddle with example
You need to put the entire case statment inside sum()
inorder to avoid grouping by type also else you will get this below error
Column 'tb1.type' is invalid in the select list because it is
not contained in either an aggregate function or the GROUP BY clause.
Because select returns a row for each row in the table matching the where filters. If no filters, then a result row for every row in table. If you want to "join" the rows, you could try grouping by the customer, like:
SELECT customer,
SUM(CASE WHEN TYPE = 'INVOICED' THEN INVTOTAL ELSE 0 END) as INVTOTAL,
SUM(CASE WHEN TYPE = 'ORDERS' THEN INVTOTAL ELSE 0 END) as ORDERTOTAL
FROM salestable
GROUP BY customer

Grouping Sale Quantities and Sale Figures by Days of the week using sql

Database: SQL Server 2008
I need to produce a sales report which groups sales figures into days of the week, e.g.;
Product Mon Tues Wed Thurs Friday Sat Sunday Total
Product 1 5 2 0 4 3 2 1 17
Product 2 2 1 4 3 1 1 1 13
I have two joined tables tbl_orders (primary table holding order no, order status etc.), tbl_orderitems (table holding item information, qty, price, product etc).
The date field is dbo.tbl_orders.dte_order_stamp
Structure of joined tables;
SELECT
dbo.tbl_orders.uid_orders,
dbo.tbl_orders.dte_order_stamp,
dbo.tbl_orders.txt_order_ref,
dbo.tbl_orders.uid_order_custid,
dbo.tbl_orders.uid_order_webid,
dbo.tbl_orders.txt_order_status,
dbo.tbl_orders.uid_order_addid,
dbo.tbl_orders.mon_order_tax,
dbo.tbl_orders.mon_order_grandtotal,
dbo.tbl_orders.mon_order_discount,
dbo.tbl_orders.bit_order_preorder,
dbo.tbl_orders.int_order_deposit_percent,
dbo.tbl_orders.mon_order_delivery,
dbo.tbl_orders.txt_order_deltype,
dbo.tbl_orders.txt_order_process,
dbo.tbl_orders.txt_voucher_code,
dbo.tbl_orders.txt_order_terms,
dbo.tbl_orders.dte_order_paydate,
dbo.tbl_orders.int_order_taxrate,
dbo.tbl_orders.txt_order_googleid,
dbo.tbl_orders.bit_order_archive,
dbo.tbl_orderitems.uid_orderitems,
dbo.tbl_orderitems.uid_orditems_orderid,
dbo.tbl_orderitems.txt_orditems_pname,
dbo.tbl_orderitems.uid_orditems_pcatid,
dbo.tbl_orderitems.uid_orditems_psubcatid,
dbo.tbl_orderitems.mon_orditems_pprice,
dbo.tbl_orderitems.int_orderitems_qty,
dbo.tbl_orderitems.txt_orditems_stype,
dbo.tbl_orderitems.txt_orditems_pref,
dbo.tbl_orderitems.uid_orditems_prodid
FROM
dbo.tbl_orders
INNER JOIN dbo.tbl_orderitems ON (dbo.tbl_orders.uid_orders = dbo.tbl_orderitems.uid_orditems_orderid)
I am using the following statement to get overall sales figures, can i adapt this to group by days of the week? Not really sure where to start, i have done some reading on datepart but not quite sure how to implement it, or would i be better to wrap select statements?
SELECT
SUM(dbo.tbl_orderitems.mon_orditems_pprice) AS prodTotal,
AVG(dbo.tbl_orderitems.mon_orditems_pprice) AS avgPrice,
count(dbo.tbl_orderitems.uid_orditems_prodid) AS prodQty,
dbo.tbl_orderitems.txt_orditems_pname
FROM dbo.tbl_orderitems
INNER JOIN dbo.tbl_orders ON (dbo.tbl_orderitems.uid_orditems_orderid = dbo.tbl_orders.uid_orders)
WHERE dbo.tbl_orders.txt_order_status = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sale_status#">
GROUP BY
dbo.tbl_orderitems.txt_orditems_pname
ORDER BY dbo.tbl_orderitems.txt_orditems_pname ASC
Any help would be appreciated.
You could use a PIVOT. Like this:
SELECT
pvt.txt_orditems_pname AS Product,
pvt.[Monday],
pvt.[Tuesday],
pvt.[Wednesday],
pvt.[Thursday],
pvt.[Friday],
pvt.[Saturday],
pvt.[Sunday],
(
pvt.[Monday]+pvt.[Tuesday]+pvt.[Wednesday]+pvt.[Thursday]+pvt.[Friday]+
pvt.[Saturday]+pvt.[Sunday]
) AS Total
FROM
(
SELECT
DATENAME(WEEKDAY,dbo.tbl_orders.dte_order_stamp) AS WeekDayName,
dbo.tbl_orderitems.mon_orditems_pprice,
dbo.tbl_orderitems.txt_orditems_pname
FROM dbo.tbl_orderitems
INNER JOIN dbo.tbl_orders
ON (dbo.tbl_orderitems.uid_orditems_orderid = dbo.tbl_orders.uid_orders)
WHERE
dbo.tbl_orders.txt_order_status = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sale_status#">
) AS SourceTable
PIVOT
(
SUM(mon_orditems_pprice)
FOR WeekDayName IN([Monday],[Tuesday],[Wednesday],
[Thursday],[Friday],[Saturday],[Sunday])
)
AS pvt
Or if you do not want to use a PIVOT. You can do it like this:
SELECT
t.txt_orditems_pname AS Product,
t.[Monday],
t.[Tuesday],
t.[Wednesday],
t.[Thursday],
t.[Friday],
t.[Saturday],
t.[Sunday],
(
t.[Monday]+t.[Tuesday]+t.[Wednesday]+t.[Thursday]+t.[Friday]+
t.[Saturday]+t.[Sunday]
) AS Total
FROM
(
SELECT
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=7 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Sunday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=1 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Saturday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=2 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Monday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=3 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Tuesday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=4 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Wednesday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=5 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Thursday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=6 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Friday],
dbo.tbl_orderitems.txt_orditems_pname
FROM dbo.tbl_orderitems
INNER JOIN dbo.tbl_orders
ON (dbo.tbl_orderitems.uid_orditems_orderid = dbo.tbl_orders.uid_orders)
WHERE
dbo.tbl_orders.txt_order_status = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sale_status#">
GROUP BY
dbo.tbl_orderitems.txt_orditems_pname
) AS t

Resources