Use STUFF with INNER JOIN Query - sql

I have three tables in my database. A table for Product, a table for Types and a mapping table named Prod_Type. My database is sql server that's why I cant use the group_concat function and I am using the Stuff function.My table structures were as follows
ProductTable
Prod_ID | Name | Brand
------- ---- -----
1 | Name1 | Brand1
2 | Name2 | Brand2
3 | Name3 | Brand3
4 | Name4 | Brand4
5 | Name5 | Brand5
6 | Name6 | Brand6
7 | Name7 | Brand7
TypeTable
Type_ID | TypeName
------- --------
1 | TypeName1
2 | TypeName2
3 | TypeName3
4 | TypeName4
5 | TypeName5
Prod_TypeTable
Prod_IDM | Type_ID
-------- -------
1 | 1
1 | 3
1 | 4
1 | 5
2 | 2
2 | 3
3 | 4
4 | 5
4 | 1
5 | 4
5 | 3
5 | 2
6 | 2
6 | 3
7 | 5
I was able to join the table of product to the mapping table of Prod_type. I used stuff query to avoid multiple results. My query was something like this:
Select
top 5 *
from ProductTable
inner Join (SELECT
Prod_IDM,
STUFF((SELECT ', ' + CAST(Type_ID AS VARCHAR(10)) [text()]
FROM Prod_TypeTable
WHERE Prod_IDM = t.Prod_IDM FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') TypeID
FROM Prod_TypeTable t
GROUP BY Prod_IDM ) As TypeList on TypeList.Prod_IDM = ProductTable.Prod_ID
What I need to do now is to join my previous query result to type table in able to get the names of the types respectively. How can I possibly do that?My expected output would be like this
Prod_ID | Name | TypeName
------- ---- ---------
1 | Name1 | TypeName1, TypeName3, TypeName4, TypeName5
2 | Name2 | TypeName2, TypeName3
3 | Name3 | TypeName4
4 | Name4 | TypeName5, TypeName1
5 | Name5 | TypeName4, TypeName3, TypeName2
6 | Name6 | TypeName2, TypeName3
7 | Name7 | TypeName5

Group concatenation queries can be difficult to phrase in SQL Server, at least for earlier versions which do not have a STRING_AGG function. The trick is that the outer query should act on the table whose keys have values you want to aggregate from joining to one or more other tables. In this case, we put ProductTable on the outside, and then aggregate over everything else, to generate a CSV list of types for each product.
SELECT
p.Prod_ID,
p.Name,
TypeName = STUFF((
SELECT ',' + t.TypeName
FROM Prod_TypeTable pt
INNER JOIN TypeTable t
ON pt.Type_ID = t.Type_ID
WHERE pt.Prod_IDM = p.Prod_ID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM ProductTable p
ORDER BY p.Prod_ID;
Demo

If you are using SQL Server 2017+, I recommend using STRING_AGG as mentioned by #tim-biegeleisen. STRING_AGG concatenates the values of string expressions and places separator values between them (not added at the end of the string).
SELECT
p.Prod_ID,
p.Name,
TypeName = (
SELECT STRING_AGG ( t.TypeName, ',')
FROM Prod_TypeTable pt
INNER JOIN TypeTable t
ON pt.Type_ID = t.Type_ID
WHERE pt.Prod_IDM = p.Prod_ID
)
FROM ProductTable p
ORDER BY p.Prod_ID;
To know more about concatenation of queries in SQL Server, I have written a blog at the link below. https://blog.vcillusion.co.in/understanding-the-grouped-concatenation-sql-server/

Related

SQL Server 2014 STUFF with 2 Inner Join

My question really similar to this Use STUFF with INNER JOIN Query, but I've been trying what if the first column is a varchar type, currently this is my table. As for note I'm using SQL Server 2014
Table Customer
Cust_ID | Cust_Name
--------+---------
1 | Name1
2 | Name2
Table Order
Order_ID | Order_NO | Cust_ID
---------+----------+---------
1 | No.001 | 1
2 | No.002 | 2
Table Item
Item_ID | Order_ID | Item_Name | Quantity | Price
--------+----------+-----------+----------+-------
1 | 1 | A | 1 | 10
2 | 1 | B | 1 | 20
3 | 1 | C | 1 | 30
4 | 2 | D | 1 | 40
A few queries that I've tried:
SELECT
TBL_Sales_SO.SO_NO AS int, Tbl_Customer.Customer_Name,
ITEMS = STUFF ((SELECT DISTINCT ',' + TBL_SO_LIITEM.Item_Name
FROM Tbl_Customer
INNER JOIN TBL_Sales_SO ON Tbl_Customer.Com_Customer_ID = TBL_Sales_SO.Com_Customer_ID
INNER JOIN TBL_SO_LIITEM ON TBL_Sales_SO.Sales_SO_ID = TBL_SO_LIITEM.Sales_So_ID
WHERE TBL_Sales_SO.SO_NO = TBL_Sales_SO.Sales_SO_ID
FOR XML PATH('')), 1, 1,'')
FROM
TBL_Sales_SO, Tbl_Customer
ORDER BY
SO_NO
and the query that for me I think almost hit
SELECT
TBL_Sales_SO.SO_NO, Tbl_Customer.Customer_Name, TBL_SO_LIITEM.Item_Name,
TBL_SO_LIITEM.Quantity, TBL_SO_LIITEM.Price
FROM
((Tbl_Customer
INNER JOIN
TBL_Sales_SO on Tbl_Customer.Com_Customer_ID = TBL_Sales_SO.Com_Customer_ID)
INNER JOIN
TBL_SO_LIITEM on TBL_Sales_SO.Com_Customer_ID = TBL_SO_LIITEM.Sales_So_ID)
While tinkering the first code give me some various error, the second one im just not sure how make possible something like my target
Order_No | Name | Item_Name | Quantity | Price
no.001 | Name1 | A,B,C | 3 | 60
no.002 | Name2 | D | 1 | 40
UPDATE
Following Gordon Answer, i tinkering the code again, manage to the following table
Order_No| Name | Item_Name
no.001 | Name1 | A,B,C
no.001 | Name2 | D
no.002 | Name2 | D
no.002 | Name1 | A,B,C
and the query I use:
SELECT
s.SO_NO AS int, c.Cust_Name,
ITEMS = STUFF((SELECT DISTINCT ','+ Item_Name
FROM TBL_SO_LIITEM item
INNER JOIN TBL_Sales_SO s ON s.Sales_SO_ID = item.Sales_So_ID
WHERE c.Cust_ID = s.Cust_ID
FOR XML PATH('')), 1, 1, '')
FROM
TBL_Sales_SO s, Tbl_Customer c
WHERE
c.Cust_Name IN ('Name1','Name2')
ORDER BY
SO_NO
You seem to want:
SELECT s.SO_NO AS int, c.Customer_Name,
STUFF( (SELECT DISTINCT ',' + oi.Item_Name
FROM TBL_SO_LIITEM oi
WHERE s.Sales_SO_ID = oi.Sales_So_ID
FOR XML PATH('')), 1, 1,''
) as items
FROM TBL_Sales_SO s JOIN
Tbl_Customer c
ON s.cust_id = c.cust_id
ORDER BY SO_NO;
Note that the tables are only included once, either in the subquery or in the outer query. The naming conventions for your data is quite inconsistent, so the above is my best guess.

How to create view with concatenated list and only unique rows

Using SSMS 18 I am trying to create a view that joins three tables.
[Client_Info]
+----------+-----------+------------+
| ClientID | LastName | FirstName |
+----------+-----------+------------+
| 1 | Smith | John |
| 2 | Doe | Jane |
[Products_Ordered]
+----------+-------------+
| ClientID | ProductID |
+----------+-------------+
| 1 | 111 |
| 1 | 222 |
| 2 | 111 |
[Product_Info]
+-----------+--------------+
| ProductID | ProductName |
+-----------+--------------+
| 111 | Apples |
| 222 | Oranges |
I want to end up with the following output, where the products are concatenated into a list for each client.
+----------+-----------+------------+------------------+
| ClientID | LastName | FirstName | Products |
+----------+-----------+------------+------------------+
| 1 | Smith | John | Apples, Oranges |
| 2 | Doe | Jane | Apples |
So far, this is what I have
CREATE VIEW [dbo].[uvw_summary]
AS
SELECT A.[ClientID]
, A.[LASTNAME]
, A.[FIRSTNAME]
, (
SELECT CONVERT(VARCHAR(MAX), C.[ProductName]) + ', '
FROM [Products_Ordered] AS B
JOIN [Product_Info] AS C
ON B.[ProductID] = C.[ProductID]
WHERE A.[ClientID] = B.[ClientID]
ORDER BY B.[ProductID]
FOR XML PATH ('')
) AS [Products]
FROM [Client_Info] AS A
JOIN [Products_Ordered] AS B
ON A.[ClientID] = B.[ClientID]
JOIN [Product_Info] AS C
ON B.[ProductID] = C.[ProductID]
With the existing code, I get two rows per client when they have more than one order.
+----------+-----------+------------+------------------+
| ClientID | LastName | FirstName | Products |
+----------+-----------+------------+------------------+
| 1 | Smith | John | Apples, Oranges |
| 1 | Smith | John | Apples, Oranges |
| 2 | Doe | Jane | Apples |
Just remove the joins in the outer query, they are redundant:
CREATE VIEW [dbo].[uvw_summary]
AS
SELECT A.[ClientID]
, A.[LASTNAME]
, A.[FIRSTNAME]
, ( SELECT CONVERT(VARCHAR(MAX), C.[ProductName]) + ', '
FROM [Products_Ordered] AS B
JOIN [Product_Info] AS C
ON B.[ProductID] = C.[ProductID]
WHERE A.[ClientID] = B.[ClientID]
ORDER BY B.[ProductID]
FOR XML PATH ('')
) AS [Products]
FROM [Client_Info] AS A;
It is also probably worth using TYPE along with .value() so that you don't get issues when your string contains special XML characters
CREATE VIEW [dbo].[uvw_summary]
AS
SELECT A.[ClientID]
, A.[LASTNAME]
, A.[FIRSTNAME]
, ( SELECT CONVERT(VARCHAR(MAX), C.[ProductName]) + ', '
FROM [Products_Ordered] AS B
JOIN [Product_Info] AS C
ON B.[ProductID] = C.[ProductID]
WHERE A.[ClientID] = B.[ClientID]
ORDER BY B.[ProductID]
FOR XML PATH (''), TYPE
).value('.', 'NVARCHAR(MAX)') AS [Products]
FROM [Client_Info] AS A;
Finally, you say you are using SSMS 18, which doesn't really mean much as SSMS is a user interface, and doesn't tell us anything about the version of SQL Server you are using, but if you are using a more recent version of SSMS, then you might be using a more recent version of SQL Server, if you are, you can use STRING_AGG:
CREATE VIEW [dbo].[uvw_summary]
AS
SELECT A.[ClientID]
, A.[LASTNAME]
, A.[FIRSTNAME]
, STRING_AGG(C.[ProductName], ',') WITHIN GROUP ORDER BY c.ProductName)
FROM [Client_Info] AS A
JOIN [Products_Ordered] AS B
ON A.[ClientID] = B.[ClientID]
JOIN [Product_Info] AS C
ON B.[ProductID] = C.[ProductID]
GROUP BY a.[ClientID], A.[LASTNAME], A.[FIRSTNAME];

SQL - IN clause with no match

I'm trying to build a query where I can select from a table all products with a certain ID but I would also like to find out what products were not found within the IN clause.
Product Table
ID | Name
---|---------
1 | ProductA
2 | ProductB
4 | ProductD
5 | ProductE
6 | ProductF
7 | ProductG
select *
from products
where id in (2,3,7);
As you can see, product id 3 does not exist in the table.
My query will only return rows 2 and 7.
I would like a blank/null row returned if a value in the IN clause did not return anything.
Desired Results:
ID | Name
---|---------
2 | ProductB
3 | null
7 | ProductG
You can use a left join:
select i.id, p.name
from (select 2 as id union all select 3 union all select 7
) i left join
products p
on p.id = i.id
IN is not useful in this case.
Use a CTE with the ids that you want to search for and left join to the table:
with cte(id) as (select * from (values (2),(3),(7)))
select c.id, p.name
from cte c left join products p
on p.id = c.id
See the demo.
Results:
| id | Name |
| --- | -------- |
| 2 | ProductB |
| 3 | |
| 7 | ProductG |

Find duplicate combinations

I need a query to find duplicate combinations in these tables:
AttributeValue:
id | name
------------------
1 | green
2 | blue
3 | red
4 | 100x200
5 | 150x200
Product:
id | name
----------------
1 | Produkt A
ProductAttribute:
id | id_product | price
--------------------------
1 | 1 | 100
2 | 1 | 200
3 | 1 | 100
4 | 1 | 200
5 | 1 | 100
6 | 1 | 200
7 | 1 | 100 -- duplicate combination
8 | 1 | 100 -- duplicate combination
ProductAttributeCombinations:
id_product_attribute | id_attribute
-------------------------------------
1 | 1
1 | 4
2 | 1
2 | 5
3 | 2
3 | 4
4 | 2
4 | 5
5 | 3
5 | 4
6 | 3
6 | 5
7 | 1
7 | 4
8 | 1
8 | 5
I need SQL that creates result like:
id_product | duplicate_attributes
----------------------------------
1 | {7,8}
If I understand correct, 7 is a duplicate of 1 and 8 is a duplicate of 2. As phrased, your question is a bit confusing, because 7 and 8 are not related to each other and the only table of interest is ProductAttributeCombinations.
If this is the case, then one method is to use string aggregation
with combos as (
select id_product_attribute,
string_agg(id_attribute::text, ',' order by id_attribute) as combo
from ProductAttributeCombinations pac
group by id_product_attribute
)
select *
from combos c
where exists (select 1
from combos c2
where c2.id_product_attribute > c.id_product_attribute and
c2.combo = c.combo
);
Your question leaves some room for interpretation. Here is my educated guess:
For each product, return an array of all instances with the same set of attributes as any other instance of the same product with smaller ID.
WITH combo AS (
SELECT id_product, id, array_agg(id_attribute) AS attributes
FROM (
SELECT pa.id_product, pa.id, pac.id_attribute
FROM ProductAttribute pa
JOIN PoductAttributeCombinations pac ON pac.id_product_attribute = pa.id
ORDER BY pa.id_product, pa.id, pac.id_attribute
) sub
GROUP BY 1, 2
)
SELECT id_product, array_agg(id) AS duplicate_attributes
FROM combo c
WHERE EXISTS (
SELECT 1
FROM combo
WHERE id_product = c.id_product
AND attributes = c.attributes
AND id < c.id
)
GROUP BY 1;
Sorting can be inlined into the aggregate function so we don't need a subquery for the sort (like #Gordon already provided). This is shorter, but also typically slower:
WITH combo AS (
SELECT pa.id_product, pa.id
, array_agg(pac.id_attribute ORDER BY pac.id_attribute) AS attributes
FROM ProductAttribute pa
JOIN PoductAttributeCombinations pac ON pac.id_product_attribute = pa.id
GROUP BY 1, 2
)
SELECT ...
This only returns products with duplicate instances.
SQL Fiddle.
Your table names are rather misleading / contradict the rest of your question. Your sample data is not very clear either, only featuring a single product. I assume there are many in your table.
It's also unclear whether you are using double-quoted table names preserving CaMeL-case spelling. I assume: no.

distinct rows with group by

I have one table:
id_object | version | document
------------------------------
1 | 1 | 1
1 | 2 | 2
2 | 1 | 3
2 | 2 | 1
2 | 3 | 2
1 | 1 | 3
I want to show only one row by object with the version (max) and the document. I have tried the following"
Select Distinct
id_object ,
Max(version),
document
From
prods
Group By
id_object, document
and I get this result
1 | 1 | 1
1 | 2 | 2
2 | 1 | 3
2 | 2 | 1
2 | 3 | 2
1 | 1 | 3
As you can see, I'm getting the entire table. My question is, why?
Since you group by id_object and document, you won't get your desired result. That is because document is different for each version.
select x.id_object,
x.maxversion as version,
p.document
from
(
Select id_object, Max(version) as maxversion
From prods
Group By id_object
) x
inner join prods p on p.id_object = x.id_object
and p.version = x.maxversion
You first have to select the id_object with the max(version). That can be joined with the actual data to get the correct document.
You have to do that because you can't select columns that are not in your group by clause, except you use a aggregate function on them (like max() for instance).
(MySQL can select non aggregated columns, but please avoid that since the outcome is not always clear or even predictable)
select prods.id_object, version, document
from prods inner join
(select id_object, max(version) as ver
from prods
group by id_object) tmp on prods.id_object = tmp.id_object and prods.version = tmp.ver
Query:
SQLFIDDLEExample
SELECT p.id_object,
p.version,
p.document
FROM prods p
WHERE p.version = (SELECT Max(version)
FROM prods
WHERE id_object = p.id_object)
Result:
| ID_OBJECT | VERSION | DOCUMENT |
----------------------------------
| 1 | 2 | 2 |
| 2 | 3 | 2 |