SQL - Multiple Values comma separated when using GROUP BY [duplicate] - sql

This question already has answers here:
How can I combine multiple rows into a comma-delimited list in Oracle? [duplicate]
(11 answers)
Closed 8 years ago.
I have data that looks like
CUSTOMER, CUSTOMER_ID, PRODUCT
ABC INC 1 XYX
ABC INC 1 ZZZ
DEF CO 2 XYX
DEF CO 2 ZZZ
DEF CO 2 WWW
GHI LLC 3 ZYX
I'd like to write a query that'd make the data look like this:
CUSTOMER, CUSTOMER_ID, PRODUCTS
ABC INC 1 XYX, ZZZ
DEF CO 2 XYX, ZZZ, WWW
GHI LLC 3 ZYX
Using Oracle 10g if helps. I saw something that would work using MYSQL, but I need a plain SQL or ORACLE equivalent. I've also seen examples of stored procs that could be made, however, I cannot use a stored proc with the product i'm using.
Here's how'd it work in MySQL if I were using it
SELECT CUSTOMER,
CUSTOMER_ID,
GROUP_CONCAT( PRODUCT )
FROM MAGIC_TABLE
GROUP BY CUSTOMER, CUSTOMER_ID
Thank you.

I think LISTAGG is the best aggregate group by function to use in this situation:
SELECT CUSTOMER, CUSTOMER_ID,
LISTAGG(PRODUCT, ', ') WITHIN GROUP (ORDER BY PRODUCT)
FROM SOME_TABLE
GROUP BY CUSTOMER, CUSTOMER_ID
ORDER BY 1, 2

This link refers to a number of examples of different ways to do this on Oracle. See if there's something there that you have permissions on your database to do.

The oracle user function 'wm_concat' works the same way as LISTAGG except you cannot specify a delimiter ',' by default or a sort order. It is however compatible with 10g.

Thanks Nigel,
My SQL is not as elegant as could be, but I needed a solution that required SQL only, not PLSQL or TSQL, so it ended up looking like this:
SELECT CUSTOMER, CUSTOMER_ID, COUNT(PRODUCT) PROD_COUNT,
RTRIM(
XMLAGG( XMLELEMENT (C, PRODUCT || ',') ORDER BY PRODUCT
).EXTRACT ('//text()'), ','
) AS PRODUCTS FROM (
SELECT DISTINCT CUSTOMER, CUSTOMER_ID, PRODUCT
FROM MAGIC_TABLE
) GROUP BY CUSTOMER, CUSTOMER_ID ORDER BY 1 , 2
Still not exactly sure what the XML functions do exactly, but I'll dig in when the need arrises.

Related

SQL - Count instances in 1 field with variations in 2nd

Thanks for taking a second to read this... Have tried this in SQL, Python, and VBA with no luck (various reasons).
The data and concept are, I think, pretty simple - but I can't seem to make it work.
Column 1 has a stock market ticker; Column 2 has the company name. However, many of the company names have been truncated, or have changed over time. I want to find every instance of each ticker, where that ticker has more than 1 name.
So for example, my file has these 4 lines
IBM | Int Bus Mach
IBM | International Business M
IBM | Intl Bus Machines
IBM | Int Bus Mach
I would like to see the 3 unique company names
IBM | Int Bus Mach
IBM | International Business M
IBM | Intl Bus Machines
Any ideas are certainly appreciated!
Thanks!
According to your data examples, you should do something like this:
SELECT
market_ticker,
company_name,
count(*)
FROM yourTable
GROUP BY
market_ticker,
company_name
The extra column will give how many times the market ticker and company name repeat.
2 step Sql Solution here.
I would do the following
Simple distinct query to get all ticker and company combos then pivot
select *,
ROW_NUMBER() OVER(PARTITION BY TickerColumn ORDER BY NameColumn) AS NameNum
into #Temp
From
(
Select distinct TickerColumn, NameColumn
from Table
)x
Then Pivot ColumnNames
Select TickerColumn, [1],[2],[3],[4]
From
(select * from #temp) as Source
Pivot
(
Max(NameColumn)
For NameNum in ([1],[2],[3],[4]) ---You can add or reduce number of columns
) As PivotTable;
May be try something like this. Would bring distinct names.
WITH cte
AS (
SELECT market_ticker
,Company_name
FROM Stocks
)
SELECT a.market_ticker
,b.Company_name
FROM cte a
INNER JOIN stocks b
ON a.market_ticker = b.market_ticker
GROUP BY a.market_ticker
,b.company_name
HAVING count(a.company_name) >= 2
ORDER BY 1
,2

adding columns and returning consolidated data

Could someone assist me with writing SQL query to calculate/display the following example:
table - shipment
columns - product code / qty / unique code
sku a | 5 | nnnn
sku a | 5 | nn
sku a | 10 | (blank)
sku b | 2 | nnn
sku c | 2 | (blank)
sku c | 2 | (blank)
I'm looking for an output like this:
columns - product code / qty / unique code
sku a | 20 | nnnn, nn
sku b | 2 | nnn
sku c | 4 | (blank)
LISTAGG is your saviour here.
SELECT
product_code,
SUM(qty) as total_qty,
LISTAGG(unique_code, ',') WITHIN GROUP (ORDER BY unique_code)
FROM
shipment
GROUP BY
product_code
EDIT:
Putting the answer here for the better code output:
You have "i." in front of the fields in your listagg, but you have no table aliases. Also, you need to add pallet_id to the group by. Try this
SELECT
reference_id,
pallet_id,
SUM(update_qty) as total_qty,
LISTAGG(user_def_type_1, ',') WITHIN GROUP (ORDER BY user_def_type_1)
FROM
inventory_transaction
WHERE
code = 'Shipment' AND site_id = 'GBRUN2A' AND client_id = '021' AND dstamp >= current_date -21
GROUP BY
reference_id, pallet_id
If you are still getting an error, can you confirm you are on Oracle? I'm pretty sure it's an Oracle-only function.
Yes - Using Oracle Sql Developer version 4.0.1.14
ETA: Can you confirm which version of Oracle Database you are running. Listagg is only on Oracle 12c and Oracle 11g Release 2. If you are running a previous version, have a look here for some alternate ideas.
If you're using a version of Oracle that doesn't support LISTAGG() (e.g., Oracle 10g), then there are a couple of things you can do. The easiest is to use the undocumented WM_CONCAT() function (which returns either a VARCHAR or a CLOB depending on the release):
SELECT reference_id, pallet_id, SUM(update_qty) as total qty
, WM_CONCAT(user_def_type_1)
FROM inventory_transaction
GROUP BY reference_id
One difficulty with using WM_CONCAT() is that the results of the concatenation won't be ordered. You also have no choice about your delimiter. Another option, probably a better one, is to use XMLAGG() (this is actually a documented function):
SELECT reference_id, pallet_id, SUM(update_qty) as total qty
, XMLAGG(XMLELEMENT(e, user_def_type_1 || ',')).EXTRACT('//text()')
FROM inventory_transaction
GROUP BY reference_id;
Here you have your choice of delimiters, and XMLAGG() supports an ORDER BY clause:
SELECT reference_id, pallet_id, SUM(update_qty) as total qty
, XMLAGG(XMLELEMENT(e, user_def_type_1 || ',') ORDER BY user_def_type_1).EXTRACT('//text()')
FROM inventory_transaction
GROUP BY reference_id;
You can find other options at this Stack Overflow question.

How to concatenate rows delimited with comma using standard SQL?

Let's suppose we have a table T1 and a table T2. There is a relation of 1:n between T1 and T2. I would like to select all T1 along with all their T2, every row corresponding to T1 records with T2 values concatenated, using only SQL-standard operations.
Example:
T1 = Person
T2 = Popularity (by year)
for each year a person has a certain popularity
I would like to write a selection using SQL-standard operations, resulting something like this:
Person.Name Popularity.Value
John Smith 1.2,5,4.2
John Doe NULL
Jane Smith 8
where there are 3 records in the popularity table for John Smith, none for John Doe and one for Jane Smith, their values being the values represented above. Is this possible? How?
I'm using Oracle but would like to do this using only standard SQL.
Here's one technique, using recursive Common Table Expressions. Unfortunately, I'm not confident on its performance.
I'm sure that there are ways to improve this code, but it shows that there doesn't seem to be an easy way to do something like this using just the SQL standard.
As far as I can see, there really should be some kind of STRINGJOIN aggregate function that would be used with GROUP BY. That would make things like this much easier...
This query assumes that there is some kind of PersonID that joins the two relations, but the Name would work too.
WITH cte (id, Name, Value, ValueCount) AS (
SELECT id,
Name,
CAST(Value AS VARCHAR(MAX)) AS Value,
1 AS ValueCount
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Name) AS id,
Name,
Value
FROM Person AS per
INNER JOIN Popularity AS pop
ON per.PersonID = pop.PersonID
) AS e
WHERE id = 1
UNION ALL
SELECT e.id,
e.Name,
cte.Value + ',' + CAST(e.Value AS VARCHAR(MAX)) AS Value,
cte.ValueCount + 1 AS ValueCount
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Name) AS id,
Name,
Value
FROM Person AS per
INNER JOIN Popularity AS pop
ON per.PersonID = pop.PersonID
) AS e
INNER JOIN cte
ON e.id = cte.id + 1
AND e.Name = cte.Name
)
SELECT p.Name, agg.Value
FROM Person p
LEFT JOIN (
SELECT Name, Value
FROM (
SELECT Name,
Value,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY ValueCount DESC)AS id
FROM cte
) AS p
WHERE id = 1
) AS agg
ON p.Name = agg.Name
This is an example result:
--------------------------------
| Name | Value |
--------------------------------
| John Smith | 1.2,5,4.2 |
--------------------------------
| John Doe | NULL |
--------------------------------
| Jane Smith | 8 |
--------------------------------
As per in Oracle you can use listagg to achive this -
select t1.Person_Name, listagg(t2.Popularity_Value)
within group(order by t2.Popularity_Value)
from t1, t2
where t1.Person_Name = t2.Person_Name (+)
group by t1.Person_Name
I hope this will solve your problem.
But the comment you have given after #DavidJashi question .. well this is not sql standard and I think he is correct. I am also with David that you can not achieve this in pure sql statement.
I know that I'm SUPER late to the party, but for anyone else that might find this, I don't believe that this is possible using pure SQL92. As I discovered in the last few months fighting with NetSuite to try to figure out what Oracle methods I can and cannot use with their ODBC driver, I discovered that they only "support and guarantee" SQL92 standard.
I discovered this, because I had a need to perform a LISTAGG(). Once I found out I was restricted to SQL92, I did some digging through the historical records, and LISTAGG() and recursive queries (common table expressions) are NOT supported in SQL92, at all.
LISTAGG() was added in Oracle SQL version 11g Release 2 (2009 – 11 years ago: reference https://oracle-base.com/articles/misc/string-aggregation-techniques#listagg) , CTEs were added to Oracle SQL in version 9.2 (2007 – 13 years ago: reference https://www.databasestar.com/sql-cte-with/).
VERY frustrating that it's completely impossible to accomplish this kind of effect in pure SQL92, so I had to solve the problem in my C# code after I pulled a ton of extra unnecessary data. Very frustrating.

SQL Server - Possible Pivot Solution?

I have a simple enough issue that has been surprisingly difficult to locate online. Perhaps I am searching on improper keywords so I wanted to stop in and ask you guys because your site has been a blessing with my studies. See below scenario:
Select student, count(*) as Total, (the unknown variable: book1, book2, book3, book4, ect...) from mystudies.
Essentially all I would like to do is list out all books for a unique student id that matches the Total count. Could someone point me in the right direction, a good read or anything, so I can get a step going in the correct direction? I am assuming it would be done via a left join (not sure how to do the x1, x2, x3 part) and then just link the two by the unique student id number (no duplicates) but everyone online points to pivot but pivot appears to put all the rows into columns instead of one single column. SQL server 2005 is the platform of choice.
Thanks!
Sorry
The following query produces my unique id (the student) and the student's count for all duplicate entries in the table:
select student, count(*) as Total
from mystudies
group by student order by total desc
the part I don't know is how to create the left join on the table unique id (boookid)
select mystudies1.student, mystudies1.total, mystudies2.bookid
from ( select student, count(*) as Total
from mystudies
group by student
) mystudies1
left join
( select student, bookid
from mystudies
) mystudies2
on mystudies1.student=mystudies2.student
order by mystudies1.total desc, mystudies1.student asc
Obviously the above row will produce results similar to the following:
Student Total BookID
000001 3 100001
000001 3 100002
000001 3 100003
000002 2 200001
000002 2 200002
000003 1 300001
But what I actually want is something similar to the following:
Student Total BookID
000001 3 100001, 100002, 100003
000002 2 200001, 200002
000003 1 300001
I assumed it had to be done in a left join so that it didn't alter the actual count being performed on the student. thanks!
In SQL-Server use the FOR XML Path Method:
SELECT Student,
Total,
STUFF(( SELECT ', ' + BookID
FROM MyStudies books
WHERE Books.Student = MyStudies.Student
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)'), 1, 2, '') AS Books
FROM ( SELECT Student, COUNT(*) AS Total
FROM myStudies
GROUP BY Student
) MyStudies
I have previously given a full explanation of how the XML PATH Method works here. With a further improvement to my answer pointed out here
SQL Server Fiddle
In MySQL AND SQLite you can use the GROUP_CONCAT function:
SELECT Student,
COUNT(*) AS Total,
GROUP_CONCAT(BookID) AS Books
FROM myStudies
GROUP BY Student
MySQL Fiddle
SQLite Fiddle
In Postgresql you can use the ARRAY_AGG Function:
SELECT Student,
COUNT(*) AS Total,
ARRAY_AGG(BookID) AS Books
FROM myStudies
GROUP BY Student
Postgresql Fiddle
In oracle you can use the LISTAGG Function
SELECT Student,
COUNT(*) AS Total,
LISTAGG(BookID, ', ') WITHIN GROUP (ORDER BY BookID) AS Books
FROM myStudies
GROUP BY Student
Oracle SQL Fiddle

count without group

I have one table named GUYS(ID,NAME,PHONE) and i need to add a count of how many guys have the same name and at the same time show all of them so i can't group them.
example:
ID NAME PHONE
1 John 335
2 Harry 444
3 James 367
4 John 742
5 John 654
the wanted output should be
ID NAME PHONE COUNT
1 John 335 3
2 Harry 444 1
3 James 367 1
4 John 742 3
5 John 654 3
how could i do that? i only manage to get lot of guys with different counts.
thanks
Update for 8.0+: This answer was written well before MySQL version 8, which introduced window functions with mostly the same syntax as the existing ones in Oracle.
In this new syntax, the solution would be
SELECT
t.name,
t.phone,
COUNT('x') OVER (PARTITION BY t.name) AS namecounter
FROM
Guys t
The answer below still works on newer versions as well, and in this particular case is just as simple, but depending on the circumstances, these window functions are way easier to use.
Older versions: Since MySQL, until version 8, didn't have analytical functions like Oracle, you'd have to resort to a sub-query.
Don't use GROUP BY, use a sub-select to count the number of guys with the same name:
SELECT
t.name,
t.phone,
(SELECT COUNT('x') FROM Guys ct
WHERE ct.name = t.name) as namecounter
FROM
Guys t
You'd think that running a sub-select for every row would be slow, but if you've got proper indexes, MySQL will optimize this query and you'll see that it runs just fine.
In this example, you should have an index on Guys.name. If you have multiple columns in the where clause of the subquery, the query would probably benefit from a single combined index on all of those columns.
Use an aggregate Query:
select g.ID, g.Name, g.Phone, count(*) over ( partition by g.name ) as Count
from
Guys g;
You can still use a GROUP BY for the count, you just need to JOIN it back to your original table to get all the records, like this:
select g.ID, g.Name, g.Phone, gc.Count
from Guys g
inner join (
select Name, count(*) as Count
from Guys
group by Name
) gc on g.Name = gc.Name
In Oracle DB you can use
SELECT ID,NAME,PHONE,(Select COUNT(ID)From GUYS GROUP BY Name)
FROM GUYS ;
DECLARE #tbl table
(ID int,NAME varchar(20), PHONE int)
insert into #tbl
select
1 ,'John', 335
union all
select
2 ,'Harry', 444
union all
select
3 ,'James', 367
union all
select
4 ,'John', 742
union all
select
5 ,'John', 654
SELECT
ID
, Name
, Phone
, count(*) over(partition by Name)
FROM #tbl
ORDER BY ID
select id, name, phone,(select count(name) from users u1 where u1.name=u2.name) count from users u2
try
select column1, count(1) over ()
it should help