Concatenate row values into single row per group - sql

I have a problem.
Table-1 Name : InvoiceDetail
InvoiceNo StockCode Piece
---------- ----------- ------
1 CP-001 10
1 CP-002 15
2 CP-001 18
2 MN-001 18
Table-2 Name : Stock
StockCode StockName
----------- -----------
CP-001 Computer-A
CP-002 Computer-B
MN-001 Monitor-A
Expected Result
InvoiceNo Description TotalPiece
---------- ----------------------- ----------
1 Computer-A, Computer-B 25
2 Computer-A, Monitor-A 36
I wrote the below query
Query
SELECT InvoiceNo,
(select StockName + '-' from Stock
where StockCode = Results.StockCode
order by StockName
FOR XML PATH('')) AS Description,
SUM(Piece) AS TotalPiece
FROM InvoiceDetail Results
GROUP BY InvoiceNo, Results.StockCode
ORDER BY InvoiceNo
And results
InvoiceNo Description TotalPiece
1 Computer-A- 10
1 Computer-B- 15
2 Computer-A- 18
2 Monitor-A- 18
Whats wrong?

Perform the group by separately in a CTE, and then you can safely concatenate the description as a separate step:
with InvoiceGroupings as (
select t.InvoiceNo,
sum(t.Piece) as TotalPiece
from InvoiceDetail t
group by t.InvoiceNo)
select g.InvoiceNo,
stuff((select ', ' + s.StockName
from InvoiceDetail i
join Stock s
on i.StockCode = s.StockCode
where i.InvoiceNo = g.InvoiceNo
order by s.StockName
for xml path('')),1,2,'') as Description,
g.TotalPiece
from InvoiceGroupings g
order by g.InvoiceNo
SQL Fiddle Demo

Related

How do I get the count for orders with a certain code only when there are multiple codes for the same order possible

Sorry this is my first post - please let me know if something doesn't make sense!
I'm trying to get a count of the number of orders with a specific code XXX ONLY
lets say table A looks something like this
|ORDER ID | ITEM CODE |
123 XXX
123 YYY
123 YYY
456 XXX
456 XXX
456 XXX
789 XXX
000 YYY
what i want in the output is:
order 123 and 000 not to count
and order 456 and 789 to count as 1 each
I only want the count of the unique orders which have item code XXX ONLY
so the count/ output of the final query should be 2
currently what i have is
select order_id, item code, count(order_id) from table a
where item code = 'XXX'
group by order_id, item code
order by count(order_id)
which outputs me the following
ORDER_ID | ITEM CODE | COUNT(ORDER_ID)
123 XXX 1
345 XXX 3
789 XXX 1
This is wrong because I want the output as described above
Thanks in advance!
select order_id
from table_a
group by order_id
having min(item_code) = 'XXX'
and max(item_code) = 'XXX'
Seems like you want this :
select distinct order_id , item_code , 1 as count
from table t1
where not exists (
select 1 from table t2
where t1.order_id = t2.order_id
and t2.item_code <> 'XXX'
)
the count would be always 1 per your question
One option is to use an anti-join. For example:
select distinct t.order_id, 1 as cnt
from table_a t
left join table_a u on u.order_id = t.order_id and u.item_code <> 'XXX'
where t.item_code = 'XXX' and u.order_id is null
Result:
ORDER_ID CNT
--------- ---
789 1
456 1
See running example at db<>fiddle.
EDIT
To get the total count only, tweak the query as shown below:
select count(distinct t.order_id) as cnt
from table_a t
left join table_a u on u.order_id = t.order_id and u.item_code <> 'XXX'
where t.item_code = 'XXX' and u.order_id is null
Result:
CNT
---
2
See running example at db<>fiddle.

Need column values to be dispersed among multiple columns

I have the two tables as below :
SQL> select * from customer;
CUSTOMERID CUSTOMERNAME
---------- --------------
1 A
2 B
SQL> select * from cars;
CARID CUSTOMERID MODEL
---------- ---------- ----------
1 1 toyota
2 1 honda
3 2 suzuki
I need to write an SQL query which would give an output like below :
( Can we use PIVOT or UNPIVOT for this ? I am not sure ! )
CUSTOMERID CUSTOMERNAME Model1 Model2
---------- ------------- ------- --------
1 A toyota honda
2 B suzuki NULL
You can use window function & do conditional aggregation :
select customerid, customername,
max(case when seq = 1 then model end) as model1,
max(case when seq = 2 then model end) as model2
from (select c.*, cr.model,
row_number() over (partition by c.customerid order by cr.MODEL) as seq
from customer c inner join
cars cr
on cr.customerid = c.customerid
) t
group by customerid, customername;
If you have only two values, you can use aggregation:
select cu.customerid, cu.customername,
min(ca.model) as model1,
nullif(max(ca.model), min(ca.model)) as model2
from customers cu join
cars ca
on ca.customerid = cu.customerid
group by cu.customerid, cu.customername;
If you have an indeterminate number, I would recommend aggregating them into a string or array. That syntax depends on the database, but typical syntax is:
select cu.customerid, cu.customername,
listagg(ca.model, ',') within group (order by ca.model) as models
from customers cu join
cars ca
on ca.customerid = cu.customerid
group by cu.customerid, cu.customername;

SQL Join w/ some Math thrown in

Heck, maybe 'joining' isn't even involved. I'm way out of my sql league here. Could someone please help me out w/ the following:
Table A
ItemId ItemLookup Price
------- ---------- -----
1 123456 10.00
2 234567 7.00
3 345678 6.00
Table B
ItemId Location Qty QtyOnHold
------- ---------- ----- ---------
1 1 26 20
2 1 0 0
3 1 12 6
1 2 4 0
2 2 2 1
3 2 16 8
What I'm hoping to get is something that looks like
ItemLookup, Price, (qty minus qtyonhold for loc1), (qty minus qtyonhold for loc2)
or 123456, 10.00, 6, 4
Thank you very much for any direction you can provide.
You can use conditional aggregation and a join:
select a.ItemLookup,
sum(case when Location = 1 then Qty - QtyOnHold end) as Location1,
sum(case when Location = 2 then Qty - QtyOnHold end) as Location2
from tableb b join
tablea a
on b.ItemId = a.ItemId
group by a.ItemLookup;
Somthing like this
select tablea.* ,
(select (qty- QtyOnHold) as qty from tableb where ItemId = tablea.ItemId ans Location = 1 ) as qtyl1,
(select (qty- QtyOnHold) as qty from tableb where ItemId = tablea.ItemId ans Location = 2) as qtyl2
from tablea
This assumes that there's only one row in TableB for each ItemID + Location combination. This is basically just a "pivot", you can learn various ways to do this in MySQL here.
SELECT ItemLookup, Price,
MAX(IF(Location = 1, Qty-QtyOnHold, 0)) avail1,
MAX(IF(Location = 2, Qty-QtyOnHold, 0)) avail2
FROM TableA AS a
JOIN TableB AS b ON a.ItemId = b.ItemId
GROUP BY a.ItemId
It seems to me that it may be possible to have a variable number of locations for each item. If this is the case, you need an aggregate function to convert/concatenate multiple rows into a column.
Here's an example with MySQL's group_concat function:
select a.itemlookup,a.price,group_concat('loc ',location,'=',b.x order by location) as qty_minus_qtyonhold
from tablea a,(select itemid,location,qty-qty_onhold x from tableb
group by itemid,location) as b
where a.itemid = b.itemid
group by 1
You'll get a result like this:
itemlookup price qty_minus_qtyonhold
---------- ------ ------------------
123456 10.00 loc 1=6,loc 2=4
234567 7.00 loc 1=0,loc 2=1
345678 6.00 loc 1=6,loc 2=8
Not sure what DBMS you're using but there are similar alternatives for Oracle and SQL Server

Within the same group find and exclude records that have the same parent ID for certain types

I have a table like following:
GroupID ParentID Type
1 ABC IND
1 ABC IND
1 CDE ORD
1 EFG STD
2 ZZZ IND
2 ZZZ IND
2 ZZZ IND
3 YYY COR
3 YYY COR
I need to exclude those records that are in the same group, having the same parent ID and the type is IND or COR. But I need to keep those groups that have different parent ID and the type is not IND or COR.
So the result I want to get would be the following:
GroupID ParentID Type
1 ABC IND
1 ABC IND
1 CDE ORD
1 EFG STD
Somehow I am thinking to use
Rank () over(partition by GroupID order by ParentID), but it won't give me the results that I want.
Any thoughts? PS: This table has 5 Million+ records. Looking for the effective way to deal with it.
Thanks
The following gives you a list of the groupIDs you want to exclude
SELECT GroupID
FROM
(
SELECT GroupID,
COUNT(DISTINCT ParentID) AS PCount, COUNT(DISTINCT TypeCode) as TCount,
MAX(TypeCode) AS tCode
FROM tablename
GROUP BY GroupID
) t
WHERE PCount = 1 AND TCount = 1
AND (tCode = 'IND' OR tCode = 'COR')
Now select everything else
SELECT *
FROM tableName
WHERE GroupID not in (
SELECT GroupID
FROM
(
SELECT GroupID,
COUNT(DISTINCT ParentID) AS PCount, COUNT(DISTINCT TypeCode) as TCount,
MAX(TypeCode) AS tCode
FROM tablename
GROUP BY GroupID
) t
WHERE PCount = 1 AND TCount = 1
AND (tCode = 'IND' OR tCode = 'COR')
)
Test with fiddle --> http://sqlfiddle.com/#!3/f1d4f/15/0
How is
1 ABC IND
in result set? here type is IND and you mentioned the result set should not have type IND or COR?

Concatenate a single Column value depending on Condition

Below are the 3 tables
QuotationMaster
QuoteID QuoteNo CustomerName
-----------------------------
1 Q1 Name1
2 Q2 Name2
3 Q3 Name3
4 Q4 Name4
5 Q5 Name5
QuoteItemDetails : one quote can have many items
QuoteItemID QuoteID ItemCode ItemID
---------------------------------------------
1 1 100 1
1 1 200 2
2 2 200 2
QuoteBatchDetails : one QuoteItem can have many batches of QuoteID and ItemID are the common columns. BatchNo is varchar
QuotebatchID QuoteID BatchNo ItemID
---------------------------------------------
1 1 A 1
2 1 B 1
3 1 C 2
4 2 E 2
5 2 F 2
I want the result as
QuoteID QuoteNo CustName ItemCode BatchNo
-------------------------------------------------
1 Q1 Name1 100 A,B
1 Q1 Name1 200 C
2 Q2 Name2 200 E,F
I want to create a procedure which takes QuoteID as parameter of INT type and get the result as above.
The only problem I am facing is to concatenate the BatchNo which depends on ItemID and further on QuoteID.
Using the below query I am able to concatenate the BatchNo for a particular ID but I am not sure how to add this to the main procedure, when I do that errors pops up like subquery returns more than one value.I understand because for every quote there can be more than 1 item.
select
ID.QuoteID,ID.ItemID,
stuff((select ', ' + BatchNo
from SD_QuoteBatchDetails BD where ID.ItemID=BD.ItemID and ID.QuoteID=BD.QuoteID
for xml path('')),
1,2,'') [Values]
from SD_QuoteItemDetails QID,SD_QuoteBatchDetails ID where ID.QuoteID=QID.QuoteID
group by ID.ItemID,ID.QuoteID
Can anyone provide a query for the same.
SELECT b.QuoteItemID,
a.QuoteNo,
a.CustomerName,
b.ItemCode,
c.BatchList
FROM QuotationMaster a
INNER JOIN QuoteItemDetails b
ON a.QuoteID = b.QuoteID
INNER JOIN
(
SELECT
QuoteID,
ItemID,
STUFF(
(SELECT ', ' + BatchNo
FROM QuoteBatchDetails
WHERE QuoteID = a.QuoteID AND
ItemID = a.ItemID
FOR XML PATH (''))
, 1, 1, '') AS BatchList
FROM QuoteBatchDetails AS a
GROUP BY QuoteID, ItemID
) c ON b.QuoteID = c.QuoteID AND
b.ItemID = c.ItemID;
SQLFiddle Demo