How to get multiple columns in Crosstab - sql

I would like a cross table from the following table.
The cross table should look like this
A pivot table does not seem to solve the problem, because only one column can be used at a time. But in our case we are dealing with 4 different columns. (payment, month, year and free of charge)
I solved the problem by splitting these 4 columns into four different pivot tables, using temporary tables and finally reassembling the obtained data. But this is very complicated, long and confusing, in short not very nice...
The years and months should be shown in ascending form, exactly as you can see in the cross table above.
I have been looking for a solution for quite a while but I can't find the same problem anywhere.
If someone would give me a short, elegant solution I would be very grateful.
Under http://www.sqlfiddle.com/#!18/7216f/2 you can see the problem definition.
Thank you!

You can rank records by date in a subquery with row_number(), and then pivot with conditional aggregation:
select
ClientId,
max(case when rn = 1 then Payment end) Payment1,
max(case when rn = 2 then Payment end) Payment2,
max(case when rn = 3 then Payment end) Payment3,
max(case when rn = 1 then [Month] end) Month1,
max(case when rn = 2 then [Month] end) Month2,
max(case when rn = 3 then [Month] end) Month3,
max(case when rn = 1 then [Year] end) Year1,
max(case when rn = 2 then [Year] end) Year2,
max(case when rn = 3 then [Year] end) Year3,
max(case when rn = 1 then FreeOfCharge end) FreeOfCharge1,
max(case when rn = 2 then FreeOfCharge end) FreeOfCharge2,
max(case when rn = 3 then FreeOfCharge end) FreeOfCharge3
from (
select
t.*,
row_number() over(partition by ClientId order by [Year], [Month]) rn
from mytable t
) t
group by ClientId

You can join the table with itself a few times, as in:
with p as (
select
*, row_number() over(partition by clientid order by year, month) as n
from Payment
)
select
p1.clientid,
p1.payment, p2.payment, p3.payment,
p1.month, p2.month, p3.month,
p1.year, p2.year, p3.year,
p1.freeofcharge, p2.freeofcharge, p3.freeofcharge
from p p1
left join p p2 on p2.clientid = p1.clientid and p2.n = 2
left join p p3 on p3.clientid = p1.clientid and p3.n = 3
where p1.n = 1
See Fiddle.

Related

expected result is not getting

I have a data like this:
And I want this - I am trying with PIVOT but not getting the expected results:
Query is
SELECT AttendeeID,[Quantity1],[PROD1],[Quantity2],[PROD2],[Quantity3],[PROD3] FROM
(SELECT * ,
row_number() over(partition by AttendeeID order by AttendeeID)rn
from #ProductTestingwithPosition2) TT
PIVOT
(MAX(product) for ProductPosition in ([PROD1],[PROD2],[PROD3])) AS Tab2
PIVOT
(sum(Quantity) for QuantityPosition in ([Quantity1],[Quantity2],[Quantity3])) AS Tab3
I am getting this output:
Just use conditional aggregation:
SELECT AttendeeID,
MAX(CASE WHEN ProductPosition = 'PROD1' THEN product END) as prod1,
MAX(CASE WHEN QuantityPosition = 'QUANTITY1' THEN quantity END) as quantity1,
MAX(CASE WHEN ProductPosition = 'PROD2' THEN product END) as prod2,
MAX(CASE WHEN QuantityPosition = 'QUANTITY2' THEN quantity END) as quantity2,
MAX(CASE WHEN ProductPosition = 'PROD3' THEN product END) as prod3,
MAX(CASE WHEN QuantityPosition = 'QUANTITY3' THEN quantity END) as quantity3
FROM (SELECT ptp.* ,
row_number() over (partition by AttendeeID, ProductPosition order by AttendeeID) as seqnum
FROM #ProductTestingwithPosition2 ptp
) ptp
GROUP BY AttendeeID, seqnum;
PIVOT -- in addition to being non-standard -- is very finicky. Conditional aggregation is much more powerful and less prone to errors.

Hive rolling sum of data over date

I am working on Hive and am facing an issue with rolling counts. The sample data I am working on is as shown below:
and the output I am expecting is as shown below:
I tried using the following query but it is not returning the rolling count:
select event_dt,status, count(distinct account) from
(select *, row_number() over (partition by account order by event_dt
desc)
as rnum from table.A
where event_dt between '2018-05-02' and '2018-05-04') x where rnum =1
group by event_dt, status;
Please help me with this if some one has solved a similar issue.
You seem to just want conditional aggregation:
select event_dt,
sum(case when status = 'Registered' then 1 else 0 end) as registered,
sum(case when status = 'active_acct' then 1 else 0 end) as active_acct,
sum(case when status = 'suspended' then 1 else 0 end) as suspended,
sum(case when status = 'reactive' then 1 else 0 end) as reactive
from table.A
group by event_dt
order by event_dt;
EDIT:
This is a tricky problem. The solution I've come up with does a cross-product of dates and users and then calculates the most recent status as of each date.
So:
select a.event_dt,
sum(case when aa.status = 'Registered' then 1 else 0 end) as registered,
sum(case when aa.status = 'active_acct' then 1 else 0 end) as active_acct,
sum(case when aa.status = 'suspended' then 1 else 0 end) as suspended,
sum(case when aa.status = 'reactive' then 1 else 0 end) as reactive
from (select d.event_dt, ac.account, a.status,
max(case when a.status is not null then a.timestamp end) over (partition by ac.account order by d.event_dt) as last_status_timestamp
from (select distinct event_dt from table.A) d cross join
(select distinct account from table.A) ac left join
(select a.*,
row_number() over (partition by account, event_dt order by timestamp desc) as seqnum
from table.A a
) a
on a.event_dt = d.event_dt and
a.account = ac.account and
a.seqnum = 1 -- get the last one on the date
) a left join
table.A aa
on aa.timestamp = a.last_status_timestamp and
aa.account = a.account
group by d.event_dt
order by d.event_dt;
What this is doing is creating a derived table with rows for all accounts and dates. This has the status on certain days, but not all days.
The cumulative max for last_status_timestamp calculates the most recent timestamp that has a valid status. This is then joined back to the table to get the status on that date. Voila! This is the status used for the conditional aggregation.
The cumulative max and join is a work-around because Hive does not (yet?) support the ignore nulls option in lag().

How to return Value based on Subquery in SQL Server?

I Have a table with ITEM , OPCODE ,WorkCenter, LOT , COIL ,Strt_Time(DateTime) .I want to return Previous WorkCenter and NextWorkCenter along with ITEM , OPCODE ,WorkCenter, LOT , COIL ,Strt_Time .
A coil of particlular LOT will be routed from Workcenter to Workcenter with time stamp(Strt_time)
Please kindly advise how to achieve this in SQL Server 2008 .
Thanks in advance.
In SQL Server 2012 you should use LEAD and LAG Functions.
Since you are using SQL Server 2008, here is a workaround:
WITH WorkCenter_details AS
(
SELECT
ROW_NUMBER() OVER ( ORDER BY Strt_Time) rn,
(ROW_NUMBER() OVER ( ORDER BY Strt_Time)) / 2 rndiv2,
(ROW_NUMBER() OVER ( ORDER BY Strt_Time) + 1) / 2 rnplus1div2,
ITEM, OPCODE, WorkCenter, LOT, COIL, Strt_Time
FROM
MyTable
)
SELECT
rn, rndiv2, rnplus1div2,
ITEM, OPCODE, WorkCenter, LOT, COIL, Strt_Time,
CASE
WHEN rn % 2 = 1
THEN MAX(CASE WHEN rn % 2 = 0 THEN WorkCenter END) OVER (PARTITION BY rndiv2)
ELSE MAX(CASE WHEN rn % 2 = 1 THEN WorkCenter END) OVER (PARTITION BY rnplus1div2)
END AS WorkCenter_Previous,
CASE
WHEN rn % 2 = 1
THEN MAX(CASE WHEN rn % 2 = 0 THEN WorkCenter END) OVER (PARTITION BY rnplus1div2)
ELSE MAX(CASE WHEN rn % 2 = 1 THEN WorkCenter END) OVER (PARTITION BY rndiv2)
END AS WorkCenter_Next
FROM
WorkCenter_details
ORDER BY
ITEM, Strt_Time
This works only if workcenter is numeric

Return the First, Second and Third Earliest Purchase Dates per Purchaser

for a list of purchaser email addresses, I am trying to return one line per purchaser that has the columns '1stOrderDate', '2ndOrderDate', '3rdOrderDate' and 'TotalNumberofOrders'
I have tried using the ROW_Number function in the WHERE clause of subqueries but it reports that Windowed functions aren't allowed in the WHERE clause, so any help on how I fill in the ???s below would be gratefully received!
SELECT
PT.email AS 'Email',
MIN(OT.orderdate) AS '1stOrderDate',
??? AS '2ndOrderDate',
??? AS '3rdOrderDate',
COUNT(DISTINCT OT.order_reference) AS 'TotalNumberOfOrders'
FROM dbo.Orders AS OT
JOIN dbo.Purchaser AS PT ON OT.account_reference = PT.account_reference
GROUP BY PT.Email
You can do this with row_number() and conditional aggregation:
SELECT PT.email,
MAX(CASE WHEN seqnum = 1 THEN OT.OrderDate END) as OrderDate_1,
MAX(CASE WHEN seqnum = 2 THEN OT.OrderDate END) as OrderDate_2,
MAX(CASE WHEN seqnum = 3 THEN OT.OrderDate END) as OrderDate_3,
COUNT(DISTINCT OT.order_reference) AS TotalNumberOfOrders
FROM (SELECT o.*, ROW_NUMBER() OVER (PARTITION BY account_reference ORDER BY o.orderdate) as seqnum
FROM dbo.Orders o
) OT JOIN
dbo.Purchaser PT
ON OT.account_reference = PT.account_reference
GROUP BY PT.Email
A couple of notes:
Don't use single quotes for column aliases. Instead, choose column names that do not require escaping.
For the segnum = 1 logic, you can use MIN(), but I think consistency is a benefit here.
EDIT:
My guess is that the problem is the difference between a account_reference and email. Try this:
SELECT email,
MAX(CASE WHEN seqnum = 1 THEN OT.OrderDate END) as OrderDate_1,
MAX(CASE WHEN seqnum = 2 THEN OT.OrderDate END) as OrderDate_2,
MAX(CASE WHEN seqnum = 3 THEN OT.OrderDate END) as OrderDate_3,
COUNT(DISTINCT OT.order_reference) AS TotalNumberOfOrders
FROM (SELECT o.*, ROW_NUMBER() OVER (PARTITION BY pt.email ORDER BY o.orderdate) as seqnum
FROM dbo.Orders o JOIN
dbo.Purchaser PT
ON OT.account_reference = PT.account_reference
) OT
GROUP BY PT.Email

SQL Server 2008 Combine two rows into one

I have written pretty straightforward queries so far, so I am now looking a help to write a SQL statement so that it will combine two separate period end rows from a table into one row. The rows are basically can be matched by their PId, Region, Market, Code, Source. For example-
if 1st row is:
Id Region Market CODE Source Period_End Amt Pct
100 CAN CABLE V1 SA 20120930 100.00 0.2
and 2nd row is:
Id Region Market CODE Source Period_End Amt Pct
100 CAN CABLE V1 SA 20121231 200.00 0.5
Then the SQL should return this result:
Id Region Market CODE Source Period_End_1 Amt_1 Pct_1 Period_End_2 Amt_2 Pct_2
100 CAN CABLE V1 SA 20120930 100.00 0.2 20121231 200.00 0.5
Your help is really appreciated.
Ana.
Thanks for your responses. This is what I started with but I am not sure if I am on right direction or not. I also noticed as I would add more and more information to the row based on Period End then the below query would be too long with redundant "case condition" in each select.
select
A.id , A.region, A.market, A.code, A.source ,
case when period_end = #day_id1 then period_end else '' end as Period_End_1,
case when period_end = #day_id2 then period_end else '' end as Period_End_2,
case when period_end = #day_id1 then Amt else 0.0 end as Amt_1,
case when period_end = #day_id2 then Amt else 0.0 end as Amt_2,
case when period_end = #day_id1 then Pct else 0.0 end as Pct_1,
case when period_end = #day_id2 then pct else 0.0 end as Pct_2,
from
products A with (nolock)
where
A.product_id in (select product_id from #products) -- temp table holding multiple Ids
If I'm understanding your question correctly, you're trying to pivot multiple rows into multiple columns.
Assuming it's always 2 rows you're trying to combine, using the period_end field to order the first from the second, then something like this should work using max with case to pivot your results:
WITH CTE AS (
SELECT *,
Row_Number() Over (Partition By Id, Region, Market, Code, Source
Order By Period_End) rn
FROM YourTable
)
SELECT Id,
Region,
Market,
Code,
Source,
max(case when rn = 1 then Period_End end) Period_End_1,
max(case when rn = 1 then Amt end) Amt_1,
max(case when rn = 1 then Pct end) Pct_1,
max(case when rn = 2 then Period_End end) Period_End_2,
max(case when rn = 2 then Amt end) Amt_2,
max(case when rn = 2 then Pct end) Pct_2
FROM CTE
GROUP BY Id, Region, Market, Code, Source
If you have more potential period_end dates, then you might need to use dynamic sql to achieve your results.
SELECT t1.Id
,t1.Region
,t1.Market
,t1.CODE
,t1.Source
,t1.Period_End AS Period_End_1
,t1.Amt AS Amt_1
,t1.Pct AS Pct_1
,t2.Period_End AS Period_End_2
,t2.Amt AS Amt_2
,t2.Pct AS Pct_2
FROM Table_Name t1
INNER JOIN TABLE_Name t2 ON t1.ID = t2.ID
WHERE t1.ID = 100 AND t1.Period_End <> t2.Period_End