How to convert two rows value in a single row? - sql

I have below sql server query data
Looking for solution.
SQL Query:
SELECT
p.ProjectName,
i.ItemName,
inv.TransactionDirection,
SUM(inv.TransactionQty) AS TransactionQuantity
FROM INVTransaction inv
JOIN BDProject p ON p.ProjectID=inv.ProjectID
JOIN MDItem i ON i.ItemID=inv.ItemID
GROUP BY p.ProjectName,
i.ItemName,
inv.TransactionDirection

I think you just want conditional aggregation:
SELECT p.ProjectName, i.ItemName,
SUM(CASE WHEN inv.TransactionDirection = 'IN' THEN inv.TransactionQty ELSE 0 END) as IN_Quantity,
SUM(CASE WHEN inv.TransactionDirection = 'OUT' THEN inv.TransactionQty ELSE 0 END) as OUT_Quantity,
SUM(CASE WHEN inv.TransactionDirection = 'IN' THEN inv.TransactionQty
WHEN inv.TransactionDirection = 'OUT' THEN -inv.TransactionQty
ELSE 0
END) as Balance
FROM INVTransaction inv JOIN
BDProject p
ON p.ProjectID = inv.ProjectID JOIN
MDItem i ON i.ItemID = inv.ItemID
GROUP BY p.ProjectName, i.ItemName

You can use Pivot for this. Below is working query.
SELECT PROJECTNAME,ITEMNAME,[IN],[OUT] ,ISNULL([IN],0)-ISNULL([OUT],0) AS BALANCE FROM
(SELECT PROJECTNAME,ITEMNAME,TRANSACTIONDIRECTION,TRANSACTIONQUANTITY FROM TRANSACTIONS
)A
PIVOT (SUM(TRANSACTIONQUANTITY) FOR TRANSACTIONDIRECTION IN ([IN],[OUT])) AS PVT
Replace TRANSACTIONS with your table name

As #Gordon Linoff almost provided solution but for balance column you can replace
SUM(inv.TransactionQty) as Balance
With
SUM(CASE WHEN inv.TransactionDirection = 'IN'
THEN inv.TransactionQty
ELSE -1*inv.TransactionQty END) as Balance

Related

How to group database column?

I need to get a sales Ref. wise report which have several filters. I tried this query to generate the report.
SELECT
sp.SlpCode,
sp.SlpName,
sp.Telephone,
COUNT(od.DocNum) count,
ISNULL((SELECT COUNT(od.U_ArtWork) WHERE od.U_ArtWork = 'NotRec'), 0) Artwork,
ISNULL((SELECT COUNT(od.U_DetailPending) WHERE od.U_DetailPending = 'No'), 0) DetailPrinting
FROM
OSLP sp
LEFT JOIN
ORDR od ON SP.SlpCode = od.SlpCode
WHERE
sp.Telephone IS NOT NULL
GROUP BY
sp.SlpCode, sp.SlpName, sp.Telephone,
od.U_ArtWork, od.U_DetailPending
ORDER BY
sp.SlpName ASC;
The results are like this:
But I need those results like this:
You may try aggregating only on the three columns SlpCode, SlpName, and Telephone:
SELECT
sp.SlpCode,
sp.SlpName,
sp.Telephone,
COUNT(od.DocNum) count,
COUNT(CASE WHEN od.U_ArtWork = 'NotRec' THEN 1 END) AS Artwork,
COUNT(CASE WHEN od.U_DetailPending = 'No' THEN 1 END) AS DetailPrinting
FROM OSLP sp
LEFT JOIN ORDR od
ON SP.SlpCode = od.SlpCode
WHERE
sp.Telephone IS NOT NULL
GROUP BY
sp.SlpCode,
sp.SlpName,
sp.Telephone,
ORDER BY
sp.SlpName;
Remove od.U_ArtWork,od.U_DetailPending from group by - you are using these columns in aggregation so no need to add in group by clause
SELECT
sp.SlpCode,
sp.SlpName,
sp.Telephone,
COUNT(od.DocNum) count,
COUNT(case when od.U_ArtWork = 'NotRec' then od.U_ArtWork end) Artwork,
COUNT(case when od.U_DetailPending='No' then od.U_DetailPending end) DetailPrinting
FROM OSLP sp
LEFT JOIN ORDR od
ON SP.SlpCode = od.SlpCode
WHERE
sp.Telephone IS NOT NULL
GROUP BY
sp.SlpCode,
sp.SlpName,
sp.Telephone
ORDER BY
sp.SlpName ASC

optimize Table Spool in SQL Server Execution plan

I have the following sql query and trying to optimize it using execution plan. In execution plan it says Estimated subtree cost is 36.89. There are several table spools(Eager Spool). can anyone help me to optimize this query. Thanks in advance.
SELECT
COUNT(DISTINCT bp.P_ID) AS total,
COUNT(DISTINCT CASE WHEN bc.Description != 'S' THEN bp.P_ID END) AS m_count,
COUNT(DISTINCT CASE WHEN bc.Description = 'S' THEN bp.P_ID END) AS s_count,
COUNT(DISTINCT CASE WHEN bc.Description IS NULL THEN bp.P_ID END) AS n_count
FROM
progress_tbl AS progress
INNER JOIN Person_tbl AS bp ON bp.P_ID = progress.person_id
LEFT OUTER JOIN Status_tbl AS bm ON bm.MS_ID = bp.MembershipStatusID
LEFT OUTER JOIN Membership_tbl AS m ON m.M_ID = bp.CurrentMembershipID
LEFT OUTER JOIN Category_tbl AS bc ON bc.MC_ID = m.MembershipCategoryID
WHERE
logged_when BETWEEN '2017-01-01' AND '2017-01-31'
Here's a technique you can use.
WITH T AS
(
SELECT DISTINCT CASE
WHEN bc.Description != 'S' THEN 'M'
WHEN bc.Description = 'S' THEN 'S'
WHEN bc.Description IS NULL THEN 'N'
END AS type,
bp.P_ID
FROM progress_tbl AS progress
INNER JOIN Person_tbl AS bp
ON bp.P_ID = progress.person_id
LEFT OUTER JOIN Status_tbl AS bm
ON bm.MS_ID = bp.MembershipStatusID
LEFT OUTER JOIN Membership_tbl AS m
ON m.M_ID = bp.CurrentMembershipID
LEFT OUTER JOIN Category_tbl AS bc
ON bc.MC_ID = m.MembershipCategoryID
WHERE logged_when BETWEEN '2017-01-01' AND '2017-01-31'
)
SELECT COUNT(DISTINCT P_ID) AS total,
COUNT(CASE WHEN type= 'M' THEN P_ID END) AS m_count,
COUNT(CASE WHEN type= 'S' THEN P_ID END) AS s_count,
COUNT(CASE WHEN type= 'N' THEN P_ID END) AS n_count
FROM T
I will demonstrate it on a simpler example.
Suppose your existing query is
SELECT
COUNT(DISTINCT number) AS total,
COUNT(DISTINCT CASE WHEN name != 'S' THEN number END) AS m_count,
COUNT(DISTINCT CASE WHEN name = 'S' THEN number END) AS s_count,
COUNT(DISTINCT CASE WHEN name IS NULL THEN number END) AS n_count
FROM master..spt_values;
You can rewrite it as follows
WITH T AS
(
SELECT DISTINCT CASE
WHEN name != 'S'
THEN 'M'
WHEN name = 'S'
THEN 'S'
ELSE 'N'
END AS type,
number
FROM master..spt_values
)
SELECT COUNT(DISTINCT number) AS total,
COUNT(CASE WHEN type= 'M' THEN number END) AS m_count,
COUNT(CASE WHEN type= 'S' THEN number END) AS s_count,
COUNT(CASE WHEN type= 'N' THEN number END) AS n_count
FROM T
Note the rewrite is costed as considerably cheaper and the plan is much simpler.
As already pointed out, there seems to be some typo/copy paste issues with your query. This makes it rather difficult for us to figure out what's going on.
The table-spools probably are what's going on in the CASE WHEN b.description etc... constructions. MSSQL first creates a (memory) table with all the resulting values and then that one gets sorted and streamed through the COUNT(DISTINCT ...) operator. I don't think there is much you can do about that as the work needs to be done somewhere.
Anyway, some remarks and wild guesses:
I'm guessing that logged_when is in the progress_tbl table?
If so, do you really need to LEFT OUTER JOIN all the other tables? From what I can tell they aren't being used?
You're trying to count the number of P_IDs that match the criteria and you want to split up that number between those that have b.Description either 'S', something else, or NULL.
for this you could calculate the total as the sum of the m_count, s_count and n_count. This would save you 1 COUNT() operation, not sure it helps a lot in the bigger picture but all bits help I guess.
Something like this:
;WITH counts AS (
SELECT
COUNT(DISTINCT CASE WHEN b.Description != 'S' THEN b_p.P_ID END) AS m_count,
COUNT(DISTINCT CASE WHEN b.Description = 'S' THEN b_p.P_ID END) AS s_count,
COUNT(DISTINCT CASE WHEN b.Description IS NULL THEN b_p.P_ID END) AS n_count
FROM
progress_tbl AS progress
INNER JOIN Person_tbl AS bp ON bp.P_ID = progress.person_id
LEFT OUTER JOIN Status_tbl AS bm ON bm.MS_ID = bp.MembershipStatusID -- really needed?
LEFT OUTER JOIN Membership_tbl AS m ON m.M_ID = bp.CurrentMembershipID -- really needed?
LEFT OUTER JOIN Category_tbl AS bc ON bc.MC_ID = m.MembershipCategoryID -- really needed?
WHERE
logged_when BETWEEN '2017-01-01' AND '2017-01-31' -- what table does logged_when column come from????
)
SELECT total = m_count + s_count + n_count,
*
FROM counts
UPDATE
BEWARE: Using the answer/example code of Martin Smith I came to realize that total isn't necessarily the sum of the other fields. It could be a given P_ID shows up with different description which then might fall into different categories. Depending on your data it might thus be that my answer is plain wrong.

SQL Server 2008 case statement to count records and not another record with same date but different result

I need to count the number of id's where the Name and Eventname can have either 'Accept' or 'reject' for a value on the same date. How can i write a case statement to only use the 'Accept' value? i have tried many ways and still counts them all. Still counts the 'reject' records.
Any help is greatly appreciated.
example below:
select th.NAME,
COUNT(distinct th.HICN_CD) AS HICN_COUNT,
case th.EVENTNAME
when 'accept' then 'Accept'
when 'reject' then 'Reject' ELSE 'N/A'
END AS Event_Name
from #temp_hicn th
left join #temp_maxdate tm
on tm.HICN_CD = th.HICN_CD
group by th.NAME,
th.EVENTNAME
order by th.NAME ;
It sounds like you want conditional aggregation:
select th.NAME, COUNT(distinct th.HICN_CD) AS HICN_COUNT ,
sum(case th.EVENTNAME when 'accept' then 1 ELSE 0 END) AS NumAccept
from #temp_hicn th left join
#temp_maxdate tm
on tm.HICN_CD = th.HICN_CD
group by th.NAME
order by th.NAME;
If you want to count distinct value of HICN_CD for accepts:
select th.NAME,
COUNT(distinct case th.EVENTNAME when 'accept' then th.HICN_CD end) AS HICN_COUNT ,
sum(case th.EVENTNAME when 'accept' then 1 ELSE 0 END) AS NumAccept
from #temp_hicn th left join
#temp_maxdate tm
on tm.HICN_CD = th.HICN_CD
group by th.NAME
order by th.NAME;
SELECT DISTINCT TH.HICN_CD, TH.NAME, TH.EVENTNAME, TM.MAX_DATE
INTO #TEMP_ACCEPT
FROM #TEMP_HICN TH
LEFT JOIN #TEMP_MAXDATE TM
ON TM.HICN_CD = TH.HICN_CD
AND TM.MAX_DATE = TH.MAX_DATEID
GROUP BY TH.MEDICARE_HICN_CD, TH.NAME, TH.EVENTNAME, TM.MAX_DATE;
SELECT DISTINCT TH.MEDICARE_HICN_CD, TH.NAME, TH.EVENTNAME, TM.MAX_DATE
INTO #TEMP_REJECT
FROM #TEMP_HICN TH
LEFT JOIN #TEMP_MAXDATE TM
ON TM.HICN_CD = TH.HICN_CD
AND TM.MAX_DATE = TH.MAX_DATEID
WHERE TH.EVENTNAME = 'REJECT'
AND TH.HICN_CD + TH.NAME NOT IN (SELECT HICN_CD + NAME FROM #TEMP_ACCEPT)
GROUP BY TH.HICN_CD, TH.NAME, TH.EVENTNAME, TM.MAX_DATE;
SELECT *
INTO #TEMP_EVENT
FROM #TEMP_ACCEPT
UNION
SELECT *
FROM #TEMP_REJECT;
WITH EVENT_DUP AS (
SELECT DISTINCT HICN_CD, NAME, COUNT() AS 'EVENTS'
FROM #TEMP_EVENT
GROUP BY HICN_CD, NAME
HAVING COUNT() > 1
)
DELETE FROM #TEMP_EVENT
WHERE HICN_CD + NAME IN (SELECT HICN_CD + NAME FROM EVENT_DUP)
AND MAX_DATE IS NULL;
SELECT EV.NAME, EV.EVENTNAME ,COUNT(DISTINCT EV.HICN_CD) AS HICN_COUNT
FROM #TEMP_EVENT EV
LEFT JOIN #TEMP_MAXDATE TM
ON TM.HICN_CD = EV.HICN_CD
GROUP BY EV.NAME, EV.EVENTNAME
ORDER BY EV.NAME;

Joined SQL query MAX aggregate with condition

I am having trouble with a SQL query. Here is a representation of my schema on SQL Fiddle:
http://sqlfiddle.com/#!15/14c8e/1
The issue is that I want to return rows of data from the Invitations table and join them with a sum of both the 'sent' event_type and 'viewed' event_type from the associated events, as well as the latest created_at date.
I can get all the data and counts working, but am having issue with the last_sent_on. Is there a way I can use a condition in a MAX aggregate function?
e.g.
MAX(
SELECT events.created_at
WHERE event_type='sent'
)
If not, how would I write the proper subselect?
I am currently using Postgresql.
Thank you.
You can use a case statement inside of max just as you've done with sum. The query below will select the maximum created_at for event_type='sent'
SELECT
i.id,
i.name,
i.email,
max(case when e.event_type='sent' then e.created_at end) AS last_sent_on,
sum(case when e.event_type='sent' then 1 else 0 end) AS sent_count,
sum(case when e.event_type='viewed' then 1 else 0 end) AS view_count
FROM
invitations i
LEFT OUTER JOIN
events e
ON e.eventable_id = i.id
WHERE e.eventable_type='Invitation'
GROUP BY i.id, i.name, i.email
SQLFiddle
Try using a subquery to build the max value for sent.
SELECT
i.id,
i.name,
i.email,
sent.last_sent,
sum(case when e.event_type='sent' then 1 else 0 end) AS sent_count,
sum(case when e.event_type='viewed' then 1 else 0 end) AS view_count
FROM
invitations i
LEFT OUTER JOIN
events e
ON e.eventable_id = i.id
LEFT JOIN ( SELECT eventable_id uid, MAX(created_at) AS last_sent
FROM events
WHERE event_type = 'sent'
GROUP BY eventable_id ) AS sent
ON sent.uid = i.id
WHERE e.eventable_type='Invitation'
GROUP BY i.id, i.name, i.email, sent.last_sent

How can I rewrite this query without repeating SELECT subqueries

I have written a query like this. It is working and giving me the result which I wanted.
I have a SELECT query to get CAR_AMOUNT and HOTEL_AMOUNT.
But, I have to repeat the same SELECT query to get the SUM of both. I am not able to use alias name. How can I avoid this?
SELECT B.EMPLOYEE_ID,
(select SUM(AMOUNT)
FROM travel_reimbursements_items
WHERE type = 'CAR'
AND travel_request_no = B.travel_request_no
AND STATUS='APPROVED') as CAR_AMOUNT,
(select SUM(AMOUNT)
FROM travel_reimbursements_items
WHERE type = 'HOTEL'
AND travel_request_no = B.travel_request_no
AND STATUS='APPROVED') as HOTEL_AMOUNT,
NVL((select SUM(AMOUNT)
FROM travel_reimbursements_items
WHERE type = 'CAR'
AND travel_request_no = B.travel_request_no
AND STATUS='APPROVED'),0)
+NVL((select SUM(AMOUNT)
FROM travel_reimbursements_items
WHERE type = 'HOTEL'
AND travel_request_no = B.travel_request_no
AND STATUS='APPROVED'),0) as TOTAL
FROM TRAVEL_REQUEST_ITEM A
LEFT OUTER JOIN TRAVEL_REQUEST B
ON (B.TRAVEL_REQUEST_NO= A.TRAVEL_REQUEST_SR_NO)
select b.employee_id
, sum(case when c.type = 'CAR' then c.amount end) car_amount
, sum(case when c.type = 'HOTEL' then c.amount end) hotel_amount
, sum(c.amount) total
from travel_request_item a
left outer join travel_request b
on (b.travel_request_no= a.travel_request_sr_no)
left outer join travel_reimbursements_items c
on ( c.travel_request_no = b.travel_request_no
and c.type in ('CAR','HOTEL')
and c.status = 'APPROVED'
)
group by b.employee_id
Regards,
Rob.
This should work in oracle.
SELECT B.EMPLOYEE_ID,
SUM(CASE WHEN C.type = 'CAR' and C.STATUS='APPROVED' THEN C.AMOUNT ELSE 0 END) as CAR_AMOUNT,
SUM(CASE WHEN C.type = 'HOTEL' and C.STATUS='APPROVED' THEN C.AMOUNT ELSE 0 END) as HOTEL_AMOUNT,
SUM(CASE WHEN C.type IN ('CAR', 'HOTEL') and C.STATUS='APPROVED' THEN C.AMOUNT ELSE 0 END) as TOTAL
FROM TRAVEL_REQUEST_ITEM A
LEFT OUTER JOIN TRAVEL_REQUEST B ON (B.TRAVEL_REQUEST_NO= A.TRAVEL_REQUEST_SR_NO)
LEFT OUTER JOIN TRAVEL_REIMBURSEMENTS_ITEMS C ON (C.TRAVEL_REQUEST_NO = A.TRAVEL_REQUEST_SR_NO)
GROUP BY B.EMPLOYEE_ID
Just do a conditional sum based on the item type.